18

I'm trying to capture desktop windows in C# based on Window handles. I'm using .NET and using PInvoke to GetWindowRect() to capture the window rectangle. I've got the Window selection and rectangle capture working fine.

However, the window rectangles captured include not just the actual window size but also the Window's adornments like the shadow around it. When I try to clip the window to a bitmap the bitmap contains the area and shadow. On Windows 10 I get the transparent shadow area including any content that might be visible below the active window:

enter image description here

The code I use is simple enough capturing the Window using Win32 GetWindowRect() via PInvoke call:

var rect = new Rect();
GetWindowRect(handle, ref rect);
var bounds = new Rectangle(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top);
var result = new Bitmap(bounds.Width, bounds.Height);

using (var graphics = Graphics.FromImage(result))
{
    graphics.CopyFromScreen(new Point(bounds.Left, bounds.Top), Point.Empty, bounds.Size);
}

return result;

I then capture the image and assign it into a picture box.

In addition it looks like there's some variation between windows - some windows have shadows others do not. Most do, but some like Visual Studio and Chrome do not, so it's not even a simple matter of stripping out the extraneous pixels.

I've tried using GetClientRect() but that gets me just the client area which is not what I've after. What I'd like to get is the actual Window rectangle with borders but without the shadows.

Is there anyway to do this?

Rick Strahl
  • 17,302
  • 14
  • 89
  • 134
  • You may have to check out [SystemInformation](https://msdn.microsoft.com/en-us/library/system.windows.forms.systeminformation.aspx) in order to get a consistent border size, add that on to the client area. – t0mm13b Aug 22 '15 at 19:16
  • Rather than copying from the screen, you should copy from the window's DC (`GetWindowDC`) - this will get the window even if it's covered by another one. Also, see the 3rd comment [on this page](https://msdn.microsoft.com/en-us/library/windows/desktop/ms633519.aspx). – Lucas Trzesniewski Aug 22 '15 at 19:19
  • See this [question](http://stackoverflow.com/questions/16493698/drop-shadow-on-a-borderless-winform) temporarily modify the windows create params to remove the shadow by inverting the `CS_DROPSHADOW` flag, capture the window, and revert the mask. – t0mm13b Aug 22 '15 at 19:20
  • @LucasTrzesniewski I tried using the DC. There are other problems with that - namely I get funky capture behavior on some owner drawn applications (like Chrome). It still gets the borders. – Rick Strahl Aug 22 '15 at 22:25
  • 2
    Try `DwmGetWindowAttribute` [DwmGetWindowAttribute function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa969515(v=vs.85).aspx) with `DWMWA_EXTENDED_FRAME_BOUNDS` attribute. – γηράσκω δ' αεί πολλά διδασκόμε Aug 23 '15 at 01:05
  • Tried DwmGetWindowAttribute - results are the same. Still get the shadow. – Rick Strahl Aug 23 '15 at 01:51
  • `DwmGetWindowAttribute` is the solution. It should give a rectangle which is smaller than the rectangle from GetWindowRect. – Barmak Shemirani Aug 24 '15 at 17:22
  • @BarmakShemirani I get exactly the same dimensions with either and they include the shadow. This is on Windows 10 BTW so it's possible something's changed there. – Rick Strahl Aug 30 '15 at 18:20

2 Answers2

10

You can use DwmGetWindowAttribute. See following sample. You can use GetWindowRectangle with any handle to get its actual size.

[DllImport("dwmapi.dll")]
public static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out RECT pvAttribute, int cbAttribute);

    public struct RECT
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }

    [Flags]
    private enum DwmWindowAttribute : uint
    {
        DWMWA_NCRENDERING_ENABLED = 1,
        DWMWA_NCRENDERING_POLICY,
        DWMWA_TRANSITIONS_FORCEDISABLED,
        DWMWA_ALLOW_NCPAINT,
        DWMWA_CAPTION_BUTTON_BOUNDS,
        DWMWA_NONCLIENT_RTL_LAYOUT,
        DWMWA_FORCE_ICONIC_REPRESENTATION,
        DWMWA_FLIP3D_POLICY,
        DWMWA_EXTENDED_FRAME_BOUNDS,
        DWMWA_HAS_ICONIC_BITMAP,
        DWMWA_DISALLOW_PEEK,
        DWMWA_EXCLUDED_FROM_PEEK,
        DWMWA_CLOAK,
        DWMWA_CLOAKED,
        DWMWA_FREEZE_REPRESENTATION,
        DWMWA_LAST
    }

    public static RECT GetWindowRectangle(IntPtr hWnd)
    {
        RECT rect;

        int size = Marshal.SizeOf(typeof(RECT));
        DwmGetWindowAttribute(hWnd, (int)DwmWindowAttribute.DWMWA_EXTENDED_FRAME_BOUNDS, out rect, size);

        return rect;
    }
Riz
  • 6,746
  • 16
  • 67
  • 89
7

I'm running Windows 10 and came across the same issue when writing an app to snap windows to the top or bottom of the screen. I've found DwmGetWindowAttribute() to work. It returns a RECT with slightly different values than GetWindowRect()...

Results from a sample window:

GetWindowRect(): {X=88,Y=26,Width=871,Height=363}

DwmGetWindowAttribute(): {X=95,Y=26,Width=857,Height=356}

My testing showed that GetWindowRect() included the adornments, while DwmGetWindowAttribute() did not.

If you're getting identical results from both methods on a window with adornments, it might be that that particular window is drawing its own adornments, or that there is some other attribute or property set to the window that needs to be taken into account.

dynamichael
  • 807
  • 9
  • 9
  • Was this the Extended Frame Bounds you retrieved from DwmGetWindowAttribute? – NetMage Mar 04 '16 at 00:06
  • DwmGetWindowAttribute() gave me the actual border, whereas GetWindowRect() included the translucent "fuzzy" shadow around the border. – dynamichael Feb 10 '18 at 06:24
  • 2
    `DwmGetWindowAttribute(handle, DWMWINDOWATTRIBUTE.ExtendedFrameBounds, out var rect, Marshal.SizeOf());` worked for me on Win10. – Johannes Egger Jun 24 '19 at 12:34
  • Reference https://chromium.googlesource.com/external/webrtc/+/master/modules/desktop_capture, I have two questions here: 1. DwmGetWindowAttribute function may fail in win7 or some machines; 2. When the target window is maximized, the area obtained by DwmGetWindowAttribute or GetCroppedWindowRect still has a black border – fredirty2017 Oct 20 '20 at 10:19