C# 勇哥关于winform.Show() ,winform.ShowDialog() 窗体卡死、显示阻塞、无法置顶问题的研究

勇哥注:
一个winform窗口,被Show()后是非模态窗口,被ShowDialog()是模态窗口,这个知识是无人不晓的。
但是,有些时候,会产生弹出窗口卡死、阻塞显示、无法置顶的诸多问题。
这一篇和勇哥一起研究一下这些现象。


先来看下现象:

下面的form是由Show()方法弹出来的。屏幕产生了阻塞显示的现象。

image.png

看下任务管理器,提示这个弹出winform是“无响应”的。

image.png

如果你此时去操作调用者的ui上的功能,主程序ui也会跟着阻塞掉,全部失去响应。


在网上搜索类似问题,找到一个提问和勇哥遇到问题的一致,但是没有答案。

image.png


只能自己研究了。

勇哥试着在show()之后执行Refresh()刷新ui。

 AlarmTipsWin.StartPosition = FormStartPosition.CenterScreen;
 AlarmTipsWin.AddOrUpdate(alarmMsg, alarmtype);
 AlarmTipsWin.Show();

 AlarmTipsWin.Refresh();


可以看到界面显示阻塞的现象解决了,内容已经显示完整了。

image.png

但是实际上这时候此窗口还是阻塞卡死的状态,你的鼠标指针依然在转圈,任务管理器中还是显示“无响应”。

这种现象表明,因为Show()方法是非阻塞的方法,执行完后立刻返回继续下一条代码了,

再加上一句Refresh() 也仅仅是显示内容完整了,

但是接下来阻塞问题依旧。

所以仅仅是加Refresh() 是解决不了失去响应的问题的。


这时候,我想到,如果把Show()换成 ShowDialog() 不是就解决这个问题了吗?

因为ShowDialog()是阻塞的,除非窗口关闭,否则不会执行下一条代码。

等窗口真的关闭了,因为窗口都不存在了,自然后续不存在ui阻塞的问题了。

实验证明,我这样想是对的,这样改了后,解决了问题。


问题是解决了,但其实本文想继续深究一下这背后的知识点。

我们程序猿还是需要有点工匠精神的,多探索多有益。


勇哥引入几个问题:

(1)为啥这个时候ShowDialog()的窗口没有模态窗口的效果,即不会阻止你去操作主程序的ui。

(2)为啥从主程序的ui上同样用ShowDialog()弹出这个窗口,确实有模态窗口的效果。


上面的问题,说明了,调用者的线程不同,才是上面问题的关键。

对于(1)的问题,也就是上面做实验的时候,调用者是非ui线程,即非ui线程弹出的窗口。

而对于(2)的问题,在实验的时候,是从ui线程弹出的窗口。



即问题的本质在于,你这个winform.ShowDialog()是在哪里调用的?

如果是在非ui线程中调用的,则必须使用委托。

因此你得判断调用下面的代码的时候,决定是否需要使用委托。

Form2 form2 = new Form2(); 
form2.ShowDialog(this);


对 Windows 窗体控件进行线程安全调用,可以查询控件的 InvokeRequired 属性。


如果 InvokeRequired 返回 true,则使用实际调用控件的委托来调用 Invoke。

如果 InvokeRequired 返回 false,则直接调用控件。


下面的Form2窗体,你需要增加xShowDialog成员函数,如下:

public delegate DialogResult InvokeDelegate(Form parent);
public DialogResult xShowDialog(Form parent)
 {
     if (parent.InvokeRequired)
     {
         InvokeDelegate xShow = new InvokeDelegate(xShowDialog);
         parent.Invoke(xShow, new object[] { parent });
         return DialogResult;
     }
     return this.ShowDialog(parent);
 }

然后调用变成了下面:

Form2 form2 = new Form2(); 
form2.xShowDialog(this);



勇哥最后谈一下啥是ui线程:

C#中规定只能在ui线程上操作ui上的控件,否则会报跨线程操作ui的异常。

在winform中的定时器控件,它就是工作在ui线程上的一种定时器,因此你在里面写ui操作的代码不会报错。

但是你在winform的代码中申明一个Thread线程,此线程则不是工作在ui线程,在此线程中操作ui则会报异常。

ui线程和其它线程的区别之一是:ui线程有自己的消息循环,其它线程则没有。

通过这个区别可以判断当前线程是否是ui线程:

 if (!Application.MessageLoop)
 {
    var id = Thread.CurrentThread.ManagedThreadId;
    throw new ThreadStateException("not on the UI thread");
 }


在下面的例子里的函数是winform的Load事件代码里注册的一个类的事件处理函数。

可以看到,调用此函数的线程就不是一个ui线程。

这种情况很常见,大家一定要注意!


image.png



--------------------- 

作者:hackpig

来源:www.skcircle.com

版权声明:本文为博主原创文章,转载请附上博文链接!


本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2024年5月    »
12345
6789101112
13141516171819
20212223242526
2728293031
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864