When capturing a window manually with the Print Screen
+Alt
key combo, I get the following:
but if I try to do it programmatically using Windows API, I get this:
Why the discrepancy? How do I get the first programmatically?
Here is my code:
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool PrintWindow(IntPtr hWnd, IntPtr hdcBlt, int nFlags);
public Bitmap PrintWindow()
{
Bitmap bmp = new Bitmap(windowRect.Width, windowRect.Height, PixelFormat.Format32bppArgb);
Graphics gfxBmp = Graphics.FromImage(bmp);
IntPtr hdcBitmap = gfxBmp.GetHdc();
bool success = PrintWindow(windowHandle, hdcBitmap, 0);
gfxBmp.ReleaseHdc(hdcBitmap);
if (!success)
{
Console.WriteLine("Error copying image");
Console.WriteLine(getLastError());
}
gfxBmp.Dispose();
return bmp;
}
Update: Doing it with BitBlt does the same thing.
Here's code from CodeProject that still returns a black-masked image:
public Image CaptureWindow(IntPtr handle)
{
// get te hDC of the target window
IntPtr hdcSrc = User32.GetWindowDC(handle);
// get the size
User32.RECT windowRect = new User32.RECT();
User32.GetWindowRect(handle,ref windowRect);
int width = windowRect.right - windowRect.left;
int height = windowRect.bottom - windowRect.top;
// create a device context we can copy to
IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc);
// create a bitmap we can copy it to,
// using GetDeviceCaps to get the width/height
IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc,width,height);
// select the bitmap object
IntPtr hOld = GDI32.SelectObject(hdcDest,hBitmap);
// bitblt over
GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, CopyPixelOperation.SourceCopy | CopyPixelOperation.CaptureBlt);
// restore selection
GDI32.SelectObject(hdcDest,hOld);
// clean up
GDI32.DeleteDC(hdcDest);
User32.ReleaseDC(handle,hdcSrc);
// get a .NET image object for it
Image img = Image.FromHbitmap(hBitmap);
// free up the Bitmap object
GDI32.DeleteObject(hBitmap);
img.Save("SampleImage.png");
return img;
}
I have tried many combinations of CopyPixelOperation
, (somewhere around 15,000 of the 131,000) but it still doesn't work.
Using Windows 8, AMD Radeon HD 6870.
Update 2
It seems that the window is transparent, allowing the blue color of the window to bleed through. When I change the window color to black (using the Windows personalization dialog), I get roughly something similar to the second window. The border is still missing though.
I haven't found a solution, but it's insight into the problem.
The reason the Alt+PrntScrn
image looks different is because it's not actually taking a snapshot of the selected window - it's taking a snapshot of something like the desktop window, but clipping out the appropriate part.
To prove this, take an always-on-top window like the Task Manager and position it so it's overlaying the window that you're snapshotting. You'll see it actually includes both windows in the snapshot. Whereas a call to PrintWindow
will just return the specified window alone.
So, if you want to emulate the Alt+PrntScrn
exactly, you need to BitBlt from the desktop. Something like this:
IntPtr hDesktop = User32.GetDesktopWindow();
IntPtr hdcSrc = User32.GetWindowDC(hDesktop);
GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, windowRect.left, windowRect.top, CopyPixelOperation.SourceCopy);
I'm kind of guessing at the syntax here based on your example code. The raw Windows API calls would look like this:
HWND hDesktop = GetDesktopWindow();
HDC hdcSrc = GetDC(hDesktop);
BitBlt(hdcDest, 0, 0, width, height, hdcSrc, windowRect.left, windowRect.top, SRCCOPY);
At least that works for me. If you have any problems figuring out the rest from here, let me know in the comments.
The reason that PrintWindow does not work is because it depends on the correct handling of the WM_PRINT message by the app. A lot of apps are flaky with WM_PRINT and don't implement it right or ever test it. It's therefore a bad idea to rely on it unless you use it only on known and tested apps.
If you want to grab the window as it shows on screen, just blt it from the Desktop window handle (GetDesktopWindow()) and only blt the rect that includes the window.
Transparency is an issue with window grabbing. It is impossible to capture modern Windows OS' windows, because there is no image file type that supports fanciness like blurring the underlying imagery. However, it's possible to capture simple transparency into a PNG file.
Be careful with assuming a window looks best as seen on screen. Depending on what's underneath, this may not be true. It may also be a bad idea to copy whatever's underneath as it may not be sanctioned to appear in the image, like an explicit background image appearing in a business presentation under a screenshot of Excel :).
If you blt from the desktop, you copy what you see on screen, including any overlaying windows and underlying windows (where transparent). It is usually regarded as "cleaner" to grab the window only, as with PrintWindow, but you may want to composite this on a background of your choice, like white or blue. If you want to blt from screen, there are ways to temporarily hide windows that overlay your target, but it's a bunch of work with EnumWindows and such.
Grabbing windows correctly is not an easy task in Windows and there are many screen-grab apps that compete with each other by virtue of how well they deal with exactly this issue. Also, in Windows there are several ways to make make areas of windows transparent, and they can be used together as well.
Also, in Vista+, there's the DWM thumbnail API which allows you to get a copy of an app window drawn on your window. For a demo and source, see ShareX (formerly zScreen).
Finally, you can look into the source of Open Broadcaster Software, which uses Direct3D to do some screen grabbing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With