简体   繁体   English

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

[英]Detect when a specific window in another process opens or closes

So I made a win app and I want it to pop in front of the screen whenever I open the "Page Setup" from notepad and close it whenever I close the notepad. 所以我做了一个win应用程序,每当我打开记事本中的“页面设置”并且每当我关闭记事本时关闭它,我都希望它在屏幕前弹出。

I tried this: 我试过这个:

[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;
}

Until now my app pops in front of the screen and sets TopMost = true whenever I open notepad,not the "Page Setup" of the notepad and it moves back to the corner of the screen whenever I close the notepad. 到目前为止,我的应用程序弹出屏幕前,每当我打开记事本时设置TopMost = true ,而不是记事本的“页面设置”,每当我关闭记事本时它都会移回屏幕的一角。

All I want to do is: 我想做的就是:

I would like to move the app to the center of the screen and set TopMost = true whenever the "Page Setup" is opened and back to the corner and TopMost = false when the "Page Setup" is closed. 我想应用程序移动到屏幕的中心,并设置TopMost = true每当“页面设置”打开,回到角落, TopMost = false当“页面设置”关闭。

Location = new System.Drawing.Point(550, 330); - for center of the screen - 用于屏幕中心

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

Edit: 编辑:

I tried also this but no luck. 我也试过这个,但没有运气。

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;
        }

Edit 2: 编辑2:

I think the problem is here.I don't know how to do something like this: 我认为问题在这里。我不知道如何做这样的事情:

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

AND MainWindowTitle = 'Page Setup'

You can use either of these options: 您可以使用以下任一选项:

  • Using SetWinEventHook method 使用SetWinEventHook方法
  • Handling UI Automation Events (Preferred) (Suggested by Hans in comments ) 处理UI自动化事件(首选)(Hans在评论中的建议)

Solution 1 - Using SetWinEventHook method 解决方案1 ​​ - 使用SetWinEventHook方法

Using SetWinEventHook you can listen to some events from other processes and register a WinEventProc callback method to receive the event when the event raised. 使用SetWinEventHook您可以侦听来自其他进程的某些事件 ,并注册WinEventProc回调方法,以便在引发事件时接收事件。

Here EVENT_SYSTEM_FOREGROUND can help us. 这里EVENT_SYSTEM_FOREGROUND可以帮助我们。

We limit the event receiver to receive this event from a specific process and then we check if the text of the window which causes the event is equals to Page Setup then we can say the Page Setup window in the target process is open, otherwise we can tell the Page Setup dialog is not open. 我们限制事件接收器从特定进程接收此事件,然后我们检查导致事件的窗口文本是否等于Page Setup然后我们可以说目标进程中的Page Setup窗口是打开的,否则我们可以告诉Page Setup对话框未打开。

To keep things simple in below example I supposed a notepad instance is open when your application starts, but you can also use Win32_ProcessStartTrace to detect when a notepad application runs. 为了简化下面的示例,我假设应用程序启动时打开了一个notepad实例,但您也可以使用Win32_ProcessStartTrace来检测notepad应用程序何时运行。

To be more specific and say when the dialog is closed, you can listen to EVENT_OBJECT_DESTROY and detect if the message is for the window which we are interested in. 更具体地说,当对话框关闭时,您可以收听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";
}

Solution 2 - Handling UI Automation Events 解决方案2 - 处理UI自动化事件

As suggested in comments by Hans, you can use UI Automation APIs to subscribe for WindowOpenedEvent and WindowClosedEvent . 正如Hans的评论中所建议的那样,您可以使用UI Automation API订阅WindowOpenedEventWindowClosedEvent

In below example I supposed there is an open instance of notepad and detected opening and closing of its Page Setup dialog: 在下面的示例中,我假设有一个打开的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);
}

Don't forget to add reference to UIAutomationClient and UIAutomationTypes assemblies and add using System.Windows.Automation; 不要忘记添加对UIAutomationClientUIAutomationTypes程序集的引用,并using System.Windows.Automation;添加using System.Windows.Automation; .

You need to use user32.dll imports for this i would say. 你需要使用user32.dll导入。

Firstly, in your usings ensure you have: 首先,在您的使用中确保您拥有:

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

Then, in your class at the top insert this code to import methods from the DLL. 然后,在顶部的类中插入此代码以从DLL导入方法。

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

    [DllImport("user32.dll")]
    public static extern IntPtr GetParent(IntPtr hWnd);

Now, in your own method, the following code should work: 现在,在您自己的方法中,以下代码应该工作:

        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!
            }
        }

This should get you started. 这应该让你开始。

***** EDIT ****** *****编辑******

Okay so you have got this: 好的,你有这个:

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

This will ensure that the AppStarted() method gets fired when the notepad.exe process has started. 这将确保在notepad.exe进程启动时触发AppStarted()方法。

You then wait for 300ms (for some reason?!) and then call PopInFront: 然后等待300毫秒(由于某种原因?!)然后调用PopInFront:

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

Inside PopInFront() you attempt to find the "Page Setup" window 在PopInFront()内部,您尝试查找“页面设置”窗口

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

However, my query here is: within the 0.3 seconds that have passed, can you safly say that you have been able to open notepad, wait for the GUI to init and navigate to the File -> Page Setup menu in .3 of a second for the next code area to find the window? 但是,我的查询是:在0.3秒内,你可以安全地说你已经能够打开记事本,等待GUI初始化并导航到文件 - >页面设置菜单.3秒为下一个代码区找到窗口? - My guess is not, what you need here is a loop. - 我的猜测不是,你需要的是一个循环。

What you should be doing is: 你应该做的是:

  1. WMI Query Event Fired WMI查询事件被触发
  2. Start a background worker with a while loop that loops while the process notepad.exe is alive 使用while循环启动后台工作程序,该循环在进程notepad.exe处于活动状态时循环
  3. In the while loop, keep checking for the Page Setup window 在while循环中,继续检查“页面设置”窗口
  4. Once it's found, popup your own dialog, mark another variable to keep track that your dialog is shown 找到后,弹出您自己的对话框,标记另一个变量以跟踪您的对话框是否显示
  5. Once the page setup dialog is no longer shown (FindWindow will return zero), re-mark the variale in stage 4 and allow for the Page Setup window to be found again. 一旦页面设置对话框不再显示(FindWindow将返回零),重新标记阶段4中的变量并允许再次找到页面设置窗口。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM