Getting a window rectangle without the drop shadow
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.
Update History
- 2017-08-27 - First published
- 2020-11-22 - Updated formatting
Leave a Comment
While we appreciate comments from our users, please follow our posting guidelines. Have you tried the Cyotek Forums for support from Cyotek and the community?
Comments
nihaal
#
what abt same issue in python? any idea?
Richard Moss
#
Sorry, I don't know. I've only dabbled with Python. A quick search suggests using the built in
ctypes
library for Win32 interop, you can view the documentation at https://docs.python.org/3/library/ctypes.htmlRegards;
Richard Moss