需求:
在实际场景中会有自身程序在调用第三方的动态库过程中,因为第三方的动态库弹框导致线程阻塞,必须手动将弹窗关闭后才能回到自身程序的主线程中。
最简单的场景就是很多自助设备,本身是没有固定操作员的,如果用户在看到弹框后没有手动点击关闭则弹框会一直会存在。
解决方案:
1、通过Windows提供的API,FindWindow(通过Window的Title)获取到第三方弹框句柄,通过SendMessage向Winwos发起关闭该句柄的命令;
2、如果该弹框有“关闭“按钮,则通过FindWindow获取到句柄后,再通过FindwWindowEx找到“关闭”按钮子句柄,然后通过SendMessage发起鼠标左击事件;
Code:
方案1:
/// <summary> /// 寻找系统的全部窗口 /// </summary> /// <returns></returns> public WindowInfo[] GetAllDesktopWindows() { List<WindowInfo> wndList = new List<WindowInfo>(); EnumWindows(delegate (IntPtr hWnd, int lParam) { WindowInfo wnd = new WindowInfo(); StringBuilder sb = new StringBuilder(256); //get hwnd wnd.hWnd = hWnd; //get window name GetWindowTextW(hWnd, sb, sb.Capacity); wnd.szWindowName = sb.ToString(); //get window class GetClassNameW(hWnd, sb, sb.Capacity); wnd.szClassName = sb.ToString(); //add it into list wndList.Add(wnd); return true; }, 0); return wndList.ToArray(); } /// <summary> /// 根据指定窗口标题暴力干掉这个窗口 /// </summary> /// <param name="windowTitle">window的标题</param> /// <param name="second">指定多少秒后关闭window</param> /// <returns></returns> public async Task KillWindow(string windowTitle, int second = 3) { try { await Task.Delay(TimeSpan.FromSeconds(second)); #region 通过获取所有窗口方式关闭 WindowInfo[] a = GetAllDesktopWindows(); int i = 0; int index = 0; for (i = 0; i < a.Length; i++) { if (a[i].szWindowName.ToString().Contains(windowTitle)) { index = i; IntPtr myIntPtr = a[index].hWnd; //先置顶!!!不置顶发送windows消息没卵用 SetWindowPos(myIntPtr, -1, 0, 0, 0, 0, 1 | 2); if (myIntPtr != null) { IntPtr childHwnd = FindWindowEx(myIntPtr, IntPtr.Zero, null, "否(&N)"); SendMessage(childHwnd, BM_CLICK, 0, 0); //发送点击按钮的消息 } break; } } #endregion } catch (Exception) { } }
方案二:
/// <summary> /// 根据指定窗口标题暴力干掉这个窗口 /// </summary> /// <param name="windowTitle">window的标题</param> /// <param name="second">指定多少秒后关闭window</param> /// <returns></returns> public async Task KillWindowAndBtn(string windowTitle, int second = 3, string btnTxt = "否(&N)") { try { await Task.Delay(TimeSpan.FromSeconds(second)); #region 通过获取所有窗口方式关闭 WindowInfo[] a = GetAllDesktopWindows(); int i = 0; int index = 0; for (i = 0; i < a.Length; i++) { if (a[i].szWindowName.ToString().Contains(windowTitle)) { index = i; IntPtr myIntPtr = a[index].hWnd; //先置顶!!!不置顶发送windows消息没卵用 SetWindowPos(myIntPtr, -1, 0, 0, 0, 0, 1 | 2); if (myIntPtr != null) { IntPtr childHwnd = FindWindowEx(myIntPtr, IntPtr.Zero, null, btnTxt); SendMessage(childHwnd, BM_CLICK, 0, 0); //发送点击按钮的消息 } break; } } #endregion } catch (Exception) { } }
调用方式(第一种方案):
private async void KillCardErrorWindow() { WinUtils utils = new WinUtils(); //通过获取全部窗口方式进行模糊查询 await utils.KillWindow("卡"); }
调用第二种方案:
/// <summary> /// 杀掉医保读卡dll生成的window /// </summary> private async void KillPassWordWindow() { WinUtils utils = new WinUtils(); //通过获取全部窗口方式进行模糊查询 await utils.KillWindow("输入密码"); //"读市民卡错误提示:" }
PS: 无论使用哪一种方案,都必须在调用第三方API之前先发起杀掉window的方法,留意代码即可知道,都是通过异步执行的。
所以结合实际场景需要,请先设定Task.Delay的时间值。