当有图标时如何获取 MessageBox 的文本?

How to get the text of a MessageBox when it has an icon?

如果某个 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 returns 0 没有任何反应。

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

我最好的猜测是 FindWindowEx 的第 3 个 and/or 第 4 个参数需要更改,但我不确定要传递什么。或者可能需要更改第二个参数以跳过图标?我不太确定。

看起来当 MessageBox 有一个图标时,FindWindowEx returns 第一个 child 的文本(在这种情况下是图标)因此,长度为零。现在,在 this answer 的帮助下,我有了迭代 children 的想法,直到找到一个有文本的。这应该有效:

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

显然,您可以调整它以适应您的特定情况(例如,不断迭代直到找到您要查找的实际文本),尽管我认为带有文本的 child 可能永远是第二个:

这是一个 UI 自动化方法,可以检测系统中任何位置的 Window 打开事件,使用其子元素的文本识别 Window 并关闭 Window 确认后。

检测是使用 Automation.AddAutomationEventHandler with WindowPattern.WindowOpenedEvent and Automation Element argument set to AutomationElement.RootElement 初始化的,它没有其他祖先,可以识别整个桌面(任何 Window)。

WindowWatcher class 公开了一个 public 方法 (WatchWindowBySubElementText) 允许指定一个文本中包含的文本刚刚打开的 Window 的子元素。如果找到指定的文本,该方法将关闭 Window 并使用自定义事件处理程序通知操作,订阅者可以使用该事件处理程序来确定已检测到并关闭所监视的 Window。

示例用法,使用问题中提供的文本字符串:

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

WindowWatcher class:

此 class 需要对这些程序集的项目引用:
UIAutomationClient
UIAutomationTypes

Note that, upon identification, the class event removes the Automation event handler before notifying the subscribers. This is just an example: it points out that the handlers need to be removed at some point. The class could implement IDisposable and remove the handler(s) when disposed of.

编辑:
更改了不考虑在当前进程中创建的 Window 的条件:

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

一样,它施加了一个可能没有必要的限制:对话框也可以属于当前进程。我只留下 null 支票。

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;
                try {
                    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();
                        OnElementFound(new EventArgs());
                    }
                }
                catch (ElementNotAvailableException) {
                    // Ignore: generated when a Window is closed. Its AutomationElement   
                    // is no longer available. Usually a modal dialog in the current process. 
                }
            });
    public void OnElementFound(EventArgs e)
    {
        // Automation.RemoveAllEventHandlers(); <= If single use. Add to IDisposable.Dispose()
        ElementFound?.Invoke(this, e);
    }
}