需求背景
近来,有个需求: 和一个外部程序对接。
具体是,我这边 主程序用 Process 启动外部程序。外部程序启动后,我这边调用的窗体不允许再进行任何操作。
当外部程序关闭时,外部程序会向我这边的主程序 返回结果。
传统做法
1 Process process = Process.Start("外部程序.exe", "-外部程序的参数"); 2 process.Exited += (sender, e)=> { MessageBox.Show("外部程序关闭"); } 3 process.WaitForExit(); //主程序等待 外部程序执行结束
以往,三行代码 就能搞定。 但是有个问题: process.WaitForExit();
这段代码,将会导致 UI线程 假死。
这时,你在UI线程上,多点几次鼠标,系统就会弹窗提示: 有个程序未响应,是否将其关闭。 (优化过的Ghost系统 甚至会直接将你的 主程序关闭)
—— 这种用户体验 自然是 非常不好的。
问题来了
点击主窗体,如何让外部程序弹出模式窗体(非常难表达)
—— 就是说:启动外部程序后,如果我点击 主程序,这时候 外部程序 主动弹出,盖过 主程序,就像 模式窗体 一样。
传统的模式窗体
如下图,Form1 以 模式窗体 打开 Form2, 这时候再点击 Form1 —— Form2 会自动弹5次。
直接给出实现后的效果
我们假设,这个外部程序是 Visual Studio —— 我们点击 Form1,结果 Visual Studio 弹出来 闪了5下 (就像模式窗体一样)。
代码实现解释
效果图 已经在上面了 —— 一种很小众的需求。
实现原理:Form1 以模式窗体 打开 Form2
当 点击 Form1 时,理论上 Form2 应该 闪动5下。
但是,我们改写了 Form2 的消息机制,我们将 闪动5下 的 消息,路由给了 Visual Studio。
于是,最终的效果就是,我点击 Form1,结果 外部程序 Visual Studio 却很像模式窗体 一样的 闪了5下。
关键代码就在 Form2
1 private DateTime m_DialogTime = DateTime.Now; 2 protected override void WndProc(ref Message m) 3 { 4 //base.WndProc(ref m); 5 //return; 6 7 8 if (m.Msg == Win32Msg.WM_WINDOWPOSCHANGING) 9 { 10 //WM_WINDOWPOSCHANGING 消息之后, 1秒以内的 WM_NCACTIVATE 的消息, 才会进行消息路由 11 //为什么要控制在 1秒 内: 12 //你可以尝试一下, 去掉 m_DialogTime 相关代码, 然后将 消息路由的窗体 最小化 13 //—— 这时候, m_DialogTime 参数导致的区别就显现了 14 m_DialogTime = DateTime.Now; 15 base.WndProc(ref m); 16 return; 17 } 18 19 20 if (m.Msg == Win32Msg.WM_NCACTIVATE) 21 { 22 if ((DateTime.Now - m_DialogTime).TotalMilliseconds > 1000) return; 23 //IntPtr intPtr = FindForm3Handle(); 24 IntPtr intPtr = FindOuterFormHandle(); //查找外部程序的 句柄 25 if (intPtr != IntPtr.Zero) 26 { 27 if (!Win32API.IsZoomed(intPtr)) Win32API.ShowWindow(intPtr, 1); 28 Win32API.SendMessage(intPtr, (uint)m.Msg, (int)m.WParam, (int)m.LParam); 29 Win32API.SetWindowPos(intPtr, IntPtr.Zero, 0, 0, 0, 0, (uint)(SWPFlags.SWP_NOMOVE | SWPFlags.SWP_NOSIZE)); 30 return; 31 } 32 } 33 base.WndProc(ref m); 34 }
有人质疑了
以上效果,想必有人质疑了:
问题1:
问: 主程序是 Form1, 用户点击的也是 Form1 —— 请问:Form2 有什么用?
答: Form2 就是 用来路由消息的,把 原本 Form2 的消息 路由给 外部程序。
问题2:
问: Form2 只是提供消息的?那为什么不直接模拟 闪动5次的 消息? 为什么不删掉 Form2?
答:
闪动5次,Spy++ 拦截到的消息有 60多个,用代码模拟消息 —— 至少就是 100多行代码,还不一定正确。
Form2 确实可以删除,也确实可以用 代码来模拟消息 —— 这个设想是可行的,就是代码量大,麻烦而已。
问题3:
问: 多出来的 Form2 影响用户体验
答:
你可以把 Form2 调整为 1x1 像素 —— 然后把这个 Form2 藏起来。
或者,你可以把 Form2 做成一个 半透明的 提示窗体,其实也挺美观的。