How to: Create a Message Map for a Template Class

Message mapping in MFC provides an efficient way to direct Windows messages to an appropriate C++ object instance. Examples of MFC message map targets include application classes, document and view classes, control classes, and so on.

Traditional MFC message maps are declared using the BEGIN_MESSAGE_MAP macro to declare the start of the message map, a macro entry for each message-handler class method, and finally the END_MESSAGE_MAP macro to declare the end of the message map.

One limitation with the BEGIN_MESSAGE_MAP macro occurs when it is used in conjunction with a class containing template arguments. When used with a template class, this macro will cause a compile-time error due to the missing template parameters during macro expansion. The BEGIN_TEMPLATE_MESSAGE_MAP macro was designed to allow classes containing a single template argument to declare their own message maps.

Example

Consider an example where the MFC CListBox class is extended to provide synchronization with an external data source. The fictitious CSyncListBox class is declared as follows:

// Extends the CListBox class to provide synchronization with 
// an external data source
template <typename CollectionT>
class CSyncListBox : public CListBox
{
public:
   CSyncListBox();
   virtual ~CSyncListBox();

   afx_msg void OnPaint();
   afx_msg void OnDestroy();
   afx_msg LRESULT OnSynchronize(WPARAM wParam, LPARAM lParam);
   DECLARE_MESSAGE_MAP()

   // ...additional functionality as needed
};

The CSyncListBox class is templated on a single type that describes the data source it will synchronize with. It also declares three methods that will participate in the message map of the class: OnPaint, OnDestroy, and OnSynchronize. The OnSynchronize method is implemented as follows:

template <class CollectionT>
LRESULT CSyncListBox<CollectionT>::OnSynchronize(WPARAM, LPARAM lParam)
{
   CollectionT* pCollection = (CollectionT*)(lParam);

   ResetContent();

   if (pCollection != NULL)
   {
      INT nCount = (INT)pCollection->GetCount();
      for (INT n = 0; n < nCount; n++)
      {
         CString s = StringizeElement(pCollection, n);
         AddString(s);
      }
   }

   return 0L;
}

The above implementation allows the CSyncListBox class to be specialized on any class type that implements the GetCount method, such as CArray, CList, and CMap. The StringizeElement function is a template function prototyped by the following:

// Template function for converting an element within a collection
// to a CString object
template<typename CollectionT>
CString StringizeElement(CollectionT* pCollection, INT iIndex);

Normally, the message map for this class would be defined as:

BEGIN_MESSAGE_MAP(CSyncListBox, CListBox)
  ON_WM_PAINT()
  ON_WM_DESTROY()
  ON_MESSAGE(LBN_SYNCHRONIZE, OnSynchronize)
END_MESSAGE_MAP()

where LBN_SYNCHRONIZE is a custom user message defined by the application, such as:

#define LBN_SYNCHRONIZE (WM_USER + 1)

The above macro map will not compile, due to the fact that the template specification for the CSyncListBox class will be missing during macro expansion. The BEGIN_TEMPLATE_MESSAGE_MAP macro solves this by incorporating the specified template parameter into the expanded macro map. The message map for this class becomes:

BEGIN_TEMPLATE_MESSAGE_MAP(CSyncListBox, CollectionT, CListBox)
   ON_WM_PAINT()
   ON_WM_DESTROY()
   ON_MESSAGE(LBN_SYNCHRONIZE, OnSynchronize)
   END_MESSAGE_MAP()

The following demonstrates sample usage of the CSyncListBox class using a CStringList object:

void CSyncListBox_Test(CWnd* pParentWnd)
{
   CSyncListBox<CStringList> ctlStringLB;
   ctlStringLB.Create(WS_CHILD | WS_VISIBLE | LBS_STANDARD | WS_HSCROLL,
      CRect(10, 10, 200, 200), pParentWnd, IDC_MYSYNCLISTBOX);

   // Create a CStringList object and add a few strings
   CStringList stringList;
   stringList.AddTail(_T("A"));
   stringList.AddTail(_T("B"));
   stringList.AddTail(_T("C"));

   // Send a message to the list box control to synchronize its
   // contents with the string list
   ctlStringLB.SendMessage(LBN_SYNCHRONIZE, 0, (LPARAM)& stringList);

   // Verify the contents of the list box by printing out its contents
   INT nCount = ctlStringLB.GetCount();
   for (INT n = 0; n < nCount; n++)
   {
      TCHAR szText[256];
      ctlStringLB.GetText(n, szText);
      TRACE(_T("%s\n"), szText);
   }
}

To complete the test, the StringizeElement function must be specialized to work with the CStringList class:

template<>
CString StringizeElement(CStringList* pStringList, INT iIndex)
{
   if (pStringList != NULL && iIndex < pStringList->GetCount())
   {
      POSITION pos = pStringList->GetHeadPosition();
      for (INT i = 0; i < iIndex; i++)
      {
         pStringList->GetNext(pos);
      }
      return pStringList->GetAt(pos);
   }
   return CString(); // or throw, depending on application requirements
}

See also

BEGIN_TEMPLATE_MESSAGE_MAP
Message Handling and Mapping