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

推荐订阅源

T
The Exploit Database - CXSecurity.com
V
Vulnerabilities – Threatpost
Google DeepMind News
Google DeepMind News
Attack and Defense Labs
Attack and Defense Labs
Webroot Blog
Webroot Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
TaoSecurity Blog
TaoSecurity Blog
I
Intezer
Application and Cybersecurity Blog
Application and Cybersecurity Blog
N
News | PayPal Newsroom
S
Security Affairs
T
Tor Project blog
P
Proofpoint News Feed
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Security @ Cisco Blogs
H
Heimdal Security Blog
Hacker News: Ask HN
Hacker News: Ask HN
Help Net Security
Help Net Security
U
Unit 42
云风的 BLOG
云风的 BLOG
The Hacker News
The Hacker News
Cisco Talos Blog
Cisco Talos Blog
量子位
F
Full Disclosure
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 叶小钗
有赞技术团队
有赞技术团队
T
Troy Hunt's Blog
P
Privacy & Cybersecurity Law Blog
Forbes - Security
Forbes - Security
人人都是产品经理
人人都是产品经理
L
Lohrmann on Cybersecurity
Apple Machine Learning Research
Apple Machine Learning Research
Microsoft Security Blog
Microsoft Security Blog
博客园 - Franky
腾讯CDC
AI
AI
Last Week in AI
Last Week in AI
Latest news
Latest news
Google Online Security Blog
Google Online Security Blog
N
Netflix TechBlog - Medium
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
IT之家
IT之家
Martin Fowler
Martin Fowler
Blog — PlanetScale
Blog — PlanetScale
V2EX - 技术
V2EX - 技术
酷 壳 – CoolShell
酷 壳 – CoolShell

博客园 - 唐宋元明清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异常需要根据业务继续分析,不过保底可以通过创建子进程、以进程通信去启动相关应用,来规避。