Win32Api -- 使應用Always on top的幾種方法


本文介紹幾種使應用一直置於頂層的方法。

問題描述

一般情況下,想要將應用置於頂層,設置其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系統的任務管理器可以運行在所有應用的最上層,也許微軟正是考慮到上文提到的偽命題,因此沒有開放該接口吧,了解原理的小伙伴歡迎討論。

本文三種方案的完整demo見GitHub,可以參考的鏈接(關於該話題的討論較老了):鏈接一鏈接二


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM