【问题标题】:How to get the text of a MessageBox when it has an icon?有图标时如何获取 MessageBox 的文本?
【发布时间】:2019-07-27 14:37:00
【问题描述】:

我正在尝试关闭特定的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);

我最好的猜测是FindWindowEx 的第三和/或第四个参数需要更改,但我不确定要传递什么。或者也许第二个参数需要更改以跳过图标?我不太确定。

【问题讨论】:

  • 如果您愿意尝试 UI 自动化,Automation.AddAutomationEventHandlerWindowPattern.WindowOpenedEvent 会在 MessageBox 以任何方式打开(或关闭)时通知您。
  • @Jimi 我还需要看看刚刚打开的MessageBox 是否是我正在寻找的那个。我不想关闭所有MessageBox。只是带有我正在寻找的消息的那个。为此,我似乎仍然需要使用 FindWindow。
  • UI 自动化在事件处理程序参数中返回引发事件的元素。 Element.Current 对象具有识别 MessageBox 所需的所有属性。例如,在这种情况下,Element.Current.Name 将是 "Caption" 。
  • @Jimi,但我怎样才能得到消息框的文本。 “标题”缩小了一些范围,但对于我的目的来说太笼统了。我需要检查消息的具体文本。这适用于我的代码,除非我在消息框中显示图标。
  • 当事件处理程序返回引发事件的元素(您的消息框)时,您只需要找到具有您知道的属性的子元素。例如,要查找具有您在此处显示的文本 (Original message) 的元素,则应为 [Element].FindAll(TreeScope.Children, New PropertyCondition(AutomationElement.NameProperty, "Original message"));。如果返回的集合为空,则没有找到匹配的元素。不是你的 MessageBox。

标签: c# user32 findwindowex


【解决方案1】:

这是一种 UI 自动化方法,可以检测系统中任何位置的 Window Opened 事件,使用其子元素的 Text 识别 Window,并在确定后关闭 Window。

检测使用Automation.AddAutomationEventHandlerWindowPattern.WindowOpenedEvent 初始化,Automation Element 参数设置为AutomationElement.RootElement,它没有其他祖先,可以识别整个桌面(任何窗口)。

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

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

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

WindowWatcher 类:

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

请注意,在识别后,类事件会删除自动化 通知订阅者之前的事件处理程序。这只是一个 示例:它指出处理程序需要在某些时候被删除 观点。该类可以实现IDisposable 并删除 处置时的处理程序。

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

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

作为noted in the comments,它施加了一个可能不必要的限制:对话框也可能属于当前进程。我只留下了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);
    }
}

【讨论】:

  • 我试过了,它在我这边工作得很好,没有延迟。点赞!
  • 嗯,现在重新考虑我的编辑,我不确定您的意图(甚至是 OP 的意图)是否实际捕获属于当前进程或其他进程的窗口。请检查修改,如果它不反映您的初衷,请随时回滚。
  • @Ahmed Abdelhameed 谢谢。这可能是不必要的/限制性检查。我编辑了代码并留下了一个注释。
  • @Jimi 我一直很忙,所以我有一段时间无法回到这个问题上。看来这现在正在工作。谢谢。我确实不得不稍微调整一下,因为我不想在处理程序关闭消息框后立即删除它。我想在其他地方处理。要回答有关当前进程的问题,不,我不是在寻找来自其他进程的消息。
  • 当然。移除处理程序并不是绝对必要的。在这种情况下,这只是一个示例(还发布以指出删除这些处理程序是一项要求)。请注意,还有一个WindowPattern.WindowClosedEvent,可用于通知进程何时关闭窗口。
【解决方案2】:

似乎当 MessageBox 有一个图标时,FindWindowEx 返回第一个孩子的文本(在这种情况下是图标),因此长度为零。现在,在this answer 的帮助下,我产生了迭代孩子们的想法,直到找到一个带有文本的孩子。这应该有效:

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

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

【讨论】:

  • 那行得通。我非常接近。我想通过 IntPtr.Zero + IntPtr.Size 认为它可能会将位置移动一个(跳过图标)。无论如何,它现在正在工作。谢谢
  • 您从哪里获得“窗口搜索”实用程序?那会很有帮助的。
  • @James 这是名为Spy++ 的实用程序的一部分。
猜你喜欢
  • 1970-01-01
  • 2014-08-07
  • 1970-01-01
  • 1970-01-01
  • 2012-10-28
  • 1970-01-01
  • 1970-01-01
  • 2019-07-20
  • 2017-09-07
相关资源
最近更新 更多