在另一个应用程序的消息框中单击“捕获”按钮 [英] Capture button click inside a messagebox in another application

查看:134
本文介绍了在另一个应用程序的消息框中单击“捕获”按钮的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在另一个WinForms应用程序显示的MessageBox上捕获 OK 按钮的 Click 事件。

I want to capture the OK Button's Click event on a MessageBox shown by another WinForms application.

我想使用UI自动化来实现这一点。经过研究,我发现 IUIAutomation :: AddAutomationEventHandler 将为我完成这项工作。

I want to achieve this using UI Automation. After some research, I have found that IUIAutomation::AddAutomationEventHandler will do the work for me.

尽管我可以捕获任何其他按钮的 Click 事件,但无法捕获<$ MessageBox的c $ c> Click 事件。

Though, I can capture the Click event of any other button, I'm unable to capture a Click event of the MessageBox.

我的代码如下:

var FindDialogButton = appElement.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.NameProperty, "OK"));

if (FindDialogButton != null)
{
    if (FindDialogButton.GetSupportedPatterns().Any(p => p.Equals(InvokePattern.Pattern)))
    {
        Automation.AddAutomationEventHandler(InvokePattern.InvokedEvent, FindDialogButton, TreeScope.Element, new AutomationEventHandler(DialogHandler));
    }
}

private void DialogHandler(object sender, AutomationEventArgs e)
{
    MessageBox.Show("Dialog Button clicked at : " + DateTime.Now);
}






编辑:

我的完整代码如下:

private void DialogButtonHandle()
{
    AutomationElement rootElement = AutomationElement.RootElement;
    if (rootElement != null)
    {
        System.Windows.Automation.Condition condition = new PropertyCondition
     (AutomationElement.NameProperty, "Windows Application"); //This part gets the handle of the Windows application that has the MessageBox

        AutomationElement appElement = rootElement.FindFirst(TreeScope.Children, condition);

        var FindDialogButton = appElement.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.NameProperty, "OK")); // This part gets the handle of the button inside the messagebox
        if (FindDialogButton != null)
        {
            if (FindDialogButton.GetSupportedPatterns().Any(p => p.Equals(InvokePattern.Pattern)))
            {
                Automation.AddAutomationEventHandler(InvokePattern.InvokedEvent, FindDialogButton, TreeScope.Element, new AutomationEventHandler(DialogHandler)); //Here I am trying to catch the click of "OK" button inside the MessageBox
            }
        }
    }
}

private void DialogHandler(object sender, AutomationEventArgs e)
{
    //On Button click I am trying to display a message that the button has been clicked
    MessageBox.Show("MessageBox Button Clicked");
}


推荐答案

我试图保留此过程尽可能使用 generic ,这样无论您启动的应用程序是否正在运行,无论您正在观看的应用程序是否正在运行,它都将起作用。

I tried to keep this procedure as generic as possible, so that it will work whether the application you're watching is already running when your app is started or not.

您只需要提供监视的应用程序的进程名称或其主窗口标题,即可使该过程识别该应用程序。

使用以下字段之一以及相应的枚举数:

You just need to provide the watched Application's Process Name or its Main Window Title to let the procedure identify this application.
Use one of these Fields and the corresponding Enumerator:

private string appProcessName = "theAppProcessName"; and 
FindWindowMethod.ProcessName
// Or
private string appWindowTitle = "theAppMainWindowTitle"; and 
FindWindowMethod.Caption

将这些值传递给启动观察程序的过程,例如, :

passing these values to the procedure that starts the watcher, e.g., :

StartAppWatcher(appProcessName, FindWindowMethod.ProcessName); 

您可以看到-因为您将问题标记为 winforms -这是一个完整的表单(名为 frmWindowWatcher ),其中包含执行此任务所需的所有逻辑。

As you can see - since you tagged your question as winforms - this is a complete Form (named frmWindowWatcher) that contains all the logic required to perform this task.

工作原理:


  • 开始时 frmWindowWatcher ,该过程将验证受监视的应用程序(在此使用其进程名称进行标识,但是您可以更改其方法,如上所述),是否已在运行。

    如果是的,它初始化了一个支持类 ElementWindow ,其中将包含有关受监视应用程序的一些信息。

    如果您需要监视的应用程序已经在运行,则需要执行此支持类(在这种情况下, ElementWindow windowElement 字段不会调用 StartAppWatcher() 方法时为null)。这些信息在其他情况下也可能有用。

  • 在系统中打开新的Windows时,该过程将验证此Window是否属于受监视的应用程序。如果是这样,则进程ID将相同。如果Windows是MessageBox(使用其标准 ClassName 标识: #32770 ),则它属于受监视的应用程序,它是 AutomationEventHandler 附加在子 OK 按钮上。

    在这里,我使用的是委托: AutomationEventHandler DialogBu​​ttonHandler (用于处理程序)和实例字段( AutomationElement msgBoxButton ),因为这些引用是关闭MessageBox时需要删除按钮单击处理程序。

  • 单击MessageBox的 OK 按钮后, MessageBoxButtonHandler 方法被调用。在这里,您可以确定此时要采取的措施。

  • 关闭 frmWindowWatcher 表单时,将删除所有自动化处理程序,并调用 Automation.RemoveAllEventHandlers()方法,以提供最终清理并阻止您的应用程序从资源泄漏。

  • When you start frmWindowWatcher, the procedure verifies whether the watched application (here, identified using its Process name, but you can change the method, as already described), is already running.
    If it is, it initializes a support class, ElementWindow, which will contain some informations about the watched application.
    I added this support class in case you need to perform some actions if the watched application is already running (in this case, the ElementWindow windowElement Field won't be null when the StartAppWatcher() method is called). These informations may also be useful in other cases.
  • When a new Windows is opened in the System, the procedure verifies whether this Window belongs to the watched application. If it does, the Process ID will be the same. If the Windows is a MessageBox (identified using its standard ClassName: #32770) and it belongs to the watched Application, an AutomationEventHandler is attached to the child OK Button.
    Here, I'm using a Delegate: AutomationEventHandler DialogButtonHandler for the handler and an instance Field (AutomationElement msgBoxButton) for the Button Element, because these references are needed to remove the Button Click Handler when the MessageBox is closed.
  • When the MessageBox's OK Button is clicked, the MessageBoxButtonHandler method is called. Here, you can determine which action to take at this point.
  • When the frmWindowWatcher Form is closed, all Automation Handlers are removed, calling the Automation.RemoveAllEventHandlers() method, to provide a final clean up and prevent your app from leaking resources.

using System.Diagnostics;
using System.Linq;
using System.Windows.Automation;
using System.Windows.Forms;

public partial class frmWindowWatcher : Form
{
    AutomationEventHandler DialogButtonHandler = null;
    AutomationElement msgBoxButton = null;
    ElementWindow windowElement = null;
    int currentProcessId = 0;
    private string appProcessName = "theAppProcessName";
    //private string appWindowTitle = "theAppMainWindowTitle";

    public enum FindWindowMethod
    {
        ProcessName,
        Caption
    }

    public frmWindowWatcher()
    {
        InitializeComponent();
        using (var proc = Process.GetCurrentProcess()) {
            currentProcessId = proc.Id;
        }
        // Identify the application by its Process name...
        StartAppWatcher(appProcessName, FindWindowMethod.ProcessName);
        // ... or by its main Window Title
        //StartAppWatcher(appWindowTitle, FindWindowMethod.Caption);
    }

    protected override void OnFormClosed(FormClosedEventArgs e)
    {
        Automation.RemoveAllEventHandlers();
        base.OnFormClosed(e);
    }

    private void StartAppWatcher(string elementName, FindWindowMethod method)
    {
        windowElement = GetAppElement(elementName, method);
        // (...)
        // You may want to perform some actions if the watched application is already running when you start your app

        Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, AutomationElement.RootElement,
            TreeScope.Subtree, (elm, e) => {
                AutomationElement element = elm as AutomationElement;

                try
                {
                    if (element == null || element.Current.ProcessId == currentProcessId) return;
                    if (windowElement == null) windowElement = GetAppElement(elementName, method);
                    if (windowElement == null || windowElement.ProcessId != element.Current.ProcessId) return;

                    // If the Window is a MessageBox generated by the watched app, attach the handler
                    if (element.Current.ClassName == "#32770")
                    {
                        msgBoxButton = element.FindFirst(TreeScope.Descendants, 
                            new PropertyCondition(AutomationElement.NameProperty, "OK"));
                        if (msgBoxButton != null && msgBoxButton.GetSupportedPatterns().Any(p => p.Equals(InvokePattern.Pattern)))
                        {
                            Automation.AddAutomationEventHandler(
                                InvokePattern.InvokedEvent, msgBoxButton, TreeScope.Element,
                                    DialogButtonHandler = new AutomationEventHandler(MessageBoxButtonHandler));
                        }
                    }
                }
                catch (ElementNotAvailableException) {
                    // Ignore: this exception may be raised if you show a modal dialog, 
                    // in your own app, that blocks the execution. When the dialog is closed, 
                    // AutomationElement element is no longer available
                }
            });

        Automation.AddAutomationEventHandler(WindowPattern.WindowClosedEvent, AutomationElement.RootElement,
            TreeScope.Subtree, (elm, e) => {
                AutomationElement element = elm as AutomationElement;

                if (element == null || element.Current.ProcessId == currentProcessId || windowElement == null) return;
                if (windowElement.ProcessId == element.Current.ProcessId) {
                    if (windowElement.MainWindowTitle == element.Current.Name) {
                        windowElement = null;
                    }
                }
            });
    }

    private void MessageBoxButtonHandler(object sender, AutomationEventArgs e)
    {
        Console.WriteLine("Dialog Button clicked at : " + DateTime.Now.ToString());
        // (...)
        // Remove the handler after, since the next MessageBox needs a new handler.
        Automation.RemoveAutomationEventHandler(e.EventId, msgBoxButton, DialogButtonHandler);
    }

    private ElementWindow GetAppElement(string elementName, FindWindowMethod method)
    {
        Process proc = null;

        try {
            switch (method) {
                case FindWindowMethod.ProcessName:
                    proc = Process.GetProcessesByName(elementName).FirstOrDefault();
                    break;
                case FindWindowMethod.Caption:
                    proc = Process.GetProcesses().FirstOrDefault(p => p.MainWindowTitle == elementName);
                    break;
            }
            return CreateElementWindow(proc);
        }
        finally {
            proc?.Dispose();
        }
    }

    private ElementWindow CreateElementWindow(Process process) => 
        process == null ? null : new ElementWindow(process.ProcessName) {
            MainWindowTitle = process.MainWindowTitle,
            MainWindowHandle = process.MainWindowHandle,
            ProcessId = process.Id
        };
}

支持类,用于在被监视的应用程序上存储信息:

使用应用程序的进程名称进行初始化:

Support class, used to store informations on the watched application:
It's initialized using the App's Process Name:

public ElementWindow(string processName)

当然,您可以根据需要使用如前所述的窗口标题进行更改,或者根据需要甚至删除初始化的参数(该类只需在检测到并识别受监视的应用程序时不必将其设置为 null

but of course you can change it as required, using the Window Title as described before, or even remove the initialization's argument if you prefer (the class just need to not be null when the watched Application has been detected and identified).

using System.Collections.Generic;

public class ElementWindow
{
    public ElementWindow(string processName) => this.ProcessName = processName;

    public string ProcessName { get; set; }
    public string MainWindowTitle { get; set; }
    public int ProcessId { get; set; }
    public IntPtr MainWindowHandle { get; set; }
}

这篇关于在另一个应用程序的消息框中单击“捕获”按钮的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆