發現這個問題時,隱約記得之前有人已經發過這個問題,想把鏈接放到這裡,不過找了半天,實在找不到。日後如果找到了一定加上。
問題描述:用ShowDialog方法彈出一個模態對話框,然後將此對話框的Visibility屬性設置為Hidden,再設置回Visible,發現這個對話框已經不是模態的了。
有人會覺得關就關了得了,也不會有這個問題,干什麼要把Close取消掉然後再顯示出來呢?因為這是有應用環境的。
應用環境:有些對話框,從邏輯上就是單例的,比如Office和Visual Studio裡都有的查找對話框,顯然沒有必要同時顯示兩個。而且也沒有必要每次重新實例化並顯示出來,在用戶關閉窗體時,將窗體隱藏起來會更好,這樣上次查找的關鍵字還存在著。可以省去一些代碼保存這個歷史關鍵字。
當然,這種方式也會有不好的地方,歡迎大家指摘。
寫了一個程序來模擬這個Bug,效果如下面三張圖所示。
圖1. 主窗體,點第一個按鈕
圖2. 彈出的模態對話框,點擊按鈕將自己隱藏
圖3. 再點擊主窗體的最後一個按鈕,顯示出來,已經是非模態對話框了
以前發Bug,一般沒有去看過.NET的源代碼,這次感覺這個Bug 有點兒太不應該了,就看了看源代碼,發現WPF還特意為Dialog(模態的)的Hidden做了單獨的處理,感覺就更不應該有問題了,我們來看看源代碼。
DoDialogHide
[SecurityCritical, SecurityTreatAsSafe]
private void DoDialogHide()
{
SecurityHelper.DemandUnmanagedCode();
bool isActiveWindow = false;
if (this._dispatcherFrame != null)
{
this._dispatcherFrame.Continue = false;
this._dispatcherFrame = null;
}
if (!this._dialogResult.HasValue)
{
this._dialogResult = false;
}
this._showingAsDialog = false; //Cause this Bug
isActiveWindow = this._swh.IsActiveWindow;
this.EnableThreadWindows(true);
if ((isActiveWindow && (this._dialogPreviousActiveHandle != IntPtr.Zero)) && UnsafeNativeMethods.IsWindow(new HandleRef(this, this._dialogPreviousActiveHandle)))
{
UnsafeNativeMethods.SetActiveWindow(new HandleRef(this, this._dialogPreviousActiveHandle));
}
}
其中直接把_showingAsDialog設置為了false,當再次把窗體的Visibility設置為Visible的時候,Window類又會根據這個變量的值來判斷是否將窗體按模態的方式顯示出來。而MS對這行代碼的的注釋僅僅是“// clears _showingAsDialog”。
從源代碼上來看,WPF的Window似乎是使用下面的代碼將一個窗體從非模態變成模態的。
SetAsModal
try
{
//telluserswe'regoingmodal
ComponentDispatcher.PushModal();
_dispatcherFrame=newDispatcherFrame();
Dispatcher.PushFrame(_dispatcherFrame);
}
finally
{
//telluserswe'regoingnon-modal
ComponentDispatcher.PopModal();
}
但是當我自己在使用裡使用這個方法的時候,卻發現根本達不到目的。後來突然想到一個方法,試了一下,就可以。解決方法是,不使用Visibility = Visible,使窗體再次顯示出來。而且再調用一次ShowDialog方法來顯示這個窗體。這個方法也許只有對WPF不熟悉或是非常熟悉的人才能想得出來(我是死馬當作活馬醫碰對了),因為正常情況下,繼續地第二次調用ShowDialog方法是會拋出異常的。類似的詭異的Window的異常在[WPF]如何在關閉非模態子窗體時用消息框確認——解決最小化窗體時拋出的異常裡也有描述。
另外,在非UI線程彈出的MessageBox也是非模態的。這個解決方法很簡單,只要在Dispatcher裡彈出這個MessageBox就可以了。
本文配套源碼