c#基于Win32Api實現(xiàn)返回Windows桌面功能
實現(xiàn)方法
Windows回到桌面功能的實現(xiàn)方式有多種,可以模擬快捷鍵,也可以執(zhí)行如下方法。其中方法一需要引用Shell32.dll,方法為添加引用,選擇COM找到"Microsoft Shell Controls and Automation",選中并確認,還需要將其嵌入互操作類型置為false。
// 方法一,[參考鏈接](https://stackoverflow.com/questions/41598951/programmatically-show-the-desktop) Shell32.ShellClass objShel = new Shell32.ShellClass(); objShel.ToggleDesktop(); // 方法二,[參考鏈接](https://social.msdn.microsoft.com/Forums/vstudio/en-US/a27ca1e4-bd02-434b-8d02-06553c35f3d5/show-desktop-program-no-working) Type shellType = Type.GetTypeFromProgID("shell.application"); object shell = Activator.CreateInstance(shellType); shellType.InvokeMember("ToggleDesktop", BindingFlags.InvokeMethod, null, shell, new object[] { });
問題
正常情況下,這兩個方法都可以成功執(zhí)行。
但是,今天碰到一臺設備操作未成功。場景是WPF應用收到udp消息時,執(zhí)行回到桌面操作失敗。
看到有網(wǎng)友說執(zhí)行上述代碼時,需在STA thread中執(zhí)行,否則會報錯。方法一是需要在STA thread中執(zhí)行的,但是并不能解決該問題。
再次分析問題時發(fā)現(xiàn),當WPF應用為當前活動窗口時,操作執(zhí)行成功,否則執(zhí)行失敗。因此,先激活窗口,再執(zhí)行上述代碼就可以成功解決該問題了。
在出問題的設備上,使用簡單的Show()、Active()方法激活窗口是不行的,只會在任務欄閃爍圖標,使用如下方法可以激活
window.Show(); window.Activate();
在大部分設備上,通過 Show 和 Activate 組合可以讓窗口作為當前用戶活動的,即使窗口之前是最小化或隱藏,都可以通過 Show 的方法顯示
但是某些設備窗口被蓋在其他的窗口的下面,此時的窗口的 window.IsActive 還是 true 但是調(diào)用 Activate 不會讓窗口放在上層
我在網(wǎng)上看到好多小伙伴調(diào)用了 SetForegroundWindow 方法,其實現(xiàn)在 WPF 是開源的,可以看到 Window 的 Activate 方法是這樣寫
public bool Activate() { // this call ends up throwing an exception if Activate // is not allowed VerifyApiSupported(); VerifyContextAndObjectState(); VerifyHwndCreateShowState(); // Adding check for IsCompositionTargetInvalid if (IsSourceWindowNull || IsCompositionTargetInvalid) { return false; } return UnsafeNativeMethods.SetForegroundWindow(new HandleRef(null, CriticalHandle)); }
源代碼請看 github
也就是調(diào)用 SetForegroundWindow 和調(diào)用 Activate 方法是差不多的,如果調(diào)用 Activate 沒有用那么應該調(diào)用 SetForegroundWindow 也差不多
需要按照以下步驟
1.得到窗口句柄FindWindow
2.切換鍵盤輸入焦點AttachThreadInput
3.顯示窗口ShowWindow(有些窗口被最小化/隱藏了)
4.更改窗口的Zorder,SetWindowPos使之最上,為了不影響后續(xù)窗口的Zorder,改完之后,再還原
5.最后SetForegroundWindow
在 WPF 中對應的更改窗口的順序使用的是 Topmost 屬性,同時設置順序需要做一點小的更改
在 WPF 中通過 c# - Bring a window to the front in WPF - Stack Overflow 可以了解到如何用 AttachThreadInput 方法
整個代碼請看下面,具體的 win32 方法我就沒有寫出來了,請小伙伴自己添加
private static void SetWindowToForegroundWithAttachThreadInput(Window window) { var interopHelper = new WindowInteropHelper(window); // 以下 Win32 方法可以在 https://github.com/kkwpsv/lsjutil/tree/master/Src/Lsj.Util.Win32 找到 var thisWindowThreadId = Win32.User32.GetWindowThreadProcessId(interopHelper.Handle, IntPtr.Zero); var currentForegroundWindow = Win32.User32.GetForegroundWindow(); var currentForegroundWindowThreadId = Win32.User32.GetWindowThreadProcessId(currentForegroundWindow, IntPtr.Zero); // [c# - Bring a window to the front in WPF - Stack Overflow](https://stackoverflow.com/questions/257587/bring-a-window-to-the-front-in-wpf ) // [SetForegroundWindow的正確用法 - 子塢 - 博客園](https://www.cnblogs.com/ziwuge/archive/2012/01/06/2315342.html ) /* 1.得到窗口句柄FindWindow 2.切換鍵盤輸入焦點AttachThreadInput 3.顯示窗口ShowWindow(有些窗口被最小化/隱藏了) 4.更改窗口的Zorder,SetWindowPos使之最上,為了不影響后續(xù)窗口的Zorder,改完之后,再還原 5.最后SetForegroundWindow */ Win32.User32.AttachThreadInput(currentForegroundWindowThreadId, thisWindowThreadId, true); window.Show(); window.Activate(); // 去掉和其他線程的輸入鏈接 Win32.User32.AttachThreadInput(currentForegroundWindowThreadId, thisWindowThreadId, false); // 用于踢掉其他的在上層的窗口 window.Topmost = true; window.Topmost = false;
到此問題解決完畢。
在 WPF 中,如果想要使用代碼控制,讓某個窗口作為當前用戶的輸入的邏輯焦點的窗口,也就是在當前用戶活動的窗口的最上層窗口,默認使用 Activate 方法,通過這個方法在大部分設備都可以做到激活窗口
但是在一些特殊的設備上,使用下面代碼調(diào)起窗口只是在任務欄閃爍圖標,而沒有讓窗口放在最上層
該問題的難點在于并不是所有設備都存在該問題,我手中有兩臺設備,操作系統(tǒng)是一樣的,但一臺是好的,一臺是不行的。出問題的設備代碼是執(zhí)行了的,不知道為什么沒有效果,必須將應用置為活動窗口才行,有了解該問題的小伙伴歡迎討論。
本文測試demo的部分代碼如下,詳細可見Github。
// Wpf主窗口 public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); InitLogger(); InitUdpThread(); showDesktop = Method1; Logger.LogMessage(Severity.Info, $"start process, Main Thread id: {Thread.CurrentThread.ManagedThreadId}"); } private void InitLogger() { var file = new FileLogger("log.txt"); Logger.LogMessage(Severity.Info, "Init logger success"); } private void InitUdpThread() { Thread udpThread = new Thread(new ThreadStart(GetUdpMessage)); udpThread.IsBackground = true; udpThread.Start(); } private void GetUdpMessage() { UdpClient udpClient = null; try { udpClient = new UdpClient(10001); } catch (Exception) { Logger.LogMessage(Severity.Error, "create udp client failed"); return; } Logger.LogMessage(Severity.Info, "create udp client success"); IPEndPoint remotePoint = null; while (true) { try { byte[] receiveData = udpClient.Receive(ref remotePoint); string receiveString = Encoding.Default.GetString(receiveData); Logger.LogMessage(Severity.Info, $"receive udp message: {receiveString}"); if (receiveString.ToLower().Contains("showdesktop")) showDesktop?.Invoke(); } catch (Exception e) { Logger.LogMessage(Severity.Error, e.Message); } } } private void Button_Click(object sender, RoutedEventArgs e) { if (sender is Button btn) { switch (btn.Name) { case "method1": showDesktop = Method1; Logger.LogMessage(Severity.Info, "turn to method1"); break; case "method2": showDesktop = Method2; Logger.LogMessage(Severity.Info, "turn to method2"); break; case "activeFirst": showDesktop = ActiveFirst; Logger.LogMessage(Severity.Info, "turn to activeFirst method"); break; default: break; } } } private void Method1() { Thread newSta = new Thread(()=> { Shell32.ShellClass objShel = new Shell32.ShellClass(); objShel.ToggleDesktop(); Logger.LogMessage(Severity.Info, $"Current Thread id: {Thread.CurrentThread.ManagedThreadId}"); }); newSta.TrySetApartmentState(ApartmentState.STA); newSta.Start(); } private void Method2() { Type shellType = Type.GetTypeFromProgID("Shell.Application"); object shellObject = System.Activator.CreateInstance(shellType); shellType.InvokeMember("ToggleDesktop", System.Reflection.BindingFlags.InvokeMethod, null, shellObject, null); Logger.LogMessage(Severity.Info, $"Current Thread id: {Thread.CurrentThread.ManagedThreadId}"); } private void ActiveFirst() { App.Current.Dispatcher.Invoke(new Action(() => { Win32Api.SetWindowToForegroundWithAttachThreadInput(this); Method2(); })); } private Action showDesktop; }
以上就是c#基于Win32Api實現(xiàn)返回Windows桌面功能的詳細內(nèi)容,更多關于c# 返回Windows桌面的資料請關注腳本之家其它相關文章!
相關文章
C#中的不可變數(shù)據(jù)類型介紹(不可變對象、不可變集合)
這篇文章主要介紹了C#中的不可變數(shù)據(jù)類型介紹(不可變對象、不可變集合),本文講解了不可變對象、自定義不可變集合、Net提供的不可變集合、不可變優(yōu)點、不可變對象缺點等內(nèi)容,需要的朋友可以參考下2015-04-04詳解Unity中Mask和RectMask2D組件的對比與測試
本篇文章給大家介紹Unity中Mask和RectMask2D組件的對比與測試,包括組件用法及RectMask2D的基本用法,通過Mask的原理分析實例代碼相結(jié)合給大家講解的非常詳細,需要的朋友參考下吧2021-06-06C#中Convert.ToInt32()和int.Parse()的區(qū)別介紹
Convert是一個類,繼承自system.Object;int是值類型,在本文為大家詳細介紹下它與int.Parse()的區(qū)別,感興趣的朋友可以參考下2013-10-10c#實現(xiàn)一個超實用的證件照換底色小工具(附源碼)
這篇文章主要給大家介紹了關于利用c#實現(xiàn)一個超實用的證件照換底色小工具的相關資料,通過這個小工具大家可以很方便的進行底色的切換,不用再因為底色的原因頭疼了,需要的朋友可以參考借鑒,下面來一起學習學習吧。2018-01-01C#實現(xiàn)從多列的DataTable里取需要的幾列
這篇文章主要介紹了C#實現(xiàn)從多列的DataTable里取需要的幾列,涉及C#針對DataTable操作的相關技巧,需要的朋友可以參考下2016-03-03C#實現(xiàn)String字符串轉(zhuǎn)化為SQL語句中的In后接的參數(shù)詳解
在本篇文章中小編給大家分享的是一篇關于C#實現(xiàn)String字符串轉(zhuǎn)化為SQL語句中的In后接的實例內(nèi)容和代碼,需要的朋友們參考下。2020-01-01