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

1 篇文章 0 订阅
1 篇文章 0 订阅
还存在一些问题,再研究一下
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属性来模拟“模态”的效果。

 publicclass 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关闭的时候关闭
privatevoid 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 = parentas Window;
return
win != null ?
parent as Window :
GetOwnerWindow(parent);
}
}


使用的时候

var btn = senderas 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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现WPF弹出窗口居中,有几种方法可以实现。 一种方法是在XAML中设置WindowStartupLocation属性为CenterScreen,这将使窗口显示自动居中于屏幕。这可以通过在XAML文件中添加以下代码来实现: 另一种方法是在代码中使用Window类的Left和Top属性来计算窗口的位置。你可以在xaml.cs文件中实现一个名为CenterWindowOnScreen的方法,该方法通过获取主屏幕的宽度和高度以及窗口的宽度和高度,计算出窗口的左上角位置,从而使窗口居中于屏幕。你可以在构造函数或窗口加载事件中调用这个方法。 另外,如果你想要判断子窗口与父窗口是否在同一屏幕,你可以使用Screen类和Graphics类。通过获取父窗口的句柄,使用Screen.FromHandle方法获取父窗口所在的屏幕,并计算子窗口的左上角位置。如果子窗口的位置在父窗口所在的屏幕范围内,则它们在同一屏幕上。 综上所述,要实现WPF弹出窗口居中,你可以在XAML中设置WindowStartupLocation属性为CenterScreen,或者在代码中计算窗口的位置并使用Left和Top属性进行设置。同,你可以使用Screen类和Graphics类来判断子窗口与父窗口是否在同一屏幕上。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [WPF 窗体居中](https://blog.csdn.net/MrBaymax/article/details/89810731)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [WPF 窗口居中 & 变更触发机制](https://blog.csdn.net/sD7O95O/article/details/125465679)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值