1、ComponentDispatcher其實可以不用
2、new一個DispatcherFrame其實是把一個消息循環(姑且稱作嵌套消息循環)當做一個DispatchFrame來處理,在這個消息循環結束之前,原來的代碼就是阻塞的
3、正是因為第二個原因,如果再次彈出一個窗口,將是在前一個嵌套消息循環中,再次執行2,也就會導致第一個窗口關閉,並不會立即執行后面的代碼。
【場景描述】
某些時候可能會有這種需求,一個用戶界面里面分為好多個功能區域。這些功能區域有時候會有一些“模態”的彈出窗口的交互,這些彈出窗口需要:
1、只影響當前區域。即鼠標或者鍵盤無法操作當前區域,而其他區域不受影響。比如說,有好多個選項卡頁面,每個選項卡頁面彈出的窗口只影響當前選項卡,而不影響其他的選項卡。
2、窗口未關閉之前,后面的代碼不執行,即“阻塞”。
【問題分析】
WPF中的窗口的彈出提供了兩個方法:Show和ShowDialog。其中,Show方式是一個非“阻塞”的方法,即:調用完Show之后立即返回,后面的代碼將立即被執行,但是這個方法不會擋住整個窗口。而ShowDialog方法是一個“阻塞”的方法,即調用完ShowDialog之后需要等窗口關閉代碼才會繼續執行。然而,ShowDialog會擋住整個窗口,而不是指定的區域。
綜合這個兩個方法考慮,其實,對於場景中的第一條,我們可以考慮通過調用Show讓窗口彈出之后,即將該區域置為IsEnabled=false來模擬“模態”的窗口,這個倒不是很難。問題在於第二條,如何讓窗口“阻塞”住當前代碼的繼續執行呢?即:
var win = new MyDialog();
win.Show();
// 關閉后才要執行的代碼
1、用一個while死循環,直到關閉才跳出。如果采用這種方式,那我們不得不好好考慮一下,在UI線程做死循環UI還有沒有辦法響應。當然,方法是有的,可以實現一個WPF版的DoEvents方法,然后在循環中調用。DoEvents相關的代碼可以參見:http://www.cnblogs.com/sheva/archive/2006/08/24/485790.html
2、用異步模式來寫。代碼將類似於:
var win = new MyDialog();
// MyCloseCallback是一個窗口關閉時調用的回調
win.Show(MyCloseCallback);
這樣確實可以滿足需求,不過我們不得不忍受把一份代碼拆開為兩份來寫的痛苦。當然,匿名方法和Lamba可以帶來一些緩解,但是寫起來始終不是那么舒服。
難道,我們除了使用可怕的while死循環+DoEvents,或者忍受別扭的代碼之外,就沒有別的辦法了么?
【揭開ShowDialog的面紗】
其實,回過頭再去想,為什么ShowDialog方法就可以阻塞代碼的執行呢?如果我們能夠知道如何阻塞代碼,然后把“擋住”整個窗口的部分給去掉,不就OK了么?
好,我們請出神器Reflector打開Window的ShowDialog方法一窺究竟。

public bool? ShowDialog()
{


try
{
this._showingAsDialog = true;
this.Show();
}
catch
{


}
finally
{
this._showingAsDialog = false;
}


}

public void Show()
{
this.VerifyContextAndObjectState();
this.VerifyCanShow();
this.VerifyNotClosing();
this.VerifyConsistencyWithAllowsTransparency();
this.UpdateVisibilityProperty(Visibility.Visible);
this.ShowHelper(BooleanBoxes.TrueBox);
}

private object ShowHelper(object booleanBox)
{
if (!this._disposed)
{


if (this._showingAsDialog && this._isVisible)
{
try
{
ComponentDispatcher.PushModal();
this._dispatcherFrame = new DispatcherFrame();
Dispatcher.PushFrame(this._dispatcherFrame);
}
finally
{
ComponentDispatcher.PopModal();
}
}
}
return null;
}
ComponentDispatcher.PushModal();
ComponentDispatcher是個什么東東?MSDN之:
Enables shared control of the message pump between Win32 and WPF in interoperation scenarios.
原來是用於操作消息循環的。
而這兩句:
this._dispatcherFrame = new DispatcherFrame();
Dispatcher.PushFrame(this._dispatcherFrame);
如果有看過DoEvents的實現,就好理解了,簡單的說,這里其實是讓UI可以繼續處理消息隊列。
【實現】
有了以上准備,我們就很好處理了。
1、首先,我們通過模仿ShowHelper中的代碼,來實現阻塞代碼的執行
2、其次,我們通過設置IsEnabled屬性來模擬“模態”的效果。

public class MyDialog : Window
{
private DispatcherFrame _dispatcherFrame;
private FrameworkElement _container = null;
private double _lastLeft = 0.0;
private double _lastTop = 0.0;
public void Open(FrameworkElement container)
{
if (container != null)
{
_container = container;
// 通過禁用來模擬模態的對話框
_container.IsEnabled = false;
// 保持總在最上
this.Owner = GetOwnerWindow(container);
if (this.Owner != null)
{
this.Owner.Closing += new System.ComponentModel.CancelEventHandler(Owner_Closing);
}
// 通過監聽容器的Loaded和Unloaded來顯示/隱藏窗口
_container.Loaded += new RoutedEventHandler(Container_Loaded);
_container.Unloaded += new RoutedEventHandler(Container_Unloaded);
}
this.Show();
try
{
ComponentDispatcher.PushModal();
_dispatcherFrame = new DispatcherFrame(true);
Dispatcher.PushFrame(_dispatcherFrame);
}
finally
{
ComponentDispatcher.PopModal();
}
}
// 在Owner關閉的時候關閉
private void Owner_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
this.Close();
}
private void Container_Unloaded(object sender, RoutedEventArgs e)
{
// 只能通過這種方式隱藏,而不能通過Visiblity = Visibility.Collapsed,否則會失效
_lastLeft = this.Left;
_lastTop = this.Top;
this.Left = -10000;
this.Top = -10000;
}
private void Container_Loaded(object sender, RoutedEventArgs e)
{
this.Left = _lastLeft;
this.Top = _lastTop;
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
base.OnClosing(e);
if (_container != null)
{
_container.Loaded -= Container_Loaded;
_container.Unloaded -= Container_Unloaded;
}
if (this.Owner != null)
{
this.Owner.Closing -= Owner_Closing;
}
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
// 當關閉終止消息循環
if (_dispatcherFrame != null)
{
_dispatcherFrame.Continue = false;
}
// 這里必須強制調用一下
// 否則出現同時點開多個窗口時,只有一個窗口讓代碼繼續
ComponentDispatcher.PopModal();
if (_container != null)
{
_container.IsEnabled = true;
}
}
private Window GetOwnerWindow(FrameworkElement source)
{
var parent = VisualTreeHelper.GetParent(source) as FrameworkElement;
if (parent == null)
return null;
var win = parent as Window;
return
win != null ?
parent as Window :
GetOwnerWindow(parent);
}
}
使用的時候

var btn = sender as Button;
var dialog = new MyDialog
{
Width = 300,
Height = 200,
Content = btn.Tag,
WindowStartupLocation = WindowStartupLocation.CenterOwner
};
var border = this.FindName((string)btn.Tag) as Border;
dialog.Open(border);
// 在窗口關閉之前不會執行
MessageBox.Show(string.Format("{0}上彈出對話框結束!", btn.Tag));
代碼下載