本文介紹如何將窗口置于最頂層,以及解決在頂層顯示時對鎖屏登錄界面的影響等問題。用于實(shí)現(xiàn)類似Launcher、系統(tǒng)工具等應(yīng)用需要窗口層級比Windows開始菜單以及置頂任務(wù)欄還要高的場景
一般情況下的窗口置頂,可以設(shè)置WPF窗口屬性Topmost=true
也可以使用WIN32-SetWindowPos函數(shù)SetWindowPos 函數(shù) (winuser.h) - Win32 apps | Microsoft Learn,設(shè)置窗口層級:
1 /// <summary>設(shè)置窗口位置</summary>
2 /// <param name="hwnd">窗口句柄</param>
3 /// <param name="hWndInsertAfter">跟隨的窗口句柄</param>
4 /// <param name="x">X軸坐標(biāo)</param>
5 /// <param name="y">Y軸坐標(biāo)</param>
6 /// <param name="width">寬</param>
7 /// <param name="height">高</param>
8 /// <param name="uFlags">標(biāo)志位</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,需要置頂可以傳入?yún)?shù)HWND_TOPMOST(-1)。設(shè)置后會在任務(wù)欄上方顯示(注意:不是開始菜單顯示時的任務(wù)欄,開始菜單顯示后任務(wù)欄層級是超級高的,置頂層級需要再次提升,下面會講到)
如果你軟件的置頂需求是常駐,需要解決與其它置頂窗口的層級沖突、搶他們的層級,可以加個定時器:
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 }
當(dāng)然,這種窗口置頂方案,遇上比你更流氓的軟件就GG了,會搶來搶去。
最上層置頂(比Windows開始菜單以及置頂任務(wù)欄還要高),根據(jù)我們MVP毅仔提供的方案 讓你的程序置頂?shù)奖认到y(tǒng)界面都更上層,就像任務(wù)管理器/放大鏡一樣絕對置頂 - walterlv,我們簡單補(bǔ)充整理:
1. 添加app.manifest,并修改requestedExecutionLevel為管理員啟動權(quán)限、添加UI置頂權(quán)限,詳細(xì)的可以了解 /MANIFESTUAC(將 UAC 信息嵌入到清單中) | Microsoft Learn
<requestedExecutionLevel level="requireAdministrator" uiAccess="true" />
這里的窗口置頂可以設(shè)置比系統(tǒng)界面更高的置頂,也就是說可以比一些系統(tǒng)級別的置頂還要高,效果同任務(wù)管理器的絕對置頂。UiAccess可以幫應(yīng)用程序繞過用戶界面保護(hù)級別、并將輸入引導(dǎo)到桌面上的更高權(quán)限窗口
2. 給Windows設(shè)置屬性ShowInTaskbar="True"、Topmost="True",
3. 添加程序簽名

4. 將程序放在安裝目錄下C:\Program Files、C:\Program Files (x86)。確保應(yīng)用程序是從受信任的位置啟動的,因?yàn)?Windows 對 UIAccess 應(yīng)用程序的啟動位置有嚴(yán)格限制。
啟動后,窗口層級就比Windows開始菜單以及設(shè)置置頂?shù)娜蝿?wù)管理器,都要高。窗口層級關(guān)系如下,桌面<一般應(yīng)用窗口<Windows開始菜單<置頂?shù)娜蝿?wù)管理器<當(dāng)前置頂應(yīng)用Demo:

層級設(shè)置沒問題。我們來說下這個方案的幾個問題
1. Windows鎖屏/登錄界面,置頂窗口也會顯示,影響了用戶操作
解決:監(jiān)聽鎖屏/解鎖屏的事件,添加窗口Topmost修改
1 public MainWindow()
2 {
3 InitializeComponent();
4 //當(dāng)前登錄的用戶變化
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. 任務(wù)欄圖標(biāo)如果有需求需要隱藏的話,設(shè)置窗口ShowInTaskbar=false無法隱藏圖標(biāo)
這種情況下,我磨了下代碼,可以這么操作:
1 int exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
2 // 設(shè)置窗口樣式為工具窗口, 不在任務(wù)欄顯示
3 exStyle |= WS_EX_TOOLWINDOW;
4 SetWindowLong(hWnd, GWL_EXSTYLE, exStyle);
5 //二次設(shè)置任務(wù)欄不顯示
6 ShowInTaskbar = false;
使用SetWindowLong來設(shè)置窗口為工具窗口樣式,然后更新窗口屬性ShowInTaskbar=false:
WS_EX_TOOLWINDOW 樣式 - 此擴(kuò)展窗口樣式用于創(chuàng)建工具窗口。Windows 不將此類工具窗口視為常規(guī)應(yīng)用程序窗口,因此默認(rèn)情況下不在任務(wù)欄中顯示
ShowInTaskbar = false - WPF是通過設(shè)置ShowInTaskbar來實(shí)現(xiàn)不在任務(wù)欄中顯示。
注意:需要倆個同時設(shè)置,有uiAccess的置頂應(yīng)用才能隱藏任務(wù)欄
我猜測,是uiAccess會影響窗口樣式的應(yīng)用方式,而WS_EX_TOOLWINDOW結(jié)合ShowInTaskbar,明確告訴 WPF 不要在任務(wù)欄中顯示此窗口,進(jìn)一步確保了圖標(biāo)不顯示出來。
此類場景置頂代碼如下:
1 public void SetTopmost()
2 {
3 IntPtr hWnd = _hWnd;
4 // 將窗口設(shè)置為頂層窗口
5 Topmost = true;
6
7 int exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
8 // 設(shè)置窗口樣式為工具窗口, 不在任務(wù)欄顯示
9 exStyle |= WS_EX_TOOLWINDOW;
10 SetWindowLong(hWnd, GWL_EXSTYLE, exStyle);
11 //二次設(shè)置任務(wù)欄不顯示
12 ShowInTaskbar = false;
13 }
也可以看github倉庫完整代碼 kybs00/WindowsShowTopDemo,需要快速驗(yàn)證置頂可以用這個WindowShowTopDemo.exe:
3. 根據(jù)小伙伴反饋,應(yīng)用設(shè)置了uiAccess后,在此進(jìn)程打開其它軟件,其它軟件內(nèi)部調(diào)用SetParent實(shí)現(xiàn)相關(guān)功能時會失敗
這個我驗(yàn)證了下確實(shí)如此。目前暫無解決方案、需要具體分析,不過保底可以通過創(chuàng)建子進(jìn)程、以進(jìn)程通信去啟動相關(guān)應(yīng)用,來規(guī)避。
?轉(zhuǎn)自https://www.cnblogs.com/kybs0/p/18658281
該文章在 2025/1/9 9:24:52 編輯過