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.

An example of unexpected values when asking for window boundaries
An example of unexpected values when asking for window boundaries

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.

Drop shadows appear around windows in Windows 10
Drop shadows appear around windows in Windows 10

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.

csharp
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.

csharp
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.

csharp
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

Like what you're reading? Perhaps you like to buy us a coffee?

Donate via Buy Me a Coffee

Donate via PayPal


Comments

# nihaal

what abt same issue in python? any idea?

Reply

# 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.html

Regards;
Richard Moss

Reply