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


猜你喜欢
- 0.前言HashMap简述:HashMap 基于哈希表的 Map 接口实现,是以 key-value 存储形式存在,即主要用来存放键值对。H
- 背景:今天新生成一个springboot项目,然而启动日志,还有mybatis的详细日志无法打印出来,自写程序中打印的日志可以输出;网上找了
- 绑定多个按钮到同一个事件1.添加代码private void clauseElementClicked(object sender, Eve
- 最近一段时间在研究OAuth2的使用,想整个单点登录,从网上找了很多demo都没有实施成功,也许是因为压根就不懂OAuth2的原理导致。有那
- 本文为大家解析了java原码补码反码的关系,供大家参考,具体内容如下原码:不管源数据是十进制还是十六进制,统统将数字转成二进制形式反码:把原
- springcloud集成nacos遇到的问题1.获取不到配置文件信息有时候新建了配置文件后浏览器访问发现获取不到里面的值,原来spring
- 1.基本介绍Java自带日期格式化工具DateFormat ,但是DateFormat 的所有实现,包括 SimpleDateFormat
- 一、导论java技术体系中所提到的内存自动化管理归根结底就是内存的分配与回收两个问题,之前已经和大家谈过java回收的相关知识,今天来和大家
- 前言: 由于前段时间,项目组长分配的任务是要完成一个在线编写SQL并要实现查询功能的需求,最终需要将查询到的数据以JSON格式显示到响应数据
- 1.前言在java当中,若是进行比较,大家可能第一时间想到,==或是!=,这种数学上的比较符>、接下来,我就分别介绍并演示
- 本文实例讲述了Android编程设计模式之Builder模式。分享给大家供大家参考,具体如下:一、介绍Builder模式是一步一步创建一个复
- foreach 循环 list(map)直接上代码:整体需求就是1.分页对象里面有map map里面又有数组对象2.分页对象里面有list
- 一、医院接口本文继续开发分布式医疗挂号系统,进入到医院信息、科室、排版接口的开发,内容比较枯燥。关于医院医院信息的上传接口实现,已经在上一篇
- 在阻塞队里中,除了对元素进行增加和删除外,我们可以把元素的删除做一个延迟的处理,即使用DelayQueue的方法。本文就来和大家聊聊Java
- 简单使用redis-zset实现排行榜此方法实现一个根据某字段的查询次数进行排行,查询的次数越多排行越前(从大到小排序),适用于初学者1.添
- 最近在研究JSON,Java中有很多处理JSON的类库,lib-json、sf-json、fastjson还有Jackson Json。第一
- 前言我们之前讲述的动画都需要主动触发或者是重复执行,那有没有自己触发动画的组件呢?这样我们就可以在 StatelessWidget 里直接使
- 本文实例讲述了Java删除二叉搜索树最大元素和最小元素的方法。分享给大家供大家参考,具体如下:在前面一篇《Java二叉搜索树遍历操作》中完成
- 知识点回顾封装封装(有时称为数据隐藏)是与对象有关的一个重要概念。从形式上来看,封装不过是将数据和行为组合在一个包中,并对对象的使用者隐藏了
- 面试题1:说一下你对ReentrantLock的理解?ReentrantLock是JDK1.5引入的,它拥有与synchronized相同的