Getting the location of the Close button in the title bar, from Windows 2000 or Windows XP


Today's Little Program locates the × button in the corner of the window and displays a balloon tip pointing at it. We did this some time ago with the help of the WM_GET­TITLE­BAR­INFO­EX message, which is new for Windows Vista. But what if you don't have that message available, say, because you're running on Windows 2000 or Windows XP or (gasp) Windows 98?

You can use the classic Accessibility interface IAccessible to enumerate the buttons in the title bar and see which one the window reports as the Close button.

Let's take the program from last time and change the Get­Close­Button­Center function:

#include <oleacc.h>
#include <atlbase>

BOOL GetCloseButtonCenter(HWND hwnd, POINT *ppt)
{
 CComPtr<IAccessible> spacc;
 if (FAILED(AccessibleObjectFromWindow(hwnd, OBJID_TITLEBAR,
                   IID_PPV_ARGS(&spacc)))) return FALSE;
 CComQIPtr<IEnumVARIANT> spenum(spacc);
 if (!spenum) return FALSE;
 for (CComVariant vtChild; spenum->Next(1, &vtChild, nullptr) == S_OK;
      vtChild.Clear()) {
  CComVariant vtState;
  if (FAILED(spacc->get_accState(vtChild, &vtState))) continue;
  if (vtState.vt != VT_I4) continue;
  if (vtState.lVal & (STATE_SYSTEM_INVISIBLE |
                      STATE_SYSTEM_OFFSCREEN |
                      STATE_SYSTEM_UNAVAILABLE)) continue;
  long left, top, width, height;
  if (FAILED(spacc->accLocation(&left, &top, &width, &height,
                                vtChild))) continue;
  POINT pt = { left + width / 2, top + height / 2 };
  if (SendMessage(hwnd, WM_NCHITTEST, 0,
                  MAKELPARAM(pt.x, pt.y)) == HTCLOSE) {
   *ppt = pt;
   return TRUE;
  }
 }
 return FALSE;
}

We obtain the IAccessible interface for the title bar and proceed to enumerate its children. For each child, we get its location, and then use the WM_NC­HIT­TEST message to determine programmatically what that location corresponds to. If the answer is "This is the Close button," then we found the button and report its center.

Note that this once again highlights the distinction between WM_NC­MOUSE­MOVE and WM_NC­HIT­TEST. Hit-testing can occur for reasons other than mouse movement.

Exercise: Why couldn't we use the IAccessible::get_accName method to figure out which button each child represents?

Comments (11)
  1. Joshua says:

    Because it's localizable.

  2. Erik says:

    Guess: The accName will be localized, and have exciting new values in translated versions of Windows, while the program will only look for the English one and conclude that no close button exists.

  3. skSdnW says:

    Why does MS not document the system metrics used by classic/pre-uxtheme windows and common controls? web.archive.org/…/original.aspx is really useful and I wish all of this was actually documented.

  4. @skSdnW: The size of those metrics can vary depending on the DPI settings (otherwise known as "adjust this ruler on your screen control") or if you adjusted the size of the various elements in the Appearances control panel.

  5. SI says:

    They can vary, but knowing when to use which metric is indeed very useful. For example when custom drawing a column in a listview, its not always straightforward to figure out the various alignment issues, and when to use which value.

  6. skSdnW says:

    @MNGoldenEagle: When visual styles are off GetSystemMetrics and SystemParametersInfo are then only things you have to work with and knowing which SM_ values are used where would be useful. Some controls hardcode a pixel value in some places (ListView? Menu?) but most of the time there should be a metric you can use…

  7. Medinoc says:

    I'm surprised you can QueryInterface an IAccessible into an IEnumVARIANT: IEnumXxxxx are one-time used objects; that would mean the IAccessible's children can only be enumerated once…

  8. SI says:

    It could be creating a subobject and returning it when handling the QI. As long as QIing the IEnumVARIANT to IUnknown returns the same IUnknown pointer as QIing the base object to IUnknown it should be legal COM. This just means going from the IEnumVariant -> IUnknown ->IEnumVariant would reset the enumeration.

  9. Medinoc says:

    @Joker_vD: Isn't that what IServiceProvider does? The services it provides don't have to implement QueryService…

  10. Joker_vD says:

    Yeah, that's an exciting quirk of COM: while it stands for *Component* Object Model, you actually don't get to see any components! You only see interfaces that you cast one to another, which causes many interesting things to the components behind the scenes. Really, why couldn't we have actual components with QI to dispense interfaces? Then interfaces themselves wouldn't have QI, and I bet that would simplify aggregation tremendously.

  11. John Doe says:

    @SI, It's barely legal COM, if at all.

    If the QueryInterface for IEnumVARIANT returns the same enumerator but resetting it before, then QI is having completely undesired and unreliable side effects, most probably not observable across apartments due to interface pointer caching.

    If, on the other hand, it returns a new kind-of-aggregatee enumerator (remember, QI for IUnknown must return the same pointer), given CComQIPtr<IEnumVARIANT> spenum1(spacc) and CComQIPtr<IEnumVARIANT> spenum2(spacc), the calls to spenum1->Next will not affect the calls to spenum2->Next, so it becomes apparent you're violating identity.

    Either way, you should always call Clone on such QI'ed IEnumVARIANT, which should provide a fresh, non-aggregated and single-identity enumerator.

    Unless, Of Course™, in this instance, Clone itself misbehaves, such as simply resetting the current enumerator and returning it (or one that shares state), because it was most likely implemented by teh the same m̶o̶r̶o̶n̶ person that miscoded QueryInterface.

    @Joker_vD, I can't tell if you're being serious.  QueryInterface is so essential in IUnknown that, without it, COM wouldn't even get to first base, much less being likened to love.

    For instance, what good would an IUnknown pointer be, by itself, without QueryInterface?  Or pick your favorite widespread used interface and ask the same question.

    Objects would implement an ubiquitous casting interface e.g. IDynamicCast, you'd reduce IUnknown or have a separate interface for reference count e.g. IReferenceCount, and you'd have to decide when it was interesting to pass IDynamicCast or one of its descendants.

    You can easily reach the conclusion that IDynamicCast is way (WWAAYY) more interesting, because you can get IReferenceCount, or anything at all from it.

    But you'll soon find it hard to properly maintain an object's lifetime.  If there was a rule stating that casting doesn't automatically increment the count, you'd have a serious Release race condition on the client.  If there was a rule stating that casting to IReferenceCount doesn't increase the count, it would make only pointer to this interface, but such an important one, subject to the Release race condition.  If casting would always increase the count, how would to keep the count of casts you've made?  Also, how would you allow interfaces to be actual inner instances (e.g. tear-offs) or an aggregatee?

    So, you easily reach the conclusion that every interface pointer must have lifetime management.  Seems like IUnknown.

    @Medinoc, the closest is actually IObjectProvider->QueryObject, because IServiceProvider->QueryService may delegate to a chain or hierarchy of objects.  But only IUnknown->QueryInterface requires and guarantees a set of properties, such as object identity, essential to even the most basic purposes.

Comments are closed.

Skip to main content