【WPF】如何让弹出的窗口"阻塞"

您所在的位置:网站首页 阻止弹框怎么关闭 【WPF】如何让弹出的窗口"阻塞"

【WPF】如何让弹出的窗口"阻塞"

2024-02-23 19:52| 来源: 网络整理| 查看: 265

还存在一些问题,再研究一下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.html2、用异步模式来写。代码将类似于:var win = new MyDialog();// MyCloseCallback是一个窗口关闭时调用的回调win.Show(MyCloseCallback);这样确实可以满足需求,不过我们不得不忍受把一份代码拆开为两份来写的痛苦。当然,匿名方法和Lamba可以带来一些缓解,但是写起来始终不是那么舒服。难道,我们除了使用可怕的while死循环+DoEvents,或者忍受别扭的代码之外,就没有别的办法了么?

【揭开ShowDialog的面纱】

其实,回过头再去想,为什么ShowDialog方法就可以阻塞代码的执行呢?如果我们能够知道如何阻塞代码,然后把“挡住”整个窗口的部分给去掉,不就OK了么?好,我们请出神器Reflector打开Window的ShowDialog方法一窥究竟。

Codepublic bool? ShowDialog(){        try    {        this._showingAsDialog = true;        this.Show();    }    catch    {            }    finally    {        this._showingAsDialog = false;    }    }核心的部分如上所示,其实ShowDialog只是置了一个状态,最主要的代码还是在Show里面。好,我们接着转战到Show Codepublic void Show(){    this.VerifyContextAndObjectState();    this.VerifyCanShow();    this.VerifyNotClosing();    this.VerifyConsistencyWithAllowsTransparency();    this.UpdateVisibilityProperty(Visibility.Visible);    this.ShowHelper(BooleanBoxes.TrueBox);}

 

前面都是在做一些检查,最终的调用原来跑到了ShowHelper Codeprivate 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属性来模拟“模态”的效果。

 

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

使用的时候

Code            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



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3