views:

117

answers:

2

I am trying to automate a third-party Win32 application where I want to capture the graphics content of a particular window at defined time intervals. I am in the early phases of this, and I'm currently trying to use the Microsoft UI Automation API via C# to do most of the interaction between my client app and the external app. I can now get the external app to do what I want it to do, but now I want to capture the graphics from a specific window that seems to be some third-party owner-drawn control. How can I do this? The window I want to capture is the one marked by the red rectangle in this image:

I need what's in the red rectangle

I have an implementation that sort of works, but it's dependent on the external app's UI being on top, and that's not guaranteed for me, so I'd prefer to find something more general.

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

The above works well enough when the automated app is on top, but it just blindly copies the screen in the rectangle, so my code is at the mercy of whatever happens to be running on the machine and might cover my app's window.

I have read some suggestions to send the WM_PRINT message to the window. This question/answer from a few months back seemed promising, but when I use this code, I just get a white rectangle with none of my control's actual contents.

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

So, first, is it even possible for me to reliably save a bitmap of a window's contents? If so, what is the best way, and what is wrong with my WM_PRINT with SendMessage attempt?

A: 

There is no reliable way to get a bitmap from a different app if that app is not on top. That's because the app's controls do not even render if the app isn't visible, and Windows does not necessarily even remember what the last known contents of the control were after that control loses the topmost position in the z-order.

Your best bet is to move the target app to the front of the z-order at the time that you need to take the screenshot, and then optionally restore the original z-order after capturing the image.

JSBangs
+1  A: 

This modification of the PrintWindow API example on the pinvoke.net site seems to have done the trick.

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

This works if the app is covered by another window, but it doesn't work if the app is minimized. I think I can live with that.

Chris Farmer