【问题标题】:Capture Button Click event inside a MessageBox in another application在另一个应用程序的 MessageBox 内捕获按钮单击事件
【发布时间】:2020-01-30 18:07:48
【问题描述】:

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

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

虽然我可以捕获任何其他按钮的Click 事件,但我无法捕获MessageBox 的Click 事件。

我的代码如下:

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

【问题讨论】:

  • 什么是appElement?在将处理程序添加到其元素之一之前,您需要识别消息框(如果消息框实际上是MessageBox)。在此处查看使用 UI 自动化的答案:How to get the text of a MessageBox when it has an icon? 以使其正常工作。
  • appElement 是 MessageBox 在其中打开的另一个应用程序。而且我已经在 FindDialogBu​​tton 中识别了 MessageBox。在里面我得到了 MessageBox 的句柄。
  • appElement 是 MessageBox 在其中打开的另一个应用程序。 UI 自动化不处理 Application 元素,它处理控件。所以,appElement 应该是一个 Window 元素。 MessageBox 不属于另一个窗口,它是一个独立的窗口。你是如何检测到这个 MessageBox 的打开的?缺少这部分代码,因此我不知道您现在正在处理什么。
  • 是的,这就是我的意思,appElement 是一个窗口。我正在通过这一行检测 MessageBox 的打开 var FindDialogBu​​tton = appElement.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.NameProperty, "OK"));我也可以通过ClassName找到消息框不是问题,这里的问题是我想检测对话框按钮的点击
  • 这不是检测WindowOpened 事件的方法。 MessageBox 不是另一个 Window 的后代。请参阅我第一条评论中的链接。

标签: c# .net winforms button ui-automation


【解决方案1】:

我尝试将此过程保持为通用,以便无论您正在观看的应用程序是否在您的应用程序启动时已经运行,它都能正常工作。

您只需要提供被监视的应用程序的进程名称或其主窗口标题即可让程序识别此应用程序。
使用这些字段之一和相应的枚举器:

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

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

StartAppWatcher(appProcessName, FindWindowMethod.ProcessName); 

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

它是如何工作的:

  • 当您启动 frmWindowWatcher 时,该过程会验证被监视的应用程序(此处使用其进程名称进行标识,但您可以更改方法,如前所述)是否已在运行。
    如果是,它会初始化一个支持类,ElementWindow,其中将包含有关被监视应用程序的一些信息。
    我添加了这个支持类,以防你需要在监视的应用程序已经运行时执行一些操作(在这种情况下,ElementWindow windowElement 字段在 StartAppWatcher() 方法被调用)。这些信息在其他情况下也可能有用。
  • 当在系统中打开一个新的窗口时,程序会验证这个窗口是否属于被监视的应用程序。如果是,进程 ID 将是相同的。如果 Windows 是 MessageBox(使用其标准 ClassName 标识:#32770)并且属于被监视的应用程序,则将 AutomationEventHandler 附加到子 OK 按钮。
    在这里,我使用一个代表:AutomationEventHandler DialogButtonHandler 作为处理程序,一个实例字段 (AutomationElement msgBoxButton) 作为按钮元素,因为需要删除这些引用MessageBox 关闭时的按钮单击处理程序。
  • 当MessageBox的OK按钮被点击时,MessageBoxButtonHandler方法被调用。在这里,您可以确定此时要采取的行动。
  • frmWindowWatcher 表单关闭时,所有自动化处理程序都将被删除,调用Automation.RemoveAllEventHandlers() 方法,以提供最终清理并防止您的应用程序泄漏资源。


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

支持类,用于存储被监视应用的信息:
它是使用应用程序的进程名称初始化的:

public ElementWindow(string processName)

当然,您可以根据需要更改它,使用前面描述的窗口标题,或者如果您愿意,甚至可以删除初始化的参数(当已检测和识别监视的应用程序时,该类只需要不是null )。

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

【讨论】:

  • 这对我有用。非常感谢你的帮助。一个问题是我们需要这个 ElementWindow 类还是不需要这个?
  • 不,没有必要。它仅用于存储有关被监视应用程序的一些数据(它是 ProcessID、MainWindowHandle 等)。如果你不需要执行任何需要这些信息的操作,你只需要一个Field来存储App的ProcessID,它用于标识被监视的Application的Process,这样你就可以判断一个新打开的Window是否属于这个应用程序(请注意,某些应用程序可能有多个 ProcessID,但这是另一回事 :) 和 MainWindowTitle,以确定主应用程序窗口是否已关闭。
  • 请注意,我已经编辑了代码,因为我留下了一个不需要的字段 buttonElement(在尝试缩短代码时,我忘了删除它),现在已重命名为 msgBoxButton这是删除 AutomationEventHandler 所需的唯一 AutomationElement。注释已相应更新。
  • 太棒了!非常感谢你的帮助。非常感激。 :)
  • 嗨@Jimi,我需要一些帮助,我在 WPF 应用程序上尝试相同的代码来获取按钮单击的句柄,但它不适用于 WPF 应用程序。它仅适用于 Winform 应用程序或窗口中出现的任何其他对话框。请帮忙
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-23
  • 2015-12-29
  • 1970-01-01
相关资源
最近更新 更多