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.

About The Author

Gravatar

The founder of Cyotek, Richard enjoys creating new blog content for the site. Much more though, he likes to develop programs, and can often found writing reams of code. A long term gamer, he has aspirations in one day creating an epic video game. Until that time, he is mostly content with adding new bugs to WebCopy and the other Cyotek products.

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?

Styling with Markdown is supported