Winform ShowDialog如何让先前Show的窗体可以交互
背景描述
最近项目中有一个需求,全局有一个共用的窗体,能够打开不同模块的报告,由于需要兼容不同模块,代码复杂,启动速度慢。优化方案为将窗体启动时就创建好,需要查看报告时,使用此单例弹窗加载不同模块下的报告。
原项目模块是通过在主框架(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 }
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 }
关键代码为:
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 }
关键代码:
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 }
执行效果: