Winform ShowDialog如何让先前Show的窗体可以交互

发布时间 2023-07-03 11:25:36作者: 天命小猪

背景描述

最近项目中有一个需求,全局有一个共用的窗体,能够打开不同模块的报告,由于需要兼容不同模块,代码复杂,启动速度慢。优化方案为将窗体启动时就创建好,需要查看报告时,使用此单例弹窗加载不同模块下的报告。

原项目模块是通过在主框架(Form1)下加载不同Tab页实现的,因此查看报告弹窗(Form2)是非模态Show出来。后来业务要求新加入一个模块,模块页面是通过模态方式呈现(Form3),即ShowDialog的方式。

这就有一个问题,如果用户在Form1下先打开Form2,然后又需要在Form3下打开Form2,此时Form2是无法操作的。如下:

解决思路

这个问题和winform设计有关,只能去看看winform源代码是如何区别处理Show和ShowDialog。

看看反编译后的代码:
Show:
 1 public void Show(IWin32Window owner)
 2 {
 3     if (owner == this)
 4     {
 5         throw new InvalidOperationException(SR.GetString("OwnsSelfOrOwner", "Show"));
 6     }
 7 
 8     if (base.Visible)
 9     {
10         throw new InvalidOperationException(SR.GetString("ShowDialogOnVisible", "Show"));
11     }
12 
13     if (!base.Enabled)
14     {
15         throw new InvalidOperationException(SR.GetString("ShowDialogOnDisabled", "Show"));
16     }
17 
18     if (!TopLevel)
19     {
20         throw new InvalidOperationException(SR.GetString("ShowDialogOnNonTopLevel", "Show"));
21     }
22 
23     if (!SystemInformation.UserInteractive)
24     {
25         throw new InvalidOperationException(SR.GetString("CantShowModalOnNonInteractive"));
26     }
27 
28     if (owner != null && ((int)UnsafeNativeMethods.GetWindowLong(new HandleRef(owner, Control.GetSafeHandle(owner)), -20) & 8) == 0 && owner is Control)
29     {
30         owner = ((Control)owner).TopLevelControlInternal;
31     }
32 
33     IntPtr activeWindow = UnsafeNativeMethods.GetActiveWindow();
34     IntPtr intPtr = (owner == null) ? activeWindow : Control.GetSafeHandle(owner);
35     IntPtr zero = IntPtr.Zero;
36     base.Properties.SetObject(PropDialogOwner, owner);
37     Form ownerInternal = OwnerInternal;
38     if (owner is Form && owner != ownerInternal)
39     {
40         Owner = (Form)owner;
41     }
42 
43     if (intPtr != IntPtr.Zero && intPtr != base.Handle)
44     {
45         if (UnsafeNativeMethods.GetWindowLong(new HandleRef(owner, intPtr), -8) == base.Handle)
46         {
47             throw new ArgumentException(SR.GetString("OwnsSelfOrOwner", "show"), "owner");
48         }
49 
50         zero = UnsafeNativeMethods.GetWindowLong(new HandleRef(this, base.Handle), -8);
51         UnsafeNativeMethods.SetWindowLong(new HandleRef(this, base.Handle), -8, new HandleRef(owner, intPtr));
52     }
53 
54     base.Visible = true;
55 }
View Code

  

ShowDialog:
  1 public DialogResult ShowDialog(IWin32Window owner)
  2 {
  3     if (owner == this)
  4     {
  5         throw new ArgumentException(SR.GetString("OwnsSelfOrOwner", "showDialog"), "owner");
  6     }
  7 
  8     if (base.Visible)
  9     {
 10         throw new InvalidOperationException(SR.GetString("ShowDialogOnVisible", "showDialog"));
 11     }
 12 
 13     if (!base.Enabled)
 14     {
 15         throw new InvalidOperationException(SR.GetString("ShowDialogOnDisabled", "showDialog"));
 16     }
 17 
 18     if (!TopLevel)
 19     {
 20         throw new InvalidOperationException(SR.GetString("ShowDialogOnNonTopLevel", "showDialog"));
 21     }
 22 
 23     if (Modal)
 24     {
 25         throw new InvalidOperationException(SR.GetString("ShowDialogOnModal", "showDialog"));
 26     }
 27 
 28     if (!SystemInformation.UserInteractive)
 29     {
 30         throw new InvalidOperationException(SR.GetString("CantShowModalOnNonInteractive"));
 31     }
 32 
 33     if (owner != null && ((int)UnsafeNativeMethods.GetWindowLong(new HandleRef(owner, Control.GetSafeHandle(owner)), -20) & 8) == 0 && owner is Control)
 34     {
 35         owner = ((Control)owner).TopLevelControlInternal;
 36     }
 37 
 38     CalledOnLoad = false;
 39     CalledMakeVisible = false;
 40     CloseReason = CloseReason.None;
 41     IntPtr capture = UnsafeNativeMethods.GetCapture();
 42     if (capture != IntPtr.Zero)
 43     {
 44         UnsafeNativeMethods.SendMessage(new HandleRef(null, capture), 31, IntPtr.Zero, IntPtr.Zero);
 45         SafeNativeMethods.ReleaseCapture();
 46     }
 47 
 48     IntPtr intPtr = UnsafeNativeMethods.GetActiveWindow();
 49     IntPtr intPtr2 = (owner == null) ? intPtr : Control.GetSafeHandle(owner);
 50     IntPtr zero = IntPtr.Zero;
 51     base.Properties.SetObject(PropDialogOwner, owner);
 52     Form ownerInternal = OwnerInternal;
 53     if (owner is Form && owner != ownerInternal)
 54     {
 55         Owner = (Form)owner;
 56     }
 57 
 58     try
 59     {
 60         SetState(32, value: true);
 61         dialogResult = DialogResult.None;
 62         CreateControl();
 63         if (intPtr2 != IntPtr.Zero && intPtr2 != base.Handle)
 64         {
 65             if (UnsafeNativeMethods.GetWindowLong(new HandleRef(owner, intPtr2), -8) == base.Handle)
 66             {
 67                 throw new ArgumentException(SR.GetString("OwnsSelfOrOwner", "showDialog"), "owner");
 68             }
 69 
 70             zero = UnsafeNativeMethods.GetWindowLong(new HandleRef(this, base.Handle), -8);
 71             UnsafeNativeMethods.SetWindowLong(new HandleRef(this, base.Handle), -8, new HandleRef(owner, intPtr2));
 72         }
 73 
 74         try
 75         {
 76             if (dialogResult == DialogResult.None)
 77             {
 78                 Application.RunDialog(this);
 79             }
 80         }
 81         finally
 82         {
 83             if (!UnsafeNativeMethods.IsWindow(new HandleRef(null, intPtr)))
 84             {
 85                 intPtr = intPtr2;
 86             }
 87 
 88             if (UnsafeNativeMethods.IsWindow(new HandleRef(null, intPtr)) && SafeNativeMethods.IsWindowVisible(new HandleRef(null, intPtr)))
 89             {
 90                 UnsafeNativeMethods.SetActiveWindow(new HandleRef(null, intPtr));
 91             }
 92             else if (UnsafeNativeMethods.IsWindow(new HandleRef(null, intPtr2)) && SafeNativeMethods.IsWindowVisible(new HandleRef(null, intPtr2)))
 93             {
 94                 UnsafeNativeMethods.SetActiveWindow(new HandleRef(null, intPtr2));
 95             }
 96 
 97             SetVisibleCore(value: false);
 98             if (base.IsHandleCreated)
 99             {
100                 if (OwnerInternal != null && OwnerInternal.IsMdiContainer)
101                 {
102                     OwnerInternal.Invalidate(invalidateChildren: true);
103                     OwnerInternal.Update();
104                 }
105 
106                 DestroyHandle();
107             }
108 
109             SetState(32, value: false);
110         }
111     }
112     finally
113     {
114         Owner = ownerInternal;
115         base.Properties.SetObject(PropDialogOwner, null);
116     }
117 
118     return DialogResult;
119 }
View Code

 

关键代码为:
1 if (dialogResult == DialogResult.None)
2 {
3     Application.RunDialog(this);
4 }

 

顺着找:
1 internal static void RunDialog(Form form)
2 {
3     Application.ThreadContext.FromCurrent().RunMessageLoop(4, new Application.ModalApplicationContext(form));
4 }

 

再看看RunMessageLoop:
 1 internal void RunMessageLoop(int reason, ApplicationContext context)
 2 {
 3     IntPtr userCookie = IntPtr.Zero;
 4     if (Application.useVisualStyles)
 5     {
 6         userCookie = UnsafeNativeMethods.ThemingScope.Activate();
 7     }
 8     try
 9     {
10         this.RunMessageLoopInner(reason, context);
11     }
12     finally
13     {
14         UnsafeNativeMethods.ThemingScope.Deactivate(userCookie);
15     }
16 }

 

 
 
找到Inner代码:
  1 private void RunMessageLoopInner(int reason, ApplicationContext context)
  2 {
  3     if (reason == 4 && !SystemInformation.UserInteractive)
  4     {
  5         throw new InvalidOperationException(SR.GetString("CantShowModalOnNonInteractive"));
  6     }
  7     if (reason == -1)
  8     {
  9         this.SetState(8, false);
 10     }
 11     if (Application.ThreadContext.totalMessageLoopCount++ == 0)
 12     {
 13         Application.ThreadContext.baseLoopReason = reason;
 14     }
 15     this.messageLoopCount++;
 16     if (reason == -1)
 17     {
 18         if (this.messageLoopCount != 1)
 19         {
 20             throw new InvalidOperationException(SR.GetString("CantNestMessageLoops"));
 21         }
 22         this.applicationContext = context;
 23         this.applicationContext.ThreadExit += this.OnAppThreadExit;
 24         if (this.applicationContext.MainForm != null)
 25         {
 26             this.applicationContext.MainForm.Visible = true;
 27         }
 28         DpiHelper.InitializeDpiHelperForWinforms();
 29         AccessibilityImprovements.ValidateLevels();
 30     }
 31     Form form = this.currentForm;
 32     if (context != null)
 33     {
 34         this.currentForm = context.MainForm;
 35     }
 36     bool flag = false;
 37     bool flag2 = false;
 38     HandleRef hWnd = new HandleRef(null, IntPtr.Zero);
 39     if (reason == -2)
 40     {
 41         flag2 = true;
 42     }
 43     if (reason == 4 || reason == 5)
 44     {
 45         flag = true;
 46         bool flag3 = this.currentForm != null && this.currentForm.Enabled;
 47         this.BeginModalMessageLoop(context);
 48         hWnd = new HandleRef(null, UnsafeNativeMethods.GetWindowLong(new HandleRef(this.currentForm, this.currentForm.Handle), -8));
 49         if (hWnd.Handle != IntPtr.Zero)
 50         {
 51             if (SafeNativeMethods.IsWindowEnabled(hWnd))
 52             {
 53                 SafeNativeMethods.EnableWindow(hWnd, false);
 54             }
 55             else
 56             {
 57                 hWnd = new HandleRef(null, IntPtr.Zero);
 58             }
 59         }
 60         if (this.currentForm != null && this.currentForm.IsHandleCreated && SafeNativeMethods.IsWindowEnabled(new HandleRef(this.currentForm, this.currentForm.Handle)) != flag3)
 61         {
 62             SafeNativeMethods.EnableWindow(new HandleRef(this.currentForm, this.currentForm.Handle), flag3);
 63         }
 64     }
 65     try
 66     {
 67         if (this.messageLoopCount == 1)
 68         {
 69             WindowsFormsSynchronizationContext.InstallIfNeeded();
 70         }
 71         if (flag && this.currentForm != null)
 72         {
 73             this.currentForm.Visible = true;
 74         }
 75         if ((!flag && !flag2) || this.ComponentManager is Application.ComponentManager)
 76         {
 77             bool flag4 = this.ComponentManager.FPushMessageLoop((IntPtr)this.componentID, reason, 0);
 78         }
 79         else if (reason == 2 || reason == -2)
 80         {
 81             bool flag4 = this.LocalModalMessageLoop(null);
 82         }
 83         else
 84         {
 85             bool flag4 = this.LocalModalMessageLoop(this.currentForm);
 86         }
 87     }
 88     finally
 89     {
 90         if (flag)
 91         {
 92             this.EndModalMessageLoop(context);
 93             if (hWnd.Handle != IntPtr.Zero)
 94             {
 95                 SafeNativeMethods.EnableWindow(hWnd, true);
 96             }
 97         }
 98         this.currentForm = form;
 99         Application.ThreadContext.totalMessageLoopCount--;
100         this.messageLoopCount--;
101         if (this.messageLoopCount == 0)
102         {
103             WindowsFormsSynchronizationContext.Uninstall(false);
104         }
105         if (reason == -1)
106         {
107             this.Dispose(true);
108         }
109         else if (this.messageLoopCount == 0 && this.componentManager != null)
110         {
111             this.RevokeComponent();
112         }
113     }
114 }
View Code

 

关键代码:
 1 if (reason == 4 || reason == 5)
 2 {
 3         flag = true;
 4         bool flag3 = this.currentForm != null && this.currentForm.Enabled;
 5         this.BeginModalMessageLoop(context);
 6         hWnd = new HandleRef(null, UnsafeNativeMethods.GetWindowLong(new HandleRef(this.currentForm, this.currentForm.Handle), -8));
 7         if (hWnd.Handle != IntPtr.Zero)
 8         {
 9             if (SafeNativeMethods.IsWindowEnabled(hWnd))
10             {
11                 SafeNativeMethods.EnableWindow(hWnd, false);
12             }
13             else
14             {
15                 hWnd = new HandleRef(null, IntPtr.Zero);
16             }
17         }
18         if (this.currentForm != null && this.currentForm.IsHandleCreated && SafeNativeMethods.IsWindowEnabled(new HandleRef(this.currentForm, this.currentForm.Handle)) != flag3)
19         {
20             SafeNativeMethods.EnableWindow(new HandleRef(this.currentForm, this.currentForm.Handle), flag3);
21         }
22 }

 

这边的逻辑其实就是:如果启用模态弹窗,则先把所有窗体禁用,然后找到当前需要展示的窗体,启用它。
知道了原理,我们依葫芦画瓢即可:调用WinApi中的EnableWindow。
部分示例代码:
1 private void btnOpenOldForm_Click(object sender, EventArgs e)
2 {
3     OldFrom.Hide();
4     OldFrom.Show(this);
5     OldFrom.BringToFront();
6     var formhandle = OldFrom.Handle;
7     NativeMethodHelper.EnableWindow(new HandleRef(null, formhandle), true);
8 }

 

附上winapi帮助类:
 1 public class NativeMethodHelper
 2 {
 3     [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
 4     public static extern IntPtr SetActiveWindow(HandleRef hWnd);
 5 
 6     [DllImport("user32.dll", CharSet = CharSet.Auto, EntryPoint = "GetWindowLongPtr")]
 7     public static extern IntPtr GetWindowLongPtr64(HandleRef hWnd, int nIndex);
 8 
 9     public static IntPtr SetWindowLong(HandleRef hWnd, int nIndex, HandleRef dwNewLong)
10     {
11         if (IntPtr.Size == 4)
12         {
13             return NativeMethodHelper.SetWindowLongPtr32(hWnd, nIndex, dwNewLong);
14         }
15         return NativeMethodHelper.SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
16     }
17     [DllImport("user32.dll", CharSet = CharSet.Auto, EntryPoint = "SetWindowLong")]
18     public static extern IntPtr SetWindowLongPtr32(HandleRef hWnd, int nIndex, HandleRef dwNewLong);
19 
20     [DllImport("user32.dll", CharSet = CharSet.Auto, EntryPoint = "SetWindowLongPtr")]
21     public static extern IntPtr SetWindowLongPtr64(HandleRef hWnd, int nIndex, HandleRef dwNewLong);
22 
23     /// <summary>
24     /// 启用窗体
25     /// </summary>
26     /// <param name="hWnd"></param>
27     /// <param name="enable"></param>
28     /// <returns></returns>
29     [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
30     public static extern bool EnableWindow(HandleRef hWnd, bool enable);
31 }

 

执行效果: