前言
本文介绍如何将窗口置于最顶层,以及解决在顶层显示时对锁屏登录界面的影响等问题。用于实现类似Launcher、系统工具等应用需要窗口层级比Windows开始菜单以及置顶任务栏还要高的场景。
一般情况下的窗口置顶,可以设置WPF窗口属性Topmost=true
也可以使用WIN32-SetWindowPos函数SetWindowPos 函数 (winuser.h) - Win32 apps | Microsoft Learn,设置窗口层级:
1 /// <summary>设置窗口位置</summary>
2 /// <param name="hwnd">窗口句柄</param>
3 /// <param name="hWndInsertAfter">跟随的窗口句柄</param>
4 /// <param name="x">X轴坐标</param>
5 /// <param name="y">Y轴坐标</param>
6 /// <param name="width">宽</param>
7 /// <param name="height">高</param>
8 /// <param name="uFlags">标志位</param>
9 /// <returns></returns>
10 [DllImport("user32.dll", SetLastError = true)]
11 public static extern bool SetWindowPos(IntPtr hwnd, IntPtr hWndInsertAfter, int x, int y, int width, int height, uint uFlags);
hWndInsertAfter,需要置顶可以传入参数HWND_TOPMOST(-1)。设置后会在任务栏上方显示(注意:不是开始菜单显示时的任务栏,开始菜单显示后任务栏层级是超级高的,置顶层级需要再次提升,下面会讲到)
如果你软件的置顶需求是常驻,需要解决与其它置顶窗口的层级冲突、抢他们的层级,可以加个定时器:
private nint _handle;
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
_handle = new WindowInteropHelper(this).Handle;
SetWindowPos(_handle, -1, 0, 0, 0, 0, 1);
//定时器置顶
var timer = new Timer();
timer.Interval = 100;
timer.Elapsed += Timer_Elapsed;
timer.Start();
}
private void Timer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
{
SetWindowPos(_handle, -1, 0, 0, 0, 0, 1);
}
当然,这种窗口置顶方案,遇上比你更流氓的软件就GG了,会抢来抢去。
最上层置顶(比Windows开始菜单以及置顶任务栏还要高),根据我们MVP毅仔提供的方案 让你的程序置顶到比系统界面都更上层,就像任务管理器/放大镜一样绝对置顶 - walterlv,我们简单补充整理:
1、 添加app.manifest,并修改requestedExecutionLevel为管理员启动权限、添加UI置顶权限,详细的可以了解 /MANIFESTUAC(将 UAC 信息嵌入到清单中) | Microsoft Learn
<requestedExecutionLevel level="requireAdministrator" uiAccess="true" />
这里的窗口置顶可以设置比系统界面更高的置顶,也就是说可以比一些系统级别的置顶还要高,效果同任务管理器的绝对置顶。
UiAccess可以帮应用程序绕过用户界面保护级别、并将输入引导到桌面上的更高权限窗口
2、给Windows设置属性ShowInTaskbar="True"、Topmost="True",
3、添加程序签名
4、将程序放在安装目录下C:\Program Files、C:\Program Files (x86)。确保应用程序是从受信任的位置启动的,因为 Windows 对 UIAccess 应用程序的启动位置有严格限制。
启动后,窗口层级就比Windows开始菜单以及设置置顶的任务管理器,都要高。窗口层级关系如下,桌面<一般应用窗口<Windows开始菜单<置顶的任务管理器<当前置顶应用Demo:
层级设置没问题。
我们来说下这个方案的几个问题
1、Windows锁屏/登录界面,置顶窗口也会显示,影响了用户操作
解决:监听锁屏/解锁屏的事件,添加窗口Topmost修改
public MainWindow()
{
InitializeComponent();
//当前登录的用户变化
SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
}
private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
switch (e.Reason)
{
//解锁屏
case SessionSwitchReason.SessionUnlock:
Topmost = true;
break;
//锁屏
case SessionSwitchReason.SessionLock:
Topmost = false;
break;
}
}
锁屏后窗口层级隐藏效果:
2、任务栏图标如果有需求需要隐藏的话,设置窗口ShowInTaskbar=false无法隐藏图标
这种情况下,我磨了下代码,可以这么操作:
int exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
// 设置窗口样式为工具窗口, 不在任务栏显示
exStyle |= WS_EX_TOOLWINDOW;
SetWindowLong(hWnd, GWL_EXSTYLE, exStyle);
//二次设置任务栏不显示
ShowInTaskbar = false;
使用SetWindowLong来设置窗口为工具窗口样式,然后更新窗口属性 ShowInTaskbar=false:
WS_EX_TOOLWINDOW 样式 - 此扩展窗口样式用于创建工具窗口。Windows 不将此类工具窗口视为常规应用程序窗口,因此默认情况下不在任务栏中显示
ShowInTaskbar = false - WPF是通过设置ShowInTaskbar来实现不在任务栏中显示。
注意:需要俩个同时设置,有uiAccess的置顶应用才能隐藏任务栏
我猜测,是uiAccess会影响窗口样式的应用方式,而WS_EX_TOOLWINDOW结合ShowInTaskbar,明确告诉 WPF 不要在任务栏中显示此窗口,进一步确保了图标不显示出来。
此类场景置顶代码如下:
public void SetTopmost()
{
IntPtr hWnd = _hWnd;
// 将窗口设置为顶层窗口
Topmost = true;
int exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
// 设置窗口样式为工具窗口, 不在任务栏显示
exStyle |= WS_EX_TOOLWINDOW;
SetWindowLong(hWnd, GWL_EXSTYLE, exStyle);
//二次设置任务栏不显示
ShowInTaskbar = false;
}
也可以看github仓库完整代码 kybs00/WindowsShowTopDemo,需要快速验证置顶可以用这个WindowShowTopDemo.exe:
3、根据小伙伴反馈,应用设置了uiAccess后,在此进程打开其它软件,其它软件内部调用SetParent实现相关功能时会失败。
这个我验证了下确实如此。
目前暂无解决方案、需要具体分析,不过保底可以通过创建子进程、以进程通信去启动相关应用,来规避。
最后
如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。
也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!
优秀是一种习惯,欢迎大家留言学习!
作者:唐宋元明清2188
出处:cnblogs.com/kybs0/p/18658281
声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!