一个关于.Net的SaveFileDialog控件(Winform)的有趣问题

场景:winform的程序中,有一个画面上放了一个Button,点击这个Button会调用.Net控件SaveFileDialog的ShowDialog方法。

场景很简单,但是碰到了这样一个有趣的问题:

在机器很慢的情况下,连续快速两次点击上述Button,会导致栈溢出异常(StackOverflowException)。

由于机器很慢的情况难以模拟且不能稳定重现,尝试在Button的点击事件中先用异步委托调一次SaveFileDialog.ShowDialog,然后再正常调用一次SaveFileDialog.ShowDialog,然后。。。问题重现了!

SaveFileDialog的基类CommonDialog代码如下:

 1 public DialogResult ShowDialog(IWin32Window owner)
 2 {
 3     IntSecurity.SafeSubWindows.Demand();
 4     if (!SystemInformation.UserInteractive)
 5     {
 6         throw new InvalidOperationException(SR.GetString("CantShowModalOnNonInteractive"));
 7     }
 8     NativeWindow window = null;
 9     IntPtr zero = IntPtr.Zero;
10     DialogResult cancel = DialogResult.Cancel;
11     try
12     {
13         if (owner != null)
14         {
15             zero = Control.GetSafeHandle(owner);
16         }
17         if (zero == IntPtr.Zero)
18         {
19             zero = UnsafeNativeMethods.GetActiveWindow();
20         }
21         if (zero == IntPtr.Zero)
22         {
23             window = new NativeWindow();
24             window.CreateHandle(new CreateParams());
25             zero = window.Handle;
26         }
27         if (helpMsg == 0)
28         {
29             helpMsg = SafeNativeMethods.RegisterWindowMessage("commdlg_help");
30         }
31         NativeMethods.WndProc d = new NativeMethods.WndProc(this.OwnerWndProc);
32         this.hookedWndProc = Marshal.GetFunctionPointerForDelegate(d);
33         IntPtr userCookie = IntPtr.Zero;
34         try
35         {
36             this.defOwnerWndProc = UnsafeNativeMethods.SetWindowLong(new HandleRef(this, zero), -4, d);
37             if (Application.UseVisualStyles)
38             {
39                 userCookie = UnsafeNativeMethods.ThemingScope.Activate();
40             }
41             Application.BeginModalMessageLoop();
42             try
43             {
44                 cancel = this.RunDialog(zero) ? DialogResult.OK : DialogResult.Cancel;
45             }
46             finally
47             {
48                 Application.EndModalMessageLoop();
49             }
50             return cancel;
51         }
52         finally
53         {
54             IntPtr windowLong = UnsafeNativeMethods.GetWindowLong(new HandleRef(this, zero), -4);
55             if ((IntPtr.Zero != this.defOwnerWndProc) || (windowLong != this.hookedWndProc))
56             {
57                 UnsafeNativeMethods.SetWindowLong(new HandleRef(this, zero), -4, new HandleRef(this, this.defOwnerWndProc));
58             }
59             UnsafeNativeMethods.ThemingScope.Deactivate(userCookie);
60             this.defOwnerWndProc = IntPtr.Zero;
61             this.hookedWndProc = IntPtr.Zero;
62             GC.KeepAlive(d);
63         }
64     }
65     finally
66     {
67         if (window != null)
68         {
69             window.DestroyHandle();
70         }
71     }
72     return cancel;
73 }

注意第36行代码:

this.defOwnerWndProc = UnsafeNativeMethods.SetWindowLong(new HandleRef(this, zero), -4, d);

上面的d指向这里:

 1 protected virtual IntPtr OwnerWndProc(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam)
 2 {
 3     if (msg != helpMsg)
 4     {
 5         return UnsafeNativeMethods.CallWindowProc(this.defOwnerWndProc, hWnd, msg, wparam, lparam);
 6     }
 7     if (NativeWindow.WndProcShouldBeDebuggable)
 8     {
 9         this.OnHelpRequest(EventArgs.Empty);
10     }
11     else
12     {
13         try
14         {
15             this.OnHelpRequest(EventArgs.Empty);
16         }
17         catch (Exception exception)
18         {
19             Application.OnThreadException(exception);
20         }
21     }
22     return IntPtr.Zero;
23 }

它使用了自己的消息处理器替代了SaveFileDialog所属画面的消息处理器,然后把画面的消息处理器暂存在this.defOwnerWndProc中,然后在自己的消息处理中再转调画面的消息处理器(见上面第5行)。

简单来说,Dialog拦截了Form的消息处理,在自己的消息处理器处理完后,再将消息分发会Form。

基于以上处理,如果连续调两次ShowDialog会如何??

第一次是OK的,但是当第二次执行第36行的代码时,SetWindowLong将会返回之前的处理器,即Dialog自己的消息处理器,然后保存在this.defOwnerWndProc中,等到Dialog自己的消息处理器处理完后,想要将消息再分发给Form时,this.defOwnerWndProc已经被修改为自己的消息处理器,然后就没有然后了。。。

时间: 2024-07-28 20:33:32

一个关于.Net的SaveFileDialog控件(Winform)的有趣问题的相关文章

C#在某个线程上创建的控件不能成为在另一个线程上创建的控件的父级

首先在form1的窗体载入中新建了一个Class1对象并将本身的引用传递进入其构造函数,然后在Class1的构造函数中创建一个线程.该线程所代理的方法事件是本类中的一个add方法.而add方法的内容则是在form1上放一个textbox.然而这个流程你需要注意的有几个问题:1.哪个是主线程?所谓主线程是第一个启动的线程,是从main开始的.form1的这个窗体是由主线程创建的.2.Thread t的线程是什么?t是由主线程创建的,t的操作内容是在由主线程创建的窗体上放一个textbox.也就是说

QTP回放时弹出 提示:一个或多个ActiveX控件无法显示

今天在录系统的脚本,回放时遇到以下的问题: 在QTP中弹出消息框,一个或多个ActiveX控件无法显示,如下图: 在Tools-->Options-->Active screen-->Advanced里把"Load ActiveX",即可解决你的问题. QTP回放时弹出 提示:一个或多个ActiveX控件无法显示

一个Activity掌握Design新控件

一个Activity掌握Design新控件 欢迎转载,转载请注明原文地址:http://blog.csdn.net/lavor_zl/article/details/51295364谢谢. 谷歌在推出Android5.0的同时推出了全新的设计Material Design,谷歌为了给我们提供更加规范的MD设计风格的控件,在2015年IO大会上推出了Design支持包,Design常用的新控件有下面8种. 1. TextInputLayout(文本输入布局) TextInputLayout的作用是

QTP录制后弹出框一个或多个ActiveX控件无法显示的解决方法

制一段脚本代码,在专家视图窗口中编辑录制的脚本代码时,会碰到弹出一个对话窗口,提示为"当前安全设置禁止运行该页中的ActiveX 控件,因此,该页可能无法正常显示"类似的东西,而在人工操作时却没有这样的现象.(如图) 问题主要出在于QTP设置本身,之所以在编辑录制好的脚本时,QTP出现关于Active的对话提示窗口,问题在于QTP的设置,要消除该提示窗口.应对QTP作如下设置:toos–options–Active Screen–Advanced–点选Load ActiveX cont

创建一个带模版的用户控件 V.3

再重构此篇<创建一个带模版的用户控件  V.2>http://www.cnblogs.com/insus/p/4164149.html 让其它动态实现header,Item和Footer. Insus.NET不想所有代码写在InstantiateIn(Control container)方法内的switch分流上.其实是想使用开发设计模式的中介者(Mediator)来拆分它. 拆分为四个方法:ListItemType.Header: ListItemType.Item: ListItemTyp

创建一个带模版的用户控件 V.2

前面有做练习<创建一个带模版的用户控件>http://www.cnblogs.com/insus/p/4161544.html .过于简化.通常使用数据控件Repeater会有网页写好Header,Item,AlternatingItem和Footer.如果需要动态产生列时,我们需要在后台写好模板. 再来复习一下这篇<Repeater控件动态变更列(Header,Item和Foot)信息>http://www.cnblogs.com/insus/archive/2013/03/22

轻量、强大的代码编辑器控件-WinForm完美版

前段时间做个小项目需要用到一个代码编辑器控件,但网上搜了半天,居然没发现一个完全满意的编辑器.现有的一些编辑器有: FastedTextBox 优点:1.  轻量. 2. 样式美观. 3. DEMO完整,文档丰富. 缺点:1. 不支持中文/中文字体显示难看 2. 代码折叠只能支持整行. ICSharpCode.AvalonEdit 优点:功能强大,几乎完美,非常接近 VS 缺点:1. 使用 WPF 开发. 2. 在 WinForm 使用不大稳定,比如不能同时开两个窗口,图形错位. 3. 内存多.

用c/c++混合编程方式为ios/android实现一个自绘日期选择控件(一)

本文为原创,如有转载,请注明出处:http://www.cnblogs.com/jackybu 前言 章节: 1.需求描述以及c/c++实现日期和月历的基本操作 2.ios实现自绘日期选择控件 3.android实现自绘日期选择控件 目的: 通过一个相对复杂的自定义自绘控件来分享: 1.ios以及android自定义自绘控件的开发流程 2.objc与c/c++混合编程 3.android ndk的环境配置,android studio ndk的编译模式,swig在android ndk开发中的作

一个Android上的弹幕控件Open Danmaku

弹幕现在很流行,特别是在视频播放时,安卓上没有简单好用的弹幕控件,于是自己写了一个. 项目地址: https://github.com/linsea/OpenDanmaku 或者国内开源中国上的镜像: http://git.oschina.net/dictfb/OpenDanmaku 使用方法 完整的使用方法请查看项目的Demo. 1. 在build.gradle文件中加入依赖. dependencies { compile 'com.linsea:opendanmaku:[email prot