如何:为模板类创建消息映射

MFC 中的消息映射提供了一种将 Windows 消息定向到合适的 C++ 对象实例的有效方法。 MFC 消息映射目标的示例包括应用程序类、文档和视图类、控件类等。

传统的 MFC 消息映射通过以下方式进行声明:使用 BEGIN_MESSAGE_MAP 宏声明消息映射的开头和每个消息处理程序类方法的宏项,最后使用 END_MESSAGE_MAP 宏声明消息映射的结尾。

BEGIN_MESSAGE_MAP 宏与包含模板参数的类一起使用时,该宏将受到限制。 在与模板类一起使用时,此宏会导致编译时错误,因为宏展开过程中缺少模板参数。 BEGIN_TEMPLATE_MESSAGE_MAP 宏旨在允许包含单个模板参数的类声明其自己的消息映射。

示例

请考虑这样的示例:扩展 MFC CListBox 类来提供与外部数据源的同步。 虚构的 CSyncListBox 类的声明方式如下:

// 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
};

CSyncListBox 类针对一个类型进行模板化,该类型描述它将与之同步的数据源。 它还声明了将参与类的消息映射的 3 个方法:OnPaintOnDestroyOnSynchronize。 按如下实现 OnSynchronize 方法:

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;
}

上述实现使 CSyncListBox 类可在实现 GetCount 方法的任何类类型上专用化,这些类型包括 CArrayCListCMapStringizeElement 函数是通过以下代码原型化的模板函数:

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

通常,此类的消息映射的定义方式如下:

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

LBN_SYNCHRONIZE 是应用程序定义的自定义用户消息,例如:

#define LBN_SYNCHRONIZE (WM_USER + 1)

上面的宏映射不会进行编译,因为在宏扩展的过程中,CSyncListBox 类的模板规范将缺失。 BEGIN_TEMPLATE_MESSAGE_MAP 宏通过将指定模板参数合并到扩展的宏映射中来解决此问题。 此类的消息映射将成为:

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

下面演示使用 CStringList 对象的 CSyncListBox 类的示例用法:

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);
   }
}

为了完成测试,必须将 StringizeElement 函数专用化为与 CStringList 类一起使用:

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
}

另请参阅

BEGIN_TEMPLATE_MESSAGE_MAP
消息处理和映射