Получение текста из диалогового окна, в котором не используется элемент управления меткой?
Это продолжение моего предыдущего вопроса Как подавить диалоговое окно, отображаемое на экране COM-сервера Inproc.
История:
Резюме моей ситуации: у меня есть COM-сервер Inproc, написанный в Delphi от третьего лица. Одна из функций, которые я вызываю, отображает диалоговое окно с сообщением об ошибке, если оно ловушки определенного типа ошибки. Проблема в том, что я пытаюсь обрабатывать данные навалом, а источник данных, который я использую, вызывает появление диалогового окна с ошибкой (благодаря ответу на мой предыдущий вопрос он автоматически закрывается, и я смог запустить его завершение, оно отобразило бы диалоговое окно и потребовало, чтобы кто-то нажал OK 9923 раза). Процесс блокируется до закрытия окна сообщения.
Вопрос:
Я хотел бы лучше узнать, что сказал диалог об ошибке. Однако любая попытка получить текстовый текст диалогового окна не удалась.
![Image of Dialog Box]()
//Snip
private void StartWindowListener()
{
//Queue the watcher on the message pump if we are not watching.
if (_watcherRunning == false)
{
_watcherRunning = true;
_dummyForm.BeginInvoke(new Action(() =>
{
_watcherRunning = false;
//If we are not inside the com object don't enumerate.
if (_insideCom == false) return;
// Enumerate windows to find dialogs
EnumThreadWndProc callback = new EnumThreadWndProc(CheckWindow);
EnumThreadWindows(GetCurrentThreadId(), callback, IntPtr.Zero);
GC.KeepAlive(callback);
}));
}
}
private bool CheckWindow(IntPtr hWnd, IntPtr lp)
{
// Checks if hWnd is the expected dialog
StringBuilder sb = new StringBuilder(260);
GetClassName(hWnd, sb, sb.Capacity);
if (sb.ToString() == "TMessageForm")
{
//This returns the dialog box title
GetWindowText(hWnd, sb, sb.Capacity);
//This returns IntPtr.Zero
var hDialogText = GetDlgItem(hWnd, 0xFFFF);
if (hDialogText != IntPtr.Zero)
GetWindowText(hDialogText, sb, sb.Capacity);
//This returns a empty string
GetDlgItemText(hWnd, 0xFFFF, sb, sb.Capacity);
//Only sees the OK button.
IntPtr hCtl = IntPtr.Zero;
HashSet<IntPtr> seen = new HashSet<IntPtr>();
while ((hCtl = GetNextDlgGroupItem(hWnd, hCtl, false)) != IntPtr.Zero)
{
//When we see the same control twice, break out of the loop.
if (seen.Add(hCtl) == false)
break;
GetClassName(hCtl, sb, sb.Capacity);
SendMessage(hCtl, WM_GETTEXT, sb.Capacity, sb)
//Close the dialog by sending WM_CLOSE to the window
SendMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}
//Snip...
}
return true;
}
//Snip...
// P/Invoke declarations
const int WM_CLOSE = 0x0010;
private delegate bool EnumThreadWndProc(IntPtr hWnd, IntPtr lp);
[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(int tid, EnumThreadWndProc callback, IntPtr lp);
[DllImport("user32.dll")]
private static extern int GetClassName(IntPtr hWnd, StringBuilder buffer, int buflen);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
[DllImport("kernel32.dll")]
private static extern int GetCurrentThreadId();
Я думал, что, возможно, я прервал диалог, прежде чем он добавит текст к нему (он еще не полностью окрашен, когда я разбиваю код выше). Однако, ставя Application.DoEvents
внутри StartWindowListener до того, как он запустится, перечисление позволяет полностью рисовать диалоговое окно, но я все равно получаю те же результаты, что и я, с указанным выше кодом.
Выполнение Ctrl-C работает корректно в диалоговом окне, поэтому я мог бы использовать это в крайнем случае, но, будучи тем, что я должен повторять этот 9923 раз, я хотел бы избежать использования этого программного обеспечения.
Есть ли какие-либо другие методы, которые я могу попытаться получить в текстовом поле?
Ответы
Ответ 1
Благодаря комментарий Sertac Я узнал, что текст в сообщениях Delphi не является объектами окна, они нарисованы с помощью методов DrawText. Я использовал EasyHook, чтобы перехватить вызовы Windows API, и теперь я могу захватить текст, который меня волнует.
////It appears that DrawText always calls DrawTextEx so it is getting intercepted twice.
//// Only need to hook DrawTextEx
static EasyHook.LocalHook _drawTextExAHook;
//Snip...
public override void Run()
{
//Snip...
IntPtr drawTextExAPtr = EasyHook.LocalHook.GetProcAddress("user32", "DrawTextExA");
_drawTextExAHook = EasyHook.LocalHook.Create(drawTextExAPtr, new DrawTextExDelegate(DrawTextEx_Hooked), null);
//The COM stuff must be run in a STA Thread so we can intercept the message boxes that it throws up.
var staThread = new Thread(() =>
{
try
{
var threadID = new[] { GetCurrentThreadId() };
//Enable the hook on the current thread.
_drawTextExAHook.ThreadACL.SetInclusiveACL(threadID);
//Tell the dummy form to start ComThread
_dummyForm = new DummyForm(ComThread);
Application.Run(_dummyForm);
}
finally
{
if(_drawTextExAHook != null)
_drawTextExAHook.Dispose();
}
});
staThread.SetApartmentState(ApartmentState.STA);
staThread.Name = "Com Thread";
staThread.Start();
//Wait for the Com Thread to finish.
staThread.Join();
}
//Snip...
private delegate int DrawTextExDelegate(IntPtr hdc, string lpchText, int cchText,
ref Rect lprc, uint dwDTFormat, ref DRAWTEXTPARAMS lpDTParams);
private int DrawTextEx_Hooked(IntPtr hdc, string lpchText, int cchText, ref Rect lprc,
uint dwDTFormat, ref DRAWTEXTPARAMS lpDTParams)
{
LogErrorText(lpchText);
return DrawTextEx(hdc, lpchText, cchText, ref lprc, dwDTFormat, ref lpDTParams);
}
[DllImport("user32.dll")]
static extern int DrawTextEx(IntPtr hdc, string lpchText, int cchText,
ref Rect lprc, uint dwDTFormat, ref DRAWTEXTPARAMS lpDTParams);