我可以在另一个应用程序进程中获取任意窗口的位图吗?

时间:2010-05-14 16:08:36

标签: gdi+ sendmessage microsoft-ui-automation

我正在尝试自动化第三方Win32应用程序,我希望以定义的时间间隔捕获特定窗口的图形内容。我处于早期阶段,我目前正在尝试通过C#使用Microsoft UI Automation API来完成客户端应用和外部应用之间的大部分互动。我现在可以让外部应用程序执行我想要它做的事情,但现在我想从特定窗口捕获图形,这似乎是一些第三方所有者绘制的控件。我怎样才能做到这一点?我要捕获的窗口是此图像中红色矩形标记的窗口:

I need what's in the red rectangle http://img168.imageshack.us/img168/8169/5142010110014am.png

我有一种实现方式,但它依赖于外部应用程序的UI位于顶部,而且这对我来说无法保证,所以我更愿意找到更通用的内容。

var p = Process.Start("c:\myapp.exe");
var mainForm = AutomationElement.FromHandle(p.MainWindowHandle);
// "workspace" below is the window whose content I want to capture.
var workspace = mainForm.FindFirst(TreeScope.Descendents,
                    new PropertyCondition(AutomationElement.ClassNameProperty, "AfxFrameOrView70u"));
var rect = (Rect) workspace.GetCurrentPropertyValue(AutomationElement.BoundingRectangleProperty);
using (var bmp = new Bitmap((int)rect.Width, (int)rect.Height))
{
    using (var g = Graphics.FromImage(bmp))
    {
        g.CopyFromScreen((int)rect.Left, (int)rect.Top, 0, 0, new Size((int)rect.Width, (int)rect.Height));
        bmp.Save(@"c:\screenshot.png", ImageFormat.Png);
    }
}

当自动应用程序位于顶部时,上面的效果非常好,但它只是盲目地复制矩形中的屏幕,所以我的代码完全可以在机器上运行,并可能覆盖我的应用程序窗口。

我已经阅读了一些将WM_PRINT消息发送到窗口的建议。几个月前的这个question/answer看起来很有希望,但是当我使用这个代码时,我只得到一个没有我控件实际内容的白色矩形。

var prop = (int)workspace.GetCurrentPropertyValue(AutomationElement.NativeWindowHandleProperty);
var hwnd = new IntPtr(prop);
using ( var bmp2 = new Bitmap((int)rect.Width, (int)rect.Height))
{
    using (Graphics g = Graphics.FromImage(bmp2))
    {
        g.FillRectangle(SystemBrushes.Control, 0, 0, (int)rect.Width, (int)rect.Height);
        try
        {
            SendMessage(hwnd, WM_PRINT, g.GetHdc().ToInt32(), (int)(DrawingOptions.PRF_CHILDREN | DrawingOptions.PRF_CLIENT | DrawingOptions.PRF_OWNED));
        }
        finally
        {
            g.ReleaseHdc();
        }
        bmp2.Save(@"c:\screenshot.bmp");
    }
}

那么,首先,我是否可以可靠地保存窗口内容的位图?如果是,那么WM_PRINT SendMessage {{1}}尝试的最佳方式是什么,以及出了什么问题?

2 个答案:

答案 0 :(得分:4)

对pinvoke.net网站上PrintWindow API example的这种修改似乎已经成功了。

Bitmap bmp = new Bitmap((int)rect.Width, (int)rect.Height);
Graphics memoryGraphics = Graphics.FromImage(bmp);
IntPtr dc = memoryGraphics.GetHdc();
bool success = PrintWindow(hwnd, dc, 0);
memoryGraphics.ReleaseHdc(dc);
bmp.Save(@"c:\screenshot.bmp");

如果应用程序被另一个窗口覆盖,则此方法有效,但如果应用程序已最小化,则无效。我想我可以忍受。

答案 1 :(得分:1)

如果该应用不在顶部,则没有可靠的方式从其他应用获取位图。这是因为如果应用程序不可见,应用程序的控件甚至不会呈现,并且Windows不一定记得控件的最后已知内容在该控件失去z顺序中的最高位置之后。

您最好的选择是在您需要截取屏幕截图时将目标应用程序移动到z顺序的前面,然后在捕获图像后选择性地恢复原始z顺序。