【问题标题】:Getting the text from a dialog box that does not use a label control?从不使用标签控件的对话框中获取文本?
【发布时间】:2012-09-24 17:47:41
【问题描述】:

这是我之前的问题How to supress a dialog box an Inproc COM Server displays的延续。


背景:

回顾一下我的情况:我有一个来自第 3 方的用 Delphi 编写的 Inproc COM 服务器。如果捕获特定类型的错误,我调用的函数之一将显示错误消息对话框。问题是我正在尝试批量处理数据,而我正在使用的数据源导致该错误对话框弹出很多(感谢我之前的问题的答案,它现在自动关闭并且我能够运行它完成,它会显示对话框并要求有人按 OK 9923 次)。进程阻塞,直到消息框关闭。


问题:

我希望更好地记录错误对话框所说的内容。但是,任何获取对话框正文的尝试都失败了。

//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's 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();

我想我可能在对话框开始添加文本之前中断了对话框(当我中断上面的代码时,它还没有完全绘制)。但是,在开始枚举之前在 StartWindowListener 中放置 Application.DoEvents 可以让对话框完全绘制,但我仍然得到与使用上述代码发布的结果相同的结果。

在对话框上执行 Ctrl-C 可以正常工作,因此我可以在紧要关头使用它,但由于我必须重复这 9923 次,我想避免以编程方式使用它。

还有其他方法可以尝试从消息框中获取文本吗?

【问题讨论】:

  • 使用 System.Windows.Automation 命名空间。这种东西正是它的用途。
  • @RaymondChen 你能提供一个如何做到这一点的例子吗?我使用自动化命名空间所做的一切都要求您在 UISpy 或 Spy++ 中列出一个项目。但是,那些不报告标签控件的我可以附加并从中读取文本,所以我不知道该怎么做。我将使用 UI Spy 的输出更新问题。
  • 我无法为您的特定程序提供示例,因为我没有您的程序。但是AutomationElement.RootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Warning")); 会让你进入对话框。然后寻找文本子项。
  • @RaymondChen 正如我在上一条评论和UISpy的截图中所说,没有Warning的文本子项,有两个子项“警告”、“确定”按钮和标题栏。标题栏有 2 个子项、一个菜单栏和关闭按钮。菜单栏有一个子项,一个菜单项“系统”(如果您右键单击,菜单会在其中移动和关闭)。就是这样,UISpy 中警告的 5 个后裔,如果我将查询结果保存为 parent.FindAll(TreeScope.Descendants, PropertyCondition.TrueCondition),则相同的 5 个 var parent = AutomationElement.RootElement.FindF...
  • 不幸的是,“TMessageForm”在所谓的“TLabel”中显示消息文本,它是一个非窗口控件。基本上,您看到的是使用“DrawText”或“DrawThemeTextEx”在父窗口上绘制的文本。

标签: c# delphi winapi messagebox


【解决方案1】:

感谢Sertac's comment 我发现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);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-19
    • 1970-01-01
    • 2011-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多