本文介紹幾種使應用一直置於頂層的方法。
問題描述
一般情況下,想要將應用置於頂層,設定其TopMost屬性為true即可。對於多個設定了TopMost屬性的應用,後啟用的在上面。
但有的應用,比如全域性的快捷操作工具條,它需要在所有應用之上,即使是設定了TopMost的應用。
解決思路
注意:使某個應用永遠不會被其它應用覆蓋,這本身是個偽命題。因為假如有兩個程式(A和B)這樣做,拖動兩個視窗使它們重疊,這兩個視窗中的一個必須在另一個之上,這在邏輯上是互相矛盾的。
所以應該儘量避免這種情況,如果非要這樣做,本文提供如下幾種辦法實現(不要將兩個這樣的應用重疊,否則會不停將置頂)。
首先,該應用程式需要設定其TopMost屬性為true,這樣普通視窗本身就會在它下面。本文主要討論該視窗如何置於設定了TopMost屬性的視窗之上。
方案一:捕獲WM_WINDOWPOSCHANGING訊息
我們知道,使用Win32的SetWindowPos介面可以改變視窗的Z Order,可以猜測,當另外一個應用置頂時,我們的應用會改變其Z Order,因此,我們可以嘗試捕獲WM_WINDOWPOSCHANGING訊息。
當視窗的大小、位置、Z序改變時,視窗會接收到WM_WINDOWPOSCHANGING訊息,我們可以使用WndProc處理視窗訊息。當捕獲到該訊息時,我們可以嘗試將應用再次置頂。關鍵程式碼如下,測試可行,但不確定是否有副作用:
/// <summary>
/// 方案一:捕獲WM_WINDOWPOSCHANGING訊息,若無SWP_NOZORDER標誌,則置頂
/// </summary>
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case Win32Api.WM_WINDOWPOSCHANGING:
Win32Api.WINDOWPOS wp = (Win32Api.WINDOWPOS)Marshal.PtrToStructure(
lParam, typeof(Win32Api.WINDOWPOS));
if ((wp.flags & Win32Api.SWP_NOZORDER) == 0)
_ = SetTopMostLater(); // 不使用棄元編譯器會發出警告
break;
}
return IntPtr.Zero;
}
private async Task SetTopMostLater()
{
await Task.Delay(300);
var interopHelper = new WindowInteropHelper(this);
Win32Api.SetWindowPos(interopHelper.Handle, Win32Api.HWND_TOPMOST, 0, 0, 0, 0, Win32Api.TOPMOST_FLAGS);
}
方案二:迴圈置頂
這個是比較容易想到的一個方案,每隔一定的時間給應用設定下TopMost,該方案也是可行的:
/// <summary>
/// 方案二:迴圈置頂
/// </summary>
/// <returns></returns>
private async Task SetTopMostLoop()
{
while (true)
{
await Task.Delay(2000);
var interopHelper = new WindowInteropHelper(this);
Win32Api.SetWindowPos(interopHelper.Handle, Win32Api.HWND_TOPMOST, 0, 0, 0, 0, Win32Api.TOPMOST_FLAGS);
}
}
方案三:使用鉤子
思考一下,其實大部分情況下,使用滑鼠或鍵盤等其它輸入裝置才會導致視窗的置頂被搶,因此可以使用全域性鉤子捕獲輸入事件,然後進行處理。
該方案是存在瑕疵的,因為存在不使用輸入裝置開啟某個應用的情況,這種情況下置頂效果就會被新開啟的置頂應用搶佔。
// 方案三:當滑鼠按下時置頂(僅考慮了滑鼠)
private void MouseHook_OnMouseActivity(object sender, System.Windows.Forms.MouseEventArgs e)
{
Console.WriteLine("mouse down......");
_ = SetTopMostLater();
}
private MouseHook _mouseHook;
最後,本文是我對該問題想到的一些解決方案,Windows系統的工作管理員可以執行在所有應用的最上層,也許微軟正是考慮到上文提到的偽命題,因此沒有開放該介面吧,瞭解原理的小夥伴歡迎討論。