检测另一个进程中的特定窗口何时打开或关闭

所以我做了一个win应用程序,每当我打开记事本中的“页面设置”并且每当我关闭记事本时关闭它,我都希望它在屏幕前弹出。

我试过这个:

[DllImport("user32.dll", SetLastError = true)] static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll")] public static extern IntPtr GetParent(IntPtr hWnd); [DllImport("User32", CharSet = CharSet.Auto)] public static extern int ShowWindow(IntPtr hWnd, int cmdShow); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool IsIconic(IntPtr hwnd); [DllImport("user32.dll")] public static extern int SetForegroundWindow(IntPtr hWnd); ManagementEventWatcher watcher; public Form1() { InitializeComponent(); var query = new WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace WHERE ProcessName = 'Notepad.exe'"); var mew = new ManagementEventWatcher(query) { Query = query }; mew.EventArrived += (sender, args) => { AppStarted(); }; mew.Start(); } protected override void OnLoad(EventArgs e) { base.OnLoad(e); watcher = new ManagementEventWatcher("Select * From Win32_ProcessStopTrace"); watcher.EventArrived += new EventArrivedEventHandler(watcher_EventArrived); watcher.Start(); } protected override void OnFormClosed(FormClosedEventArgs e) { watcher.Stop(); watcher.Dispose(); base.OnFormClosed(e); } void watcher_EventArrived(object sender, EventArrivedEventArgs e) { // var _windowHandle = FindWindow(null, "Page Setup"); if ((string)e.NewEvent["ProcessName"] == "notepad.exe") { Invoke((MethodInvoker)delegate { TopMost = false; Location = new System.Drawing.Point(1000, 1); }); } } async void AppStarted() { await Task.Delay(300); BeginInvoke(new System.Action(PoPInFront)); } void PoPInFront() { var _notepadProcess = Process.GetProcesses().Where(x => x.ProcessName.ToLower().Contains("notepad")).DefaultIfEmpty(null).FirstOrDefault(); if (_notepadProcess != null) { var _windowHandle = FindWindow(null, "Page Setup"); var _parent = GetParent(_windowHandle); if (_parent == _notepadProcess.MainWindowHandle) { Invoke((MethodInvoker)delegate { Location = new System.Drawing.Point(550, 330); TopMost = true; }); } } //var ExternalApplication = Process.GetProcessesByName("Notepad").FirstOrDefault(p => p.MainWindowTitle.Contains("Page Setup")); //Location = new System.Drawing.Point(550, 330); //TopMost = true; } 

到目前为止,我的应用程序弹出屏幕并在我打开记事本时设置TopMost = true ,而不是记事本的“页面设置”,每当我关闭记事本时它都会移回屏幕的一角。

我想做的就是:

我想将应用程序移动到屏幕的中心,并在打开“页面设置”并返回到角落时设置TopMost = false ,并在关闭“页面设置”时将TopMost = false

Location = new System.Drawing.Point(550, 330); – 用于屏幕中心

Location = new System.Drawing.Point(1000, 1); – 角落

编辑:

我也试过这个,但没有运气。

 void PoPInFront() { //IntPtr hWnd = IntPtr.Zero; //foreach (Process pList in Process.GetProcesses()) //{ // if (pList.MainWindowTitle.Contains("Page Setup")) // { // hWnd = pList.MainWindowHandle; // Location = new System.Drawing.Point(550, 330); // TopMost = true; // } //} var ExternalApplication = Process.GetProcessesByName("Notepad").FirstOrDefault(p => p.MainWindowTitle.Contains("Page Setup")); Location = new System.Drawing.Point(550, 330); TopMost = true; } 

编辑2:

我认为问题在这里。我不知道如何做这样的事情:

 var query = new WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace WHERE ProcessName = 'Notepad.exe' AND MainWindowTitle = 'Page Setup'"); 

AND MainWindowTitle = 'Page Setup'

您可以使用以下任一选项:

  • 使用SetWinEventHook方法
  • 处理UI自动化事件(首选)(Hans在评论中的建议)

解决方案1 ​​ – 使用SetWinEventHook方法

使用SetWinEventHook您可以侦听来自其他进程的某些事件 ,并注册WinEventProc回调方法,以便在引发事件时接收事件。

这里EVENT_SYSTEM_FOREGROUND可以帮助我们。

我们限制事件接收器从特定进程接收此事件,然后我们检查导致事件的窗口文本是否等于Page Setup然后我们可以说目标进程中的Page Setup窗口是打开的,否则我们可以告诉Page Setup对话框未打开。

为了简化下面的示例,我假设应用程序启动时打开了一个notepad实例,但您也可以使用Win32_ProcessStartTrace来检测notepad应用程序的运行时间。

更具体地说,当对话框关闭时,您可以收听EVENT_OBJECT_DESTROY并检测该消息是否适用于我们感兴趣的窗口。

 public const uint EVENT_SYSTEM_FOREGROUND = 0x0003; public const uint EVENT_OBJECT_DESTROY = 0x8001; public const uint WINEVENT_OUTOFCONTEXT = 0; public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime); [DllImport("user32.dll")] public static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags); [DllImport("user32.dll")] public static extern bool UnhookWinEvent(IntPtr hWinEventHook); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); 
 IntPtr hook = IntPtr.Zero; protected override void OnLoad(EventArgs e) { base.OnLoad(e); var p = System.Diagnostics.Process.GetProcessesByName("notepad").FirstOrDefault(); if (p != null) hook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, new WinEventDelegate(WinEventProc), (uint)p.Id, 0, WINEVENT_OUTOFCONTEXT); } protected override void OnFormClosing(FormClosingEventArgs e) { UnhookWinEvent(hook); base.OnFormClosing(e); } void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) { string s = "Page Setup"; StringBuilder sb = new StringBuilder(s.Length + 1); GetWindowText(hwnd, sb, sb.Capacity); if (sb.ToString() == s) this.Text = "Page Setup is Open"; else this.Text = "Page Setup is not open"; } 

解决方案2 – 处理UI自动化事件

正如Hans的评论中所建议的那样,您可以使用UI Automation API订阅WindowOpenedEventWindowClosedEvent

在下面的示例中,我认为有一个打开的notepad实例,并检测到其Page Setup对话框的打开和关闭:

 protected override void OnLoad(EventArgs e) { base.OnLoad(e); var notepad = System.Diagnostics.Process.GetProcessesByName("notepad") .FirstOrDefault(); if (notepad != null) { var notepadMainWindow = notepad.MainWindowHandle; var notepadElement = AutomationElement.FromHandle(notepadMainWindow); Automation.AddAutomationEventHandler( WindowPattern.WindowOpenedEvent, notepadElement, TreeScope.Subtree, (s1, e1) => { var element = s1 as AutomationElement; if (element.Current.Name == "Page Setup") { //Page setup opened. this.Invoke(new Action(() => { this.Text = "Page Setup Opened"; })); Automation.AddAutomationEventHandler( WindowPattern.WindowClosedEvent, element, TreeScope.Subtree, (s2, e2) => { //Page setup closed. this.Invoke(new Action(() => { this.Text = "Closed"; })); }); } }); } } 
 protected override void OnFormClosing(FormClosingEventArgs e) { Automation.RemoveAllEventHandlers(); base.OnFormClosing(e); } 

不要忘记添加对UIAutomationClientUIAutomationTypes程序集的引用,并using System.Windows.Automation;添加using System.Windows.Automation;

你需要使用user32.dll导入。

首先,在您的使用中确保您拥有:

 using System.Runtime.InteropServices; using System.Linq; 

然后,在顶部的类中插入此代码以从DLL导入方法。

  [DllImport("user32.dll", SetLastError = true)] static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll")] public static extern IntPtr GetParent(IntPtr hWnd); 

现在,在您自己的方法中,以下代码应该工作:

  var _notepadProcess = System.Diagnostics.Process.GetProcesses().Where(x => x.ProcessName.ToLower().Contains("notepad")).DefaultIfEmpty(null).FirstOrDefault(); if ( _notepadProcess != null ) { var _windowHandle = FindWindow(null, "Page Setup"); var _parent = GetParent(_windowHandle); if ( _parent == _notepadProcess.MainWindowHandle ) { //We found our Page Setup window, and it belongs to Notepad.exe - yay! } } 

这应该让你开始。

*****编辑******

好的,你有这个:

 mew.EventArrived += (sender, args) => { AppStarted(); }; 

这将确保在notepad.exe进程启动时触发AppStarted()方法。

然后等待300毫秒(出于某种原因?!)然后调用PopInFront:

 async void AppStarted() { await Task.Delay(300); BeginInvoke(new System.Action(PoPInFront)); } 

在PopInFront()内部,您尝试查找“页面设置”窗口

 var _windowHandle = FindWindow(null, "Page Setup"); 

但是,我的查询是:在0.3秒内,你可以安全地说你已经能够打开记事本,等待GUI初始化并导航到文件 – >页面设置菜单.3秒为下一个代码区找到窗口? – 我的猜测不是,你需要的是一个循环。

你应该做的是:

  1. WMI查询事件被触发
  2. 使用while循环启动后台工作程序,该循环在进程notepad.exe处于活动状态时循环
  3. 在while循环中,继续检查“页面设置”窗口
  4. 找到后,弹出您自己的对话框,标记另一个变量以跟踪您的对话框是否显示
  5. 一旦不再显示页面设置对话框(FindWindow将返回零),重新标记阶段4中的变量并允许再次找到页面设置窗口。