惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MongoDB | Blog
MongoDB | Blog
B
Blog RSS Feed
MyScale Blog
MyScale Blog
M
MIT News - Artificial intelligence
H
Hackread – Cybersecurity News, Data Breaches, AI and More
J
Java Code Geeks
U
Unit 42
Blog — PlanetScale
Blog — PlanetScale
L
LangChain Blog
C
Check Point Blog
WordPress大学
WordPress大学
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
T
Tailwind CSS Blog
Vercel News
Vercel News
腾讯CDC
GbyAI
GbyAI
有赞技术团队
有赞技术团队
S
SegmentFault 最新的问题
H
Help Net Security
博客园 - 三生石上(FineUI控件)
D
DataBreaches.Net
Microsoft Security Blog
Microsoft Security Blog
小众软件
小众软件

博客园 - 唐宋元明清2188

Electron 桌面客户端 ASAR 热更新:替换一个文件完成版本切换 .NET Win32设置只读未对齐,导致NTFS文件系统识别异常 SDD-skills执行遗漏问题 SDD基于规范编程-OpenSpec及SuperPowers .NET 磁盘BitLocker加密-技术选型 .NET Win32磁盘动态卷触发“函数不正确”问题排查 .NET SqlSugar多线程下SqlSugarClient 的线程安全陷阱 .NET 本地Db数据库-技术方案选型 .NET 磁盘Bitlocker加密-Powershell操作 .NET 磁盘管理-技术方案选型 .NET 记录Amazon上传S3异常问题 .NET 记录多框架下的Json序列化属性标记问题 .NET 阻止Windows关机以及阻止失败的一些原因 网络虚拟存储 Iscsi实现方案 Windows 网络存储ISCSI介绍 Windows 本地虚拟磁盘Vhdx .NET 数据拷贝方案选择 如何做好软件架构师 .NET开发一些书箱推荐 Windows应用开发-常用工具 WPF 记录鼠标、触摸多设备混合输入场景问题 .NET Bios相关数据读写
.NET 窗口置于最顶层
唐宋元明清2188 · 2025-01-08 · via 博客园 - 唐宋元明清2188

本文介绍如何将窗口置于最顶层,以及解决在顶层显示时对锁屏登录界面的影响等问题。用于实现类似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)。设置后会在任务栏上方显示(注意:不是开始菜单显示时的任务栏,开始菜单显示后任务栏层级是超级高的,置顶层级需要再次提升,下面会讲到)

如果你软件的置顶需求是常驻,需要解决与其它置顶窗口的层级冲突、抢他们的层级,可以加个定时器:

 1     private nint _handle;
 2     private void MainWindow_Loaded(object sender, RoutedEventArgs e)
 3     {
 4         _handle = new WindowInteropHelper(this).Handle;
 5         SetWindowPos(_handle, -1, 0, 0, 0, 0, 1);
 6         //定时器置顶
 7         var timer = new Timer();
 8         timer.Interval = 100;
 9         timer.Elapsed += Timer_Elapsed;
10         timer.Start();
11     }
12     private void Timer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
13     {
14         SetWindowPos(_handle, -1, 0, 0, 0, 0, 1);
15     }

当然,这种窗口置顶方案,遇上比你更流氓的软件就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修改

 1     public MainWindow()
 2     {
 3         InitializeComponent();
 4         //当前登录的用户变化
 5         SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
 6     }
 7     private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
 8     {
 9         switch (e.Reason)
10         {
11             //解锁屏
12             case SessionSwitchReason.SessionUnlock:
13                 Topmost = true;
14                 break;
15             //锁屏
16             case SessionSwitchReason.SessionLock:
17                 Topmost = false;
18                 break;
19         }
20     }

锁屏后窗口层级隐藏效果:

2. 任务栏图标如果有需求需要隐藏的话,设置窗口ShowInTaskbar=false无法隐藏图标

这种情况下,我磨了下代码,可以这么操作:

1     int exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
2     // 设置窗口样式为工具窗口, 不在任务栏显示
3     exStyle |= WS_EX_TOOLWINDOW;
4     SetWindowLong(hWnd, GWL_EXSTYLE, exStyle);
5     //二次设置任务栏不显示
6     ShowInTaskbar = false;

使用SetWindowLong来设置窗口为工具窗口样式,然后更新窗口属性ShowInTaskbar=false:

WS_EX_TOOLWINDOW 样式 - 此扩展窗口样式用于创建工具窗口。Windows 不将此类工具窗口视为常规应用程序窗口,因此默认情况下不在任务栏中显示

ShowInTaskbar = false - WPF是通过设置ShowInTaskbar来实现不在任务栏中显示。

注意:需要俩个同时设置,有uiAccess的置顶应用才能隐藏任务栏

我猜测,是uiAccess会影响窗口样式的应用方式,而WS_EX_TOOLWINDOW结合ShowInTaskbar,明确告诉 WPF 不要在任务栏中显示此窗口,进一步确保了图标不显示出来。

此类场景置顶代码如下:

 1     public void SetTopmost()
 2     {
 3         IntPtr hWnd = _hWnd;
 4         // 将窗口设置为顶层窗口
 5         Topmost = true;
 6 
 7         int exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
 8         // 设置窗口样式为工具窗口, 不在任务栏显示
 9         exStyle |= WS_EX_TOOLWINDOW;
10         SetWindowLong(hWnd, GWL_EXSTYLE, exStyle);
11         //二次设置任务栏不显示
12         ShowInTaskbar = false;
13     }

也可以看github仓库完整代码  kybs00/WindowsShowTopDemo,需要快速验证置顶可以用这个WindowShowTopDemo.exe

3. 根据小伙伴反馈,应用设置了uiAccess后,在此进程打开其它软件,其它软件内部调用SetParent实现相关功能时会失败

这个我验证了下分屏软件SetParent确实有此问题,分屏内部使用了SetParent来实现多个屏幕区域的组合。

我单独创建Demo验证SetParent是正常的,详见.NET SetParent - 唐宋元明清2188 - 博客园

小伙伴遇到的SetParent异常需要根据业务继续分析,不过保底可以通过创建子进程、以进程通信去启动相关应用,来规避。