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

本文关键字:何时打 窗口 进程 另一个 检测 | 更新日期: 2023-09-27 18:14:54

所以我做了一个win应用程序,我希望它在我从记事本打开"Page Setup"时弹出在屏幕前面,并在我关闭记事本时关闭它。

我试过了:

[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 = true,并在"页面设置"关闭时设置TopMost = false

Location = new System.Drawing.Point(550, 330); -为屏幕中心

Location = new System.Drawing.Point(1000, 1); - for角

编辑:

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

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自动化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;

您需要使用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!
            }
        }

这应该可以让你开始。

***** EDIT ******

好的,你得到了这个:

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

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

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

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

在PopInFront()你试图找到"页面设置"窗口

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

然而,我这里的查询是:在已经过去的0.3秒内,你能安全地说你已经能够打开记事本,等待GUI初始化并在0.3秒内导航到文件->页面设置菜单,以便下一个代码区域找到窗口吗?-我猜不是,你需要的是一个循环。

你应该做的是:

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