How to get the text of a MessageBox when it has an icon?
It appears that when the MessageBox has an icon, FindWindowEx
returns the text of the first child (which is the icon in this case) hence, the zero length. Now, with the help of this answer, I got the idea to iterate the children until finding one with a text. This should work:
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);
}
Obviously, you could adjust it to fit your particular situation (e.g., keep iterating until you find the actual text you're looking for) although I think the child with the text will probably always be the second one:
This is a UI Automation method that can detect a Window Opened event anywhere in the System, identify the Window using the Text of one its child elements and close the Window upon positive identification.
The detection is initialized using Automation.AddAutomationEventHandler with WindowPattern.WindowOpenedEvent and Automation Element argument set to AutomationElement.RootElement, which, having no other ancestors, identifies the whole Desktop (any Window).
The WindowWatcher
class exposes a public method (WatchWindowBySubElementText
) that allows to specify the Text contained in one of the sub elements of a Window that just opened. If the specified Text is found, the method closes the Window and notifies the operation using a custom event handler that a subscriber can use to determine that the watched Window has been detected and closed.
Sample usage, using the Text string as provided in the question:
WindowWatcher watcher = new WindowWatcher();
watcher.ElementFound += (obj, evt) => { MessageBox.Show("Found and Closed!"); };
watcher.WatchWindowBySubElementText("Original message");
WindowWatcher
class:
This class requires a Project Reference to these assemblies:
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.
EDIT:
Changed the condition that doesn't consider a Window created in the current Process:
if (element is null || element.Current.ProcessId != Process.GetCurrentProcess().Id)
As Ahmed Abdelhameed noted in the comments, it imposes a limitation that is probably not necessary: the Dialog could also belong to the current Process. I left there just the null
check.
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;
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();
this.OnElementFound(new EventArgs());
}
}
catch (ElementNotAvailableException) {
// Ignore: this exception may be raised when a modal dialog owned
// by the current process is shown, blocking the code execution.
// When the dialog is closed, the AutomationElement may no longer be available
}
});
public void OnElementFound(EventArgs e)
{
Automation.RemoveAllEventHandlers();
ElementFound?.Invoke(this, e);
}
}