c#基于Win32Api实现返回Windows桌面功能
- 作者: 白夜行的嘿嘿哥
- 来源: 51数据库
- 2021-07-08
实现方法
windows回到桌面功能的实现方式有多种,可以模拟快捷键,也可以执行如下方法。其中方法一需要引用shell32.dll,方法为添加引用,选择com找到"microsoft shell controls and automation",选中并确认,还需要将其嵌入互操作类型置为false。
// 方法一,[参考链接](http://www.51sjk.com/Upload/Articles/1/0/257/257751_20210630002114345.jpg
shell32.shellclass objshel = new shell32.shellclass();
objshel.toggledesktop();
// 方法二,[参考链接](http://www.51sjk.com/Upload/Articles/1/0/257/257751_20210630002114376.jpg
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](http://www.51sjk.com/Upload/Articles/1/0/257/257751_20210630002115409.jpg )
// [setforegroundwindow的正确用法 - 子坞 - 博客园](http://www.51sjk.com/Upload/Articles/1/0/257/257751_20210630002115440.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;
}
以上就是c#基于win32api实现返回windows桌面功能的详细内容,更多关于c# 返回windows桌面的资料请关注其它相关文章!
