Как получить текст MessageBox, когда у него есть иконка? - PullRequest
4 голосов
/ 06 марта 2019

Я пытаюсь закрыть конкретный MessageBox, если он появляется на основе заголовка и текста.У меня это работает, когда MessageBox не имеет значок.

IntPtr handle = FindWindowByCaption(IntPtr.Zero, "Caption");
if (handle == IntPtr.Zero)
    return;

//Get the Text window handle
IntPtr txtHandle = FindWindowEx(handle, IntPtr.Zero, "Static", null);
int len = GetWindowTextLength(txtHandle);

//Get the text
StringBuilder sb = new StringBuilder(len + 1);
GetWindowText(txtHandle, sb, len + 1);

//close the messagebox
if (sb.ToString() == "Original message")
{
    SendMessage(new HandleRef(null, handle), WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}

Приведенный выше код работает просто отлично, когда MessageBox отображается без значка , как показано ниже.

MessageBox.Show("Original message", "Caption");

Однако, если он включает значок (из MessageBoxIcon), как показано ниже, он не работает;GetWindowTextLength возвращает 0 и ничего не происходит.

MessageBox.Show("Original message", "Caption", MessageBoxButtons.OK, MessageBoxIcon.Information);

Мое лучшее предположение, что 3-й и / или 4-й параметры FindWindowEx должны измениться, но я не уверен, что вместо этого передать.Или, может быть, нужно изменить второй параметр, чтобы пропустить значок?Я не совсем уверен.

Ответы [ 2 ]

3 голосов
/ 06 марта 2019

Похоже, что когда MessageBox имеет значок, FindWindowEx возвращает текст первого дочернего элемента (в данном случае это значок), следовательно, нулевую длину.Теперь, с помощью этого ответа , у меня появилась идея перебирать детей до тех пор, пока они не найдут текст с текстом.Это должно работать:

IntPtr handle = FindWindowByCaption(IntPtr.Zero, "Caption");

if (handle == IntPtr.Zero)
    return;

//Get the Text window handle
IntPtr txtHandle = IntPtr.Zero;
int len;
do
{
    txtHandle = FindWindowEx(handle, txtHandle, "Static", null);
    len = GetWindowTextLength(txtHandle);
} while (len == 0 && txtHandle != IntPtr.Zero);

//Get the text
StringBuilder sb = new StringBuilder(len + 1);
GetWindowText(txtHandle, sb, len + 1);

//close the messagebox
if (sb.ToString() == "Original message")
{
    SendMessage(new HandleRef(null, handle), WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}

Очевидно, что вы можете настроить его в соответствии с вашей конкретной ситуацией (например, продолжайте итерации, пока не найдете нужный текст), хотя я думаю, что ребенок с текстом будетвероятно всегда будет вторым:

Messagebox in Spy++

2 голосов
/ 06 марта 2019

Это метод автоматизации пользовательского интерфейса, который может обнаружить событие «Открытое окно» в любом месте системы, идентифицировать окно по тексту одного из его дочерних элементов и закрыть окно после положительной идентификации.

Обнаружение инициализируется с использованием Automation.AddAutomationEventHandler с WindowPattern.WindowOpenedEvent и параметром элемента автоматизации, установленным в AutomationElement.RootElement , который не имеетдругих предков, идентифицирует весь рабочий стол (любое окно).

Класс WindowWatcher предоставляет открытый метод (WatchWindowBySubElementText), который позволяет указывать текст, содержащийся в одном из вложенных элементов только что открытого окна.Если указанный текст найден, метод закрывает окно и уведомляет об операции с помощью пользовательского обработчика событий, который подписчик может использовать для определения того, что наблюдаемое окно было обнаружено и закрыто.

Пример использования с использованием текстовой строки, указанной в вопросе:

WindowWatcher watcher = new WindowWatcher();
watcher.ElementFound += (obj, evt) => { MessageBox.Show("Found and Closed!"); };
watcher.WatchWindowBySubElementText("Original message");

WindowWatcher class:

Для этого класса требуется ProjectСсылка на эти сборки:
UIAutomationClient
UIAutomationTypes

Обратите внимание, что при идентификации событие класса удаляет AutomationОбработчик событий перед уведомлением подписчиков.Это всего лишь пример: он указывает, что обработчики должны быть удалены в какой-то момент.Класс может реализовать IDisposable и удалить обработчик (и) при утилизации.

EDIT :
Изменено условие, которое не учитывает окно, созданное втекущий Процесс:

if (element is null || element.Current.ProcessId != Process.GetCurrentProcess().Id)  

Как отметил Ахмед Абдельхамид в комментариях , он накладывает ограничение, которое, вероятно, не является необходимым: Dialod также может принадлежать текущему Процессу.Я оставил там только null чек.

using System;
using System.Diagnostics;
using System.Windows.Automation;

public class WindowWatcher
{
    public delegate void ElementFoundEventHandler(object sender, EventArgs e);
    public event ElementFoundEventHandler ElementFound;

    public WindowWatcher() { }
    public void WatchWindowBySubElementText(string ElementText) => 
        Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, 
            AutomationElement.RootElement, TreeScope.Subtree, (UIElm, evt) =>
            {
                AutomationElement element = UIElm as AutomationElement;
                if (element is null) return;

                AutomationElement childElm = element.FindFirst(TreeScope.Children,
                    new PropertyCondition(AutomationElement.NameProperty, ElementText));
                if (childElm != null)
                {
                    (element.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern).Close();
                    this.OnElementFound(new EventArgs());
                }
            });
    public void OnElementFound(EventArgs e)
    {
        Automation.RemoveAllEventHandlers();
        ElementFound?.Invoke(this, e);
    }
}
...