• 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,可以参考的链接(关于该话题的讨论较老了):链接一链接二

    转载请注明出处,欢迎交流。
  • 相关阅读:
    DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践
    UVA10071 Back to High School Physics
    UVA10071 Back to High School Physics
    UVA10055 Hashmat the Brave Warrior
    UVA10055 Hashmat the Brave Warrior
    UVA458 The Decoder
    UVA458 The Decoder
    HDU2054 A == B ?
    HDU2054 A == B ?
    POJ3414 Pots
  • 原文地址:https://www.cnblogs.com/louzixl/p/14930043.html
Copyright © 2020-2023  润新知