c#基于Win32Api实现返回Windows桌面功能

作者:louzi 时间:2022-11-21 15:29:51 

目录
  • 实现方法

  • 问题

    • 在出问题的设备上,使用简单的Show()、Active()方法激活窗口是不行的,只会在任务栏闪烁图标,使用如下方法可以激活

实现方法

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应用为当前活动窗口时,操作执行成功,否则执行失败。因此,先激活窗口,再执行上述代码就可以成功解决该问题了。

在出问题的设备上,使用简单的Show()、Active()方法激活窗口是不行的,只会在任务栏闪烁图标,使用如下方法可以激活


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;
}

来源:https://www.cnblogs.com/louzixl/p/14810338.html

标签:c#,Win32Api,返回桌面,windows
0
投稿

猜你喜欢

  • WPF自定义路由事件的实例教程

    2022-04-17 13:33:07
  • SpringBoot集成gRPC微服务工程搭建实践的方法

    2022-03-11 22:10:39
  • 详解Android的两种事件处理机制

    2023-10-05 23:37:20
  • Android通过自定义ImageView控件实现图片的缩放和拖动的实现代码

    2022-03-02 22:52:08
  • java实现双色球抽奖算法

    2023-11-28 23:51:51
  • java实现ip地址与十进制数相互转换

    2022-08-10 17:39:31
  • Java正则验证电话,手机,邮箱,日期,金额的方法示例

    2021-06-25 22:37:46
  • android通过自定义toast实现悬浮通知效果的示例代码

    2022-08-11 03:23:54
  • Java单例模式分析

    2023-11-16 03:17:06
  • Java class文件格式之访问标志信息_动力节点Java学院整理

    2022-10-31 18:57:29
  • SpringBoot中的Condition包下常用条件依赖注解案例介绍

    2023-05-29 11:42:17
  • Java中instanceof关键字的用法总结

    2022-12-11 15:52:27
  • c#动态调用Webservice的两种方法实例

    2021-09-19 20:12:05
  • IDEA与模拟器安装调试失败的处理方法:INSTALL_PARSE_FAILED_NO_CERTIFICATES

    2022-08-25 13:57:53
  • 解决运行jar包出错:ClassNotFoundException问题

    2021-09-09 04:58:41
  • 通过Java修改游戏存档的实现思路

    2023-07-30 20:10:55
  • Android车载多媒体开发MediaSession框架示例详解

    2022-08-10 19:49:20
  • java String的深入理解

    2022-10-30 23:50:37
  • Android O添加桌面快捷方式的示例

    2022-12-27 07:53:19
  • android shape实现阴影或模糊边效果

    2022-10-14 02:01:09
  • asp之家 软件编程 m.aspxhome.com