Нажмите кнопку захвата внутри окна сообщения в другом приложении - PullRequest
2 голосов
/ 01 октября 2019

Я хочу захватить событие OK кнопки Click в MessageBox, отображаемом другим приложением WinForms.

Я хочу добиться этого с помощью автоматизации пользовательского интерфейса. После некоторых исследований я обнаружил, что IUIAutomation :: AddAutomationEventHandler сделает всю работу за меня.

Хотя я могу перехватить событие Click любой другой кнопки, я не могу перехватить событие Click 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");
}

1 Ответ

2 голосов
/ 04 октября 2019

Я пытался сохранить эту процедуру как generic , насколько это возможно, чтобы она работала независимо от того, запущено ли уже просматриваемое приложение при запуске вашего приложения или нет.

Вам просто нужно указать имя процесса просматриваемого приложения или заголовок его главного окна, чтобы процедура могла идентифицировать это приложение.
Используйте одно из этих полей и соответствующий перечислитель:

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 не будет иметь значение null при вызове метода StartAppWatcher()). Эта информация также может быть полезна в других случаях.
  • Когда в системе открывается новая Windows, процедура проверяет, принадлежит ли это окно наблюдаемому приложению. Если это произойдет, идентификатор процесса будет таким же. Если Windows является MessageBox (идентифицируется с использованием стандартного ClassName: #32770) и принадлежит наблюдаемому приложению, AutomationEventHandler присоединяется к дочерней OK кнопке.
    Здесь я использую делегат: AutomationEventHandler DialogButtonHandler для обработчика и поле экземпляра (AutomationElement msgBoxButton) для элемента Button, поскольку эти ссылки необходимыудалить обработчик нажатия кнопки, когда 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; }
}
...