The forgotten common controls: The GetEffectiveClientRect function


The GetEffectiveClientRect function is another one in the category of functions that everybody tries to pretend doesn't exist. It's not as bad as MenuHelp, but it's still pretty awful.

The idea behind the GetEffectiveClientRect function is that you have a frame window with a bunch of optional gadgets, such as a status bar or toolbar. The important thing is that these optional gadgets all reside at the borders of the window. In our examples, the toolbar goes at the top and the status bar goes at the bottom. You might also have gadgets on the left and right such as a navigation tree or a preview pane. They can also be stacked up against the border, such as an address bar and a toolbar. The important thing is that all the gadgets go around the border.

The first parameter to the GetEffectiveClientRect function is the window whose effective client rectangle you wish to compute; no surprises there. The second parameter is a pointer to the rectangle that receives the result; again, hardly surprising. It's that third parameter, the array of integers, that is the weird one.

The first two integers in the array are ignored. The remainder of the array consists of pairs of nonzero integers; the array is terminated by a pair consisting of zeroes. Of each pair, only the second integer is used; it is the control identifier of a child window of the window you passed in. If that child window is visible (in a special sense I'll explain later), then its window rectangle is subtracted from the parent window's client rectangle. After all the rectangles of visible children are subtracted away, what remains is the effective client rectangle.

For example, suppose your window's client rectangle is 100×100 and there is a toolbar at (0, 0)–(100, 20) and a status bar at (0, 90)–(100, 100), both visible. The GetEffectiveClientRect starts with the full client rectangle (0, 0)–(100, 100), subtracts the two rectangles corresponding to the toolbar and status bar, resulting in (0, 20)–(100, 90).

(0, 0) (100, 0)
toolbar
(0, 20) (100, 20)
effective client
(0, 90) (100, 90)
status bar
(0, 100) (100, 100)

If the control IDs for the toolbar and status bar are 100 and 101, respectively, then the array you need to pass would be { *, *, ¤, 100, ¤, 101, 0, 0 } where * can be anything and ¤ can be any nonzero value.

Continuing from the above example, if the status bar were hidden, then the effective client rectangle would be (0, 20)–(100, 100) because hidden windows are ignored when computing the effective client rectangle.

Okay, first question: What is that special sense of visible I mentioned above? I didn't write simply visible because IsWindowVisible reports a window as visible only if the window and all its parents are visible. But all that GetEffectiveClientRect cares about is whether the window is visible in the sense that the WS_VISIBLE style is set. In other words, that the window would be visible if its parent is.

Why does the GetEffectiveClientRect use this strange definition of visible? Because it wants to make it possible for you to get the effective client rectangle of a window while it is still hidden, the result being the effective client rectangle you would get once the window becomes visible. This is valuable because it allows you to do your calculations "behind the scenes" while the window is still hidden (for example, in your WM_CREATE handler).

Second question: Why is the integer array so crazy? What's with all the ignored values and the "must be nonzero" values? Why can't it just be the array { 100, 101, 0 }?

The format of the integer array is the same as the one used by the ShowHideMenuCtl function. The intent was that you could use the same array for both functions. The two functions do work well together: The ShowHideMenuCtl function do the work of letting the user toggle the toolbar and status bar on and off, and GetEffectiveClientRect lets you compute the client rectangle that results.

That said, the GetEffectiveClientRect function is largely ignored nowadays. It doesn't do anything you couldn't already do yourself, and when you write your own version, you don't need to deal with that crazy integer array.

Comments (10)
  1. yellow says:

    MenuHelp isn’t that bad, and if there’s nothing more to GetEffectiveClientRect then what you wrote, then I guess it’s not that bad either. Sure I could do those things myself, but IMHO there’s next to no advantage.

    Regedit uses MenuHelp, and it seems not to be bothered by any menu index problems. IMHO why would GetEffectiveClientRect be awful if it can be useful (only) when combined with ShowHideMenuCtl? These functions can accomplish fairly common tasks.

  2. Narcolepsy says:

    I suppose it might be allright.

    If you’ve got a group of bars, each of which sits in a tray, and contain controls/windows of their own, there’s almost no need to use it.

    The bigger task is working out;

    The size of the tray from a group of dynamic bar positions.

    If the floating bar is in a sensible location to be dropped onto the tray.

    If the bar is going to be dropped, it’s location in the tray.

    If the tray can be sized to fit a bar on the basis of a given client window.

    (Don’t mention activation)

    By the time that’s all done, working out why there’s additional padding seems almost trivial…. Crazy maybe….. In fact, what they hey, I already have that darn client rect, I’ll just grab it and use it.

  3. Ulric says:

    a truly obscure function with a documentation so spartan, it really doesn’t want to be used…

    I don’t understand this bit in your article:

    "The first two integers in the array are ignored." That’s not at all in the MSDN doc

  4. Cheong says:

    From MSDN article of ShowHideMenuCtl():

    The second value in the first pair must be the handle to the application’s main menu.

    If these two function are designed to work together, I’d think the first two parameters are hardly irrelevant. (It make sense to also subtract menu area from effective client area, doesn’t it?)

    On a second thought, Vista has changed so that menus are invisible until you press "Alt", perhaps that changes the "formula" for GetEffectiveClientRect()?

  5. Leo Davidson says:

    Choeng: That isn’t Vista so much as Explorer and IE7.

    If other programs want that behaviour then they have to be changed to have it, so APIs like the one in question are unlikely to have changed. (It would break a lot if they did.)

  6. overlord pebkac says:

    It would be a support nightmare too. Heck, I’d be puzzled for a while by the new behaviour myself.

    But if there are for instance 2 gadgets at the bottom, like a status bar and a static, would this function be called twice, in practise? Once to determine where the static goes, and once more to determine what’s really left?

    Much thanks BTW for extensively documenting this function in a clear way. Too bad the MSDN doc says it’s deprecated, which could mean that this function gets dropped sometime. Though maybe (if I’ll use the 2 functions) I could copy the wine version into my project, since I’m writing free software anyway ;)

  7. Dan says:

    "It would be a support nightmare too. Heck, I’d be puzzled for a while by the new behaviour myself."

    That reminds me of an unrelated something… for a while Safari for Windows lost it’s titlebar.  There was space for it but no text, and clicking did nothing (using ALT and arrow keys seemed to work and made menus drop down from nowhere).  That was odd.  It seems fixed in the latest version though.

    Narc: I imagine this function was designed well before the more complex rebar control (which is even less complex than the docking toolbar stuff you’re describing).  But it should still work ok… as long as you recall it every time something happens that would change the size of a docked control, in order to get the new ClientRect.  Of course, this isn’t as simple a scenario as using it with a simple toolbar and statusbar with ShowHideMenuCtl… in fact you probably wouldn’t use ShowHideMenuCtl on a rebarband or floating toolbar dock area, just the individual bars (and I would have no clue if that function would even work).

  8. Shantanu says:

    Hi,

    The following MSDN page:

    http://msdn2.microsoft.com/EN-US/library/bb775674.aspx

    … mentions the following

    "The first control (first two INTs in lpInfo) is skipped completely. Pass two zeros."

    The part about the first two INTs being ignored should probably be mentioned in the main doc!!! :)

  9. Peter Donnelly (MSFT) says:

    — The part about the first two INTs being ignored should probably be mentioned in the main doc!!! —

    Consider it done.

  10. Shantanu says:

    <quote>

    Consider it done.

    </quote>

    "Thanks!" :)

Comments are closed.