在另一个应用程序中的 MessageBox 内捕获按钮单击事件 [英] Capture Button Click event inside a MessageBox in another application

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

问题描述

我想在另一个 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 的 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");
}

推荐答案

我尽量让这个过程保持通用,这样无论你正在观看的应用程序是否已经在运行,它都能工作您的应用何时启动.

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() 方法被调用).这些信息在其他情况下也可能有用.
  • 当系统中打开一个新窗口时,程序会验证此窗口是否属于被监视的应用程序.如果是,则进程 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; }
}

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

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