GetWindowRect too small on Windows 7
Asked Answered
C

6

15

The actual problem I'm trying to solve is, I want to automatically find out the size of the margins around windows. If you can find a better way, please by all means answer that instead of this.

To do this I decided to take a screenshot of a test window and measure the margins. This is simple enough, as I expect no margins will ever be bright pink, but I admit it's a hack. I use GetWindowRect (py) to get the bounding box, and PIL to grab a screenshot and crop to the bounding box. The problem is that while the crop operates correctly, the bounding box is not accurate. The Windows 7 "Snipping Tool" gets the correct dimensions. How may I do the same?

Cathern answered 7/7, 2010 at 5:53 Comment(0)
J
19

My first thoughts were listed below but if, as you state, you're certain that GetWindowRect is returning incorrect values, see RESOLUTION further down.


"What's wrong with GetSystemMetrics(SM_CXBORDER) and GetSystemMetrics(SM_CYBORDER)?

The method you're using seems a very roundabout way of doing it and, if you can call GetWindowRect(), I'm pretty certain you can call GetSystemMetrics() as well.

One other possibility is to use GetWindowRect to get the entire bounding rectangle for the window and GetClientRect to get the bounding rectangle for the client (non-border) area.

This should give you something like (100,200),(1000,900) and (112,227),(988,888) respectively and you can work out the top border as 227-200, bottom as 900-888, left as 112-100 and right as 900-888 (27,12,12,12).


RESOLUTION:

A bit of investigation turns up this. It's a thread from 2006 stating that you might not get the correct values from GetWindowsRect. The thread that pointed me to this stated:

Apps under Vista that are not linked with WINVER=6 will receive a misleading set of values here, that do not account for the extra padding of "glass" pixels Vista Aero applies to the window. This appears to happen even in Aero Basic (without Glass) to retain sizing consistency. The workaround (if you don't want to set WINVER=6) seems to be to dynamically bind to dwmapi.dll and use GetProcAddress() to obtain the DwmGetWindowAttribute() function, and call it with the DWMWA_EXTENDED_FRAME_BOUNDS argument to request the genuine window frame dimensions.

So basically, use something like (you may have to use ctypes to do this from Python):

RECT r;
HRESULT stat = DwmGetWindowAttribute (
    hwnd,
    DWMWA_EXTENDED_FRAME_BOUNDS,
    &r,
    sizeof(r));

and that should give you the correct bounding rectangle.

Jerrilyn answered 7/7, 2010 at 6:0 Comment(10)
Well, it returns 1. 1 is not the width of any of the borders on that window. I'm pretty sure the values I want for that window are 8 and 30.Cathern
How do you get 8 and 30? The correct looking frame seems to have the same width on the bottom as it does on the sides.Jerrilyn
So you're saying there are 30 pixels above the client area and 8 on the bottom and sides?Jerrilyn
The second image illustrates the border of the window and the content (pink) perfectly. By my measurements, the edge between the end of the window and the end of the center content is 8 pixels on the left, right, and bottom, and 30 pixels at the top.Cathern
I would actually print out the information you get back from GetWindowRect then use a tool like HyperSnap to work out if the co-ordinates are correct (we use that at work and it allows to to easily find the corners). It seems to me that either GetWindowsRect is lying (unlikely) or ImageGrab.grab(bbox) is deficient somehow. Personally, I'd be betting on the latter. Once you've established where the problem lies, we can think about alternatives (it's a pity the snipping tool doesn't have a cmdline interface to just grab the current window).Jerrilyn
One of the possible alternatives is to use GetWindowRect to get the whole thing then use GetClientRec to get the client area. You should be able to work out the four borders from that on a per Window basis.Jerrilyn
I already established which was lying, and it's GetWindowsRect. When I saw the outcome of the crop, I tested it myself manually. This is the origin of my statement, "the crop operates correctly".Cathern
To clarify: the dimensions returned by GetWindowsRect are a window that is 306 wide and 328 tall (e.g. (175, 175, 481, 503)). This matches perfectly with the crop (and is even positioned correctly), but is smaller than the "Snipping Tool" crop (and cuts off the corners etc.)Cathern
@Devin, see my final update. It may be that PIL is not linked with WINVER=6 and thus may be getting the incorrect values. It's worth testing the proposed solution to see if it gives you the correct values.Jerrilyn
Sorry it took so long to check up, I was procrastinating on actually testing because ctypes is a pain. It works perfectly!Cathern
G
5

I know it is a bit old topic. But it took quite a bit of searching and I went through the ctypes pain myself to get paxdiablo's solution working in Python. Just wanted to share working code sample for wxPython:

try:
    f = ctypes.windll.dwmapi.DwmGetWindowAttribute
except WindowsError:
    f = None
if f: # Vista & 7 stuff
    rect = ctypes.wintypes.RECT()
    DWMWA_EXTENDED_FRAME_BOUNDS = 9
    f(ctypes.wintypes.HWND(self.GetHandle()),
      ctypes.wintypes.DWORD(DWMWA_EXTENDED_FRAME_BOUNDS),
      ctypes.byref(rect),
      ctypes.sizeof(rect)
      )
    size = (rect.right - rect.left, rect.bottom - rect.top)        
else:      
    size = self.GetSize()
Gastrocnemius answered 19/11, 2012 at 18:28 Comment(0)
H
2

GetWindowRect on Windows 7 appears to not include the right and bottom window frame edges (at least with the Aero theme afaik), if the window was created without the WS_SIZEBOX style (i.e, you want a non-sizable window).

The problem is, WS_SIZEBOX is the same as WS_THICKFRAME, and on Aero, windows have the thickframe whether they can be resized or not. But the GetWindowRect function thinks that a non-resizable window is thinner.

The fix? You can create the window with WS_SIZEBOX, call GetWindowRect, then use SetWindowLongPtr(GWL_STYLE, ...) to turn off WS_SIZEBOX, but this will create an ugly white border inside the client area.

Instead, leave WS_SIZEBOX enabled, and simply return the same value for ptMinTrackSize and ptMaxTraceSize in the MINMAXINFO structure when responding to the WM_GETMINMAXINFO message. This will keep the window from being resizable and GetWindowRect will return proper data. The only downside is that the mouse cursor will still change to a resizing cursor when the mouse pointer goes over the window frame, but it's the lesser of the evils so far.

Husbandry answered 17/11, 2013 at 5:48 Comment(0)
R
1

DwmGetWindowAttribute

http://msdn.microsoft.com/en-us/library/aa969515%28VS.85%29.aspx

Rosinarosinante answered 7/7, 2010 at 6:11 Comment(0)
G
1

first, you call GetClientRect to retrive the client rectangle R1, then call AdjustWindowRectEx to calculate the accurate bounds according to R1.

Gigigigli answered 7/7, 2010 at 7:54 Comment(0)
F
0

GetWindowRect return correct values, but for explicit handle to the window. Use GetParent function to get handle of the parent window, while GetWindoWRect return for you maximum RECT or GetParent return value is NULL.

Fidelis answered 20/8, 2012 at 6:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.