【WPF】如何讓彈出的窗口"阻塞"


還存在一些問題,再研究一下
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;
    }
    
}
核心的部分如上所示,其實ShowDialog只是置了一個狀態,最主要的代碼還是在Show里面。好,我們接着轉戰到Show

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

 
前面都是在做一些檢查,最終的調用原來跑到了ShowHelper

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));

代碼下載

/Files/RMay/TryMessageBox.zip


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM