FREE Subscription to Dr. Dobb’s Digest: Same Great Content, New Digital Edition
Site Archive (Complete)
C++
Email
Print
Reprint

add to:
Del.icio.us
Digg
Google
Furl
Slashdot
Y! MyWeb
Blink
December 21, 2005
Flexible C++ #14: Finding Child Windows with Simulated Local Functions

(Page 1 of 3)
Matthew Wilson
A common problem in Windows programming is retrieving a descendent windows by Id. This is the topic Matthew examines this month. Additionally, he uses the problem to illustrate how some languages can use local functions to maximize encapsulation.
Flexible C++ #14

A common problem in Windows programming is retrieving a descendent windows by Id. This is the topic I examine this month. Additionally, I use the problem to illustrate how some languages can use local functions to maximize encapsulation.

This installment also introduces what will become a regular feature in this column--the "Compiler Gremlins" sidebar. In each installment that describes code that has precipitated compiler bugs (ones that have been resolved, of course), this sidebar will discuss the workarounds I've found, which, I hope, may inform on similar issues you may find in your own work in writing portable libraries.

Finding Child Windows via EnumChildWindows()

GetDlgItem() has to be a good contender for the most widely used function in Windows GUI programming. It takes a parent window handle and an Id, and returns the window handle of an immediate child window bearing that id, or NULL if none is found. Occasionally, however, one needs to get a child window that may not be an immediate descendent; for instance, it may the child of a child, or the child of a child of a child, and so on. This requires the use of the EnumChildWindows() function, to which you passes a callback function and a search structure (which is passed back to the callback function) to locate the child with the given id, as in Listing 1.

Listing 1

struct FindChild
{
  HWND  hwndChild;
  int   id;
};

BOOL CALLBACK FindChildProc(HWND hwnd, LPARAM lParam)
{
  if(::GetDlgCtrlID(hwnd) == reinterpret_cast(lParam)->id)
  {
    reinterpret_cast(lParam)->hwndChild = hwnd;
    return false; // This stops the search, since we've found our child
  }
  else
  {
    return true;
  }
}

HWND FindFirstChildById(HWND hwndParent, int id)
{
  FindChild fc = { NULL, id };
  ::EnumChildWindows(hwndParent, FindChildProc, reinterpret_cast(&fc));
  return fc.hwndChild;
}
There are several unattractive features of this implementation. Putting aside the ugly but necessary casting, the main problem is the declaration of the search function FindChildProc() and the search context type FindChild. Not only are they separate from the definition of FindFirstChildById()--which increases the likelihood that maintenance will introduce bugs--but the names of the struct and callback function unnecessarily appear in the surrounding namespace, ready to clash with other symbols. (This is so even if an anonymous namespace is used, though in that case the collision is dramatically reduced to only intra-compilation-unit collisions.) Ideally, we'd like a way to have the struct and callback function visible only to the implementation of the FindFirstChildById() function.

Implementation In D

The D language supports local functions, so a D implementation of FindFirstChildById() allowing us to declare the callback function within the main function, and so keep everything nice and neat (Listing 2).
Listing 2
typedef void  *HWND;
alias int     BOOL;
alias int     LPARAM;

extern (Windows)
{
  alias BOOL (*WNDENUMPROC)(HWND hwnd, LPARAM lParam);

  int GetDlgCtrlID(HWND);
  BOOL EnumChildWindows(HWND hwnd, WNDENUMPROC lpEnumFunc
                      , LPARAM lParam);
}

HWND FindFirstChildById(HWND hwndParent, int id)
{
  if(GetDlgCtrlID(hwndParent) == id)
  {
    return hwndParent;
  }
  else
  {
    struct ChildFind
    {
      HWND  hwndChild;
      int   id;
    };

    ChildFind cf;

    cf.hwndChild  = null;
    cf.id         = id;

    extern(Windows) static BOOL FindChildProc(HWND hwnd, LPARAM lParam)
    {
      ChildFind *find = cast(ChildFind*)(lParam);

      return (GetDlgCtrlID(hwnd) == find.id)
              ? (find.hwndChild = hwnd, false)
              : true;
    }

    EnumChildWindows(hwndParent, &FindChildProc, cast(LPARAM)(&cf));

    return cf.hwndChild;
  }
}

Implementation in C++

Unfortunately C++ does not (directly) support local functions, so we can't use this exact technique. However, C++ does support local classes (and structs and unions). And since classes may have (static) methods, we can simulate local functions using local classes, as in Listing 3.
Listing 3
#include 
HWND FindFirstChildById(HWND hwndParent, int id)
{
  if(::GetDlgCtrlID(hwndParent) == id)
  {
    return hwndParent;
  }
  else
  {
    class ChildFind
    {
    public:
      explicit ChildFind(HWND hwndParent, int id)
        : m_hwndChild(NULL)
        , m_id(id)
      {
        ::EnumChildWindows(hwndParent, EnumProc, reinterpret_cast(this));
      }

    public:
      operator HWND() const
      {
        return m_hwndChild;
      }

    private:
      static BOOL CALLBACK EnumProc(HWND hwnd, LPARAM lParam)
      {
        ChildFind &find = *reinterpret_cast(lParam);

        return (::GetDlgCtrlID(hwnd) == find.m_id)
                  ? (find.m_hwndChild = hwnd, FALSE)
                  : TRUE;
      }

    private:
      HWND      m_hwndChild;
      int const m_id;

    } find(hwndParent, id);

    return find;
  }
}

The local class ChildFind has a static method, EnumProc(), which is passed to the call to EnumChildWindows() along with the this pointer as the callback parameter. Within each callback the given window is tested against the desired Id, and if a match is found the window handle is remembered in the member variable m_hwndChild, and FALSE is returned to terminate the search.

The instance is constructed on the hwndParent and id parameters passed to FindFirstChildById(), and the implicit conversion operator is used to return the value of (the private member) m_hwndChild to the caller.

Note: In my use of the function I needed to be able to cope with the situation where the parent itself may be the one to find, hence the first call to GetDlgCtrlID(). Feel free to remove that test if you don't want those semantics.

The C++ version is found in the STLSoft libraries from version 1.8.9 onwards, and contains the workarounds described in the Compiler Gremlins sidebar. Walter and I are intending to include the D version in the D standard library in the near future. For the moment, you may download it from http://www.cuj.com/code/.

Acknowledgements

Thanks to my usual crew of disparate code pirates for their insightful criticisms: Bjorn Karlsson, Christopher Diggins, Garth Lancaster, Nevin Liber, and especially to Walter Bright, for ensuring that the D compiler kept up with the D language in this regard.

About the Author

Matthew Wilson is a software development consultant for Synesis Software, and creator of the STLSoft libraries. He is the author of Imperfect C++ (Addison-Wesley, 2004), and is currently working on his next two books, one of which is not about C++. Matthew can be contacted via http://imperfectcplusplus.com/.

1 | 2 | 3 Next Page
TOP 5 ARTICLES
No Top Articles.



MICROSITES
FEATURED TOPIC

ADDITIONAL TOPICS

INFO-LINK