时间:2023-02-05 09:40:04 | 栏目:.NET代码 | 点击:次
Windows回到桌面功能的实现方式有多种,可以模拟快捷键,也可以执行如下方法。其中方法一需要引用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[] { });
正常情况下,这两个方法都可以成功执行。
但是,今天碰到一台设备操作未成功。场景是WPF应用收到udp消息时,执行回到桌面操作失败。
看到有网友说执行上述代码时,需在STA thread中执行,否则会报错。方法一是需要在STA thread中执行的,但是并不能解决该问题。
再次分析问题时发现,当WPF应用为当前活动窗口时,操作执行成功,否则执行失败。因此,先激活窗口,再执行上述代码就可以成功解决该问题了。
window.Show(); window.Activate();
在大部分设备上,通过 Show 和 Activate 组合可以让窗口作为当前用户活动的,即使窗口之前是最小化或隐藏,都可以通过 Show 的方法显示
但是某些设备窗口被盖在其他的窗口的下面,此时的窗口的 window.IsActive 还是 true 但是调用 Activate 不会让窗口放在上层
我在网上看到好多小伙伴调用了 SetForegroundWindow 方法,其实现在 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
也就是调用 SetForegroundWindow 和调用 Activate 方法是差不多的,如果调用 Activate 没有用那么应该调用 SetForegroundWindow 也差不多
需要按照以下步骤
1.得到窗口句柄FindWindow
2.切换键盘输入焦点AttachThreadInput
3.显示窗口ShowWindow(有些窗口被最小化/隐藏了)
4.更改窗口的Zorder,SetWindowPos使之最上,为了不影响后续窗口的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使之最上,为了不影响后续窗口的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 方法,通过这个方法在大部分设备都可以做到激活窗口
但是在一些特殊的设备上,使用下面代码调起窗口只是在任务栏闪烁图标,而没有让窗口放在最上层
该问题的难点在于并不是所有设备都存在该问题,我手中有两台设备,操作系统是一样的,但一台是好的,一台是不行的。出问题的设备代码是执行了的,不知道为什么没有效果,必须将应用置为活动窗口才行,有了解该问题的小伙伴欢迎讨论。
本文测试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; }