In my last article, I describe how to use the Win32 API to capture screenshots of the desktop. There was one frustrating problem with this however - when capturing an image based on the value of the Bounds
property of a Form
unexpected values were returned for the left position, width and height of the window, causing my screenshots to be too big.
I thought that was odd but as I wanted to be able to capture unmanaged windows in future then using Form.Bounds
wasn't going to be possible anyway and I would have to use GetWindowRect
. I'm sure that deep down in the Windows Forms code base it uses the same API so I was expecting to get the same "wrong" results, and I wasn't disappointed.
Although I'm calling these values "wrong", technically they are correct - here's another example this time using a plain white background.
As you can see, Windows 10 has a subtle drop shadow affect around three edges of a window, and it seems that is classed as being part of the window. This was surprising to me as I would assumed that it wouldn't be included being part of the OS theme rather than the developers deliberate choice.
Windows has the very handy hotkey Alt+Print Screen which will capture a screenshot of the active window and place it on the Clipboard. I've used this hotkey for untold years and it never includes a drop shadow, so clearly there's a way of excluding it. Some quick searching later reveals an answer - the DwmGetWindowAttribute
function. This was introduced in Windows Vista and allows you to retrieve various extended aspects of a window, similar I think to GetWindowLong
.
DWM stands for Desktop Window Manager and is the way that windows have been rendered since Vista, replacing the old GDI system.
There's a DWMWINDOWATTRIBUTE
enumeration which lists the various supported attributes, but the one we need is DWMWA_EXTENDED_FRAME_BOUNDS
. Using this attribute will return what I consider the window boundaries without the shadow.
const int DWMWA_EXTENDED_FRAME_BOUNDS = 9; [DllImport("dwmapi.dll")] static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out RECT pvAttribute, int cbAttribute);
Calling it is a little bit more complicated that some other API's. The pvAttribute
argument is a pointer to a value - and it can be of a number of different types. For this reason, the cbAttribute
value must be filled in with the size of the value in bytes. This is a fairly common technique in Win32, although I'm more used to seeing cbSize
as a member of a struct
, not as a parameter on the call itself. Fortunately, we don't have to work this out manually as the Marshal
class provides a SizeOf
method we can use.
For sanities sake, I will also check the result code, and if it's not 0
(S_OK
) then I'll fall back to GetWindowRect
.
if (DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, out region, Marshal.SizeOf(typeof(RECT))) != 0) { NativeMethods.GetWindowRect(hWnd, out region); }
Now I have a RECT
structure that describes what I consider to be the window boundaries.
A note on Windows versions
As the DwmGetWindowAttribute
API was introduced in Windows Vista, if you want this code to work in Windows XP you'll need to check the current version of Windows. The easiest way is using Environment.OsVersion
.
public Bitmap CaptureWindow(IntPtr hWnd) { RECT region; if (Environment.OSVersion.Version.Major < 6) { GetWindowRect(hWnd, out region); } else { if (DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, out region, Marshal.SizeOf(typeof(RECT))) != 0) { GetWindowRect(hWnd, out region); } } return this.CaptureRegion(Rectangle.FromLTRB(region.teft, region.top, region.bight, region.bottom)); }
Although it should have no impact in this example, newer versions of Windows will lie to you about the version unless your application explicitly states that it is supported by the current Windows version, via an application manifest. This is another topic out of the scope of this particular article, but they are useful for a number of different cases.
Sample code
There's no explicit download to go with this article as it is all part of the Simple Screenshot Capture source code in the previous article.
All content Copyright (c) by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is https://www.cyotek.com/blog/getting-a-window-rectangle-without-the-drop-shadow?source=rss.