随笔-81  评论-709  文章-1 

『片段』Win32 模式窗体 消息路由

需求背景

近来,有个需求: 和一个外部程序对接。

具体是,我这边 主程序用 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?

答:

  1. 闪动5次,Spy++ 拦截到的消息有 60多个,用代码模拟消息 —— 至少就是 100多行代码,还不一定正确。

  2. Form2 确实可以删除,也确实可以用 代码来模拟消息 —— 这个设想是可行的,就是代码量大,麻烦而已。

问题3:

问: 多出来的 Form2 影响用户体验

答:

  1. 你可以把 Form2 调整为 1x1 像素 —— 然后把这个 Form2 藏起来。

  2. 或者,你可以把 Form2 做成一个 半透明的 提示窗体,其实也挺美观的。

posted on 2018-08-02 16:27 InkFx 阅读(...) 评论(...) 编辑 收藏