Comment : créez une table des messages pour une classe de modèle
Le mappage de message dans MFC est un moyen efficace de diriger les messages Windows à une instance d'objet appropriée C++. Les exemples de cibles de table des messages MFC comprennent des classes d'application, des classes document et vue, des classes de contrôle, et ainsi de suite.
Les tables de messages traditionnelles MFC sont déclarées avec la macro BEGIN_MESSAGE_MAP pour déclarer le début de la table des messages, une entrée macro pour chaque méthode de la classe de gestionnaire de messages, et enfin la macro END_MESSAGE_MAP pour donner la fin de la carte du message.
Une limite avec la macro BEGIN_MESSAGE_MAP se produit lorsqu'elle est utilisée conjointement avec une classe qui contient les arguments de modèle. Si elle est utilisée avec une classe de modèle, la macro entraînera une erreur de compilation en raison des paramètres de modèle manquants pendant l'expansion macro. La macro BEGIN_TEMPLATE_MESSAGE_MAP a été conçue pour autoriser les classes qui contient un seul argument de modèle pour déclarer leurs propres tables de messages.
Exemple
Prenons un exemple où la classe MFC CListBox est étendue pour assurer la synchronisation avec une source de données externe. La classe fictive CSyncListBox est déclarée comme suit :
// 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
};
La classe CSyncListBox est basée sur des modèles sur un seul type qui décrit la source de données avec laquelle elle synchronisera. Il déclare également trois méthodes qui participeront à la table des messages de la classe : OnPaint, OnDestroy, et OnSynchronize. La méthode OnSynchronize est implémentée comme suit :
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;
}
L'implémentation ci-dessus fournit la classe CSyncListBox à spécialiser sur n'importe quel type de classe qui implémente la méthode GetCount, telle que CArray, CList, et CMap. La fonction de StringizeElement est une fonction de modèle prototype par ce qui suit :
// Template function for converting an element within a collection
// to a CString object
template<typename CollectionT>
CString StringizeElement(CollectionT* pCollection, INT iIndex);
Normalement, la table des messages pour cette classe est définie comme suit :
BEGIN_MESSAGE_MAP(CSyncListBox, CListBox)
ON_WM_PAINT()
ON_WM_DESTROY()
ON_MESSAGE(LBN_SYNCHRONIZE, OnSynchronize)
END_MESSAGE_MAP()
où LBN_SYNCHRONIZE est un message utilisateur personnalisé défini par l'application, par exemple :
#define LBN_SYNCHRONIZE (WM_USER + 1)
La carte de macro ci-dessus ne compilera pas, en raison du fait que la spécification du modèle pour la classe CSyncListBox est absente pendant l'expansion macro. La macro BEGIN_TEMPLATE_MESSAGE_MAP résout ce problème en incorporant le paramètre de modèle dans la carte étendue de macro. La table des messages pour cette classe est :
BEGIN_TEMPLATE_MESSAGE_MAP(CSyncListBox, CollectionT, CListBox)
ON_WM_PAINT()
ON_WM_DESTROY()
ON_MESSAGE(LBN_SYNCHRONIZE, OnSynchronize)
END_MESSAGE_MAP()
L'exemple suivant illustre l'utilisation de l'exemple de la classe de CSyncListBox à l'aide d'un objet 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);
}
}
Pour effectuer le test, la fonction StringizeElement doit être spécialisée pour fonctionner avec la classe 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
}