问题

如何在WinForm的一个Form里面弹出一个模态Dialog?

背景

程序的框架是Winform,只有一个窗口MainForm。MainForm里面是一个TabControl,每个TabPage是一个Form,每个TabPage的Form相互独立,互不干扰,TabPage间可以随时切换。由于有某些需求,TabPage需要接受用户输入,并等待输入完成,才能执行后面的代码,此时,程序是需要阻塞等待输入的,所以需要弹出一个模态Dialog。

  1. 为什么不用MessageBox呢?因为MessageBox是直接弹出一个模态对话框且该对话框是一个新的窗口,这时候整个MainForm是伪阻塞状态,用户无法通过与MainForm的其他区域交互,包括点击标签页切换到其他TabPage。所以,我需要该对画框只在Form里显示。
  2. 为什么不用MDI呢? 最主要的原因是TabControl里的Form,其TopLevel属性是false的,如果想在Form里面添加MDI窗口,需要将Form的TopLevel属性设置为true,这时我将无法使用TabControl。

代码实现

创建一个CustomDialog类,继承Form

public class CustomDialog : Form{

}

创建CustomDialog成员变量

  1. 这里使用到了两个类, PanelControlContainer。其中Panel充当CustomDialog的容器。ControlContainer则是Panel的容器。
public class CustomDialog : Form{
private Panel? _panelContainer;
private ControlContainer? _parentContainer;
private Form? _parentForm;
// 声明Panel,ControlContainer和Form
}

定义一个ShowDialog方法

要显示模态Dialog,当然要是实现ShowDialog方法啦!这里定义了一个ShowDialog方法,和其他ShowDialog方法有些许不同,该方法的参数是ControlContainer类型, 用于接收一个控件作为父控件

public class CustomDialog : Form{
public void ShowDialog(ControlContainer parentControl){
//TODO
}

设置CustomDialog.PaneContainer的属性和内容

这部分代码最主要实现了CustomDialog在它的父控件Form中显示的功能,PS:有点简单粗暴,但是有效(_)

public class CustomDialog : Form{
private void AddDialogToTheView(){
if(ContainerControl is null){
throw new NullReferenceException(nameof(_parentContainer));
}
//panel的高度
int panelHeight = 350;
int panelWidth = 500; //panel显示的位置
int startUpLocationX = (_parentContainer.ClientSize.Width - panelWidth) / 2;
int startUpLocationY = (_parentContainer.ClientSize.Height - panelHeight) / 2; // 设置_panelContainer的属性
_panelContainer = new Panel(){
Height = panelHeight,
Width = panelWidth,
Location = new Point(startUpLocationX, startUpLocationY),
}; // 设置Dialog的属性
TopLevel = false;
DockStyle = DockStyle.Fill;
//添加进Panel里面
_panelContainer.Controls.Add(this);
Contianer.Controls.Add(_panelContainer);
// 显示Dialog
Show();
PanelControl.BringToFront(); } }

实现伪阻塞

要说实现这个CustomDialog哪里最难,应该是这个伪阻塞功能最难。前面的View相关的方案,一般人稍微思考一下都可以想出来。但是想优雅的实现CustomDialog的伪阻塞功能,确实不易

  • 如何阻塞一段代码?

    我最初的做法是这样的:
public void WaitForExit(Cancellationtoken token){
while(!toke.IsCancellationRequested){
Application.DoEvents();
}
}
CancellationTokenSource source = new CancellationTokenSource();
WaitForExit(source.Token); //user cancel
source.Cancel();

这个写法有效,但还是不够优雅 ps:切记请勿在UI线程中直接使用while(true){}

  • 最后我的写法是这样的:

    在这里我使用了一个Winform中默认没有的命名空间:System.Windows.Threading

    在csproj里开启wpf的命名空间
<enableWpf>true</enableWpf>

这里其实是借鉴了wpf的模态Dialog的实现方式,具体可以参考wpf的源码;有现成的轮子?直接偷!

public class CustomDialog : Form{
private DispatcherFrame? CurrentDispatcherFrame;
private void WaitForExit(){
try{ ComponentDispatcher.PushModal();
CurrentDispatcherFrame = new DispatcherFrame(true);
Dispatcher.PushFrame(CurrentDispatcherFrame);
}
finally{
ComponentDispatcher.PopModal();
}
}
}

当调用WaitForExit()方法后,程序就进入伪阻塞状态了,此时UI线程仍然能绘制UI;直到调用CurrentDispatcherFrame?.Continue = false;,WaitForExit才会退出伪阻塞状态。

细节优化

  • 这个时候,整个CustomDialog的大体实现基本完成了,下一步就是优化细节

重写Form的Closed事件

当调用CustomDialog的Close()方法时,会触发Form.OnClosed事件,此时阻塞状态将会退出

protected override void OnClosed(EventArgs e){
base.OnClosed(e);
if(CurrentDispatcherFrame is not null){
CurrentDispatcherFrame.Continue = false;
CurrentDispatcherFrame = null;
}
}

在ParentForm中注册关闭事件

在CustomDialog弹出的状态,如果用户想退出程序,点击MainForm的关闭按钮,此时是关闭不了的。MainForm是需要等CustomDialog关闭后才能关闭的,而CustomDialog需要等待用户关闭才能关闭。此时需要将MainForm的关闭事件注册到CustomDialog的关闭事件上。

  1. ParentForm_Closing事件
private void ParentForm_Closing(object? sender, CancelEventArgs e){
this.Close();//ParentForm关闭时,关闭CustomDialog
}
  1. ParentForm订阅关闭事件
public void ShowDialog(ContainerControl _parentContainer){

    if(_parentContainer is Form containerForm && containerForm.TopLevel){
this.Owner = containerForm;
}
_parentForm = _parentContainer.ParentForm;
_parentForm.Closing += ParentForm_Closing;//订阅关闭事件 //TO DO
}

重写Form的Closing事件

订阅了closing事件记得也要取消订阅

protected override void OnClosing(CancelEventArgs e){
base.OnClosing(e);
if(_parentForm is not null){
_parentForm.Closing -= ParentForm_Closing;
}
}

完整的ShowDialog方法

public IAsyncResult ShowDialogAsync(ContainerControl _parentContainer){
var asyncResult = _parentContainer.BeginInvoke(new Action(() =>
{
if(_parentContainer is Form containerForm && containerForm.TopLevel){
this.Owner = containerForm;
}
_parentForm = _parentContainer.ParentForm;
_parentForm.Closing += ParentForm_Closing;//订阅关闭事件 AddDialogToTheView(); //已完成
WaitForExit(); //已完成
RemoveTheDialogFromTheView();//TODO 这里懒得写了
}));
return asyncResult;
}

同步方法

public void ShowDialog(ContainerControl _parentContainer){
var asyncResult = ShowDialogAsync(_parentContainer);
asyncResult.AsyncWaitHandle.WaitOne();
}

结语

  • 至此,CustomDialog已经可以使用了。定制的DialogForm,只需要继承CustomDialog即可。其他交互逻辑在子类中实现即可

其他细节

当Form改变的时候,自动调整CustomDialog到Form中间:向_parentContainer订阅SizeChanged事件

protected void _parentContainer_SizeChanged(object sender, EventArgs e){
if (sender is not ContainerControl control || _containerPanel is null)
{
return;
} _panelContainer.Location = new Point((control.ClientSize.Width - _containerPanel.Width) / 2, (control.ClientSize.Height - _containerPanel.Height) / 2);
}

[Winform]在Form里显示模态对话框ModalDialog的更多相关文章

  1. js之模态对话框

    目标效果:点击页面按钮,显示模态对话框,在模态对话框里点击取消关闭模式对话框. 效果如下 实现代码如下: <!DOCTYPE html> <html lang="en&qu ...

  2. VC++模态对话框和非模态对话框

    MFC中有两种类型的对话框:模态对话框和非模态对话框.  模态对话框是指当其显示时,程序会暂停执行,直到关闭这个模态对话框后,才能继续执行程序中其他任务.非模态对话框是指当其显示时,允许转而执行程序中 ...

  3. QDialog 模态对话框与事件循环(exec其实就是调用了show和eventLoop.exec)

    起源 qtcn中文论坛中有网友问到: 假设程序正常运行时,只有一个简单的窗体A,此时只有一个GUI主线程,在这个主线程中有一个事件循环处理窗体上的事件.当此程序运行到某阶段时,弹出一个模态窗体B(书上 ...

  4. jQuery练习 | 模态对话框(添加删除)

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. 10wx.showToast消息提示框 wx.showModal模态对话框

    1==>wx.showToast 弹出层 在界面交互中 显示消息提示框 它是一个消失提示框 提示用户成功 或者失败等消息 <button size='mini' bindtap='hanl ...

  6. Qt对话框之二:模态、非模态、半模态对话框

    一.模态对话框 模态对话框:阻塞同一应用程序中其它可视窗口输入的对话框.模态对话框有自己的事件循环,用户必须完成这个对话框中的交互操作,并且关闭了它之后才能访问应用程序中的其它任何窗口. 显示模态对话 ...

  7. 界面交互~Toast和模态对话框

    界面交互 名称 功能说明 wx.showToast 显示消息提示框 wx.showModal 显示模态对话框 wx.showLoading 显示 loading 提示框 wx.showActionSh ...

  8. 【原创】WinForm 模态对话框

    今天解决的一个问题,记录下,以备后用. 问题描述:WinForm程序有超时自动退出功能,但是有些模态对话框不关掉的话会退出失败,原因(猜测): 程序倒计时用的System.Windows.Forms. ...

  9. VS2010/MFC对话框:非模态对话框的创建及显示

    非模态对话框的创建及显示 上一节讲了模态对话框及其弹出过程,本节接着讲另一种对话框--非模态对话框的创建及显示. 已经说过,非模态对话框显示后,程序其他窗口仍能正常运行,可以响应用户输入,还可以相互切 ...

  10. VS2010/MFC编程入门之十二(对话框:非模态对话框的创建及显示)

    上一节鸡啄米讲了模态对话框及其弹出过程,本节接着讲另一种对话框--非模态对话框的创建及显示. 鸡啄米已经说过,非模态对话框显示后,程序其他窗口仍能正常运行,可以响应用户输入,还可以相互切换.鸡啄米会将 ...

随机推荐

  1. 即时通讯技术文集(第32期):IM开发综合技术合集(Part5) [共12篇]

    为了更好地分类阅读 52im.net 总计1000多篇精编文章,我将在每周三推送新的一期技术文集,本次是第32 期. [- 1 -] IM开发干货分享:如何优雅的实现大量离线消息的可靠投递 [链接]  ...

  2. [LC1302] 层数最深叶子节点的和

    题目概述 给你一棵二叉树的根节点 root ,请你返回 层数最深的叶子节点的和 . 基本思路 这是一个简单的树的遍历的问题,可以用bfs或者dfs来解题.这里采用dfs来解,在遍历的过程中,只需要用全 ...

  3. Chrome谷歌浏览器自动升级后页面字体过小

    谷歌浏览器使用一段时间后系统自动升级后页面字体突然变小,如何进行设置呢,如下 1.在页面右上角选择浏览器设置-外观-自定义字体-设置字号等其他需要配置的参数即可

  4. 教你实现GPUImage【OpenGL渲染原理】

    一.前言 本篇主要讲解GPUImage底层是如何渲染的,GPUImage底层使用的是OPENGL,操控GPU来实现屏幕展示 由于网上OpenGL实战资料特别少,官方文档对一些方法也是解释不清楚,避免广 ...

  5. weixueyuan-Nginx编译及部署1

    https://www.weixueyuan.net/nginx/ Nginx是什么 Nginx(发音同"engine x")是一个高性能的反向代理和 Web 服务器软件,最初是由 ...

  6. 第一个shell脚本(bash脚本)

    首先它是一个脚本,并不能作为正式的编程语言.因为是跑在linux的shell中,所以叫shell脚本.说白了,shell脚本就是一些命令的集合.运维工作中把常用的一系列的操作都记录到一个文档中,然后去 ...

  7. Ubuntu13 安装vim

    问题 由于系统没有vim,只有vi,而vi 编辑文件时比较麻烦,不易操作,还没有关键词高亮显示等,故想安装vim 输入命令: sudo apt install vim 报错,找不到 apt 命令,即没 ...

  8. TCP/IP协议笔记

    TCP/IP 一.TCP/IP简介 TCP/IP 指传输控制协议/网际协议(Transmission Control Protocol / Internet Protocol),是用于因特网 (Int ...

  9. Visual Studio各个版本密钥

    1.VS2012 旗舰版:YKCW6-BPFPF-BT8C9-7DCTH-QXGWC 2.VS2013 旗舰版:BWG7X-J98B3-W34RT-33B3R-JVYW9 专业版:XDM3T-W3T3 ...

  10. AI工具推荐:领先的开源 AI 代码助手——Continue

    前言 之前介绍了VS Code中的AI插件Cline与Roo Code,这两个都是根据给定一个任务,开始自动写代码的.除了这两个AI代码工具之外,在平常我还很喜欢的就是Continue . Conti ...