Практическое руководство. Создание виртуальной схемы сообщений для класса шаблона
Сопоставление сообщений в 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
шаблонируется для одного типа, описывающего источник данных, с которым он будет синхронизироваться. Он также объявляет три метода, которые будут участвовать в карте сообщений класса: OnPaint
, OnDestroy
и OnSynchronize
. Метод 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
метод, например CArray
, CList
и CMap
. Функция StringizeElement
является прототипом функции шаблона, созданной следующим образом:
// 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()
Ниже показан пример использования CSyncListBox
класса с помощью CStringList
объекта:
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
Обработка и сопоставление сообщений