MFC には、Windows メッセージを効率よく、適切な C++ オブジェクトのインスタンスに結び付けるための手段としてメッセージ マップというしくみが用意されています。たとえば、MFC メッセージ マップの対象としては、アプリケーションのクラス、ドキュメントやビューのクラス、コントロールのクラスなどが挙げられます。
従来の MFC メッセージ マップは、メッセージ マップの開始を宣言する BEGIN_MESSAGE_MAP マクロ、メッセージ ハンドラ クラスの各メソッドに対応するマクロ エントリ、および、メッセージ マップの終了を宣言する END_MESSAGE_MAP マクロを使って宣言されます。
BEGIN_MESSAGE_MAP マクロでは、テンプレート引数を含んだクラスと組み合わせて使用した場合に、ある制限が生じます。このマクロをテンプレート クラスで使用した場合、マクロの展開時にテンプレートのパラメータが見つからないため、コンパイル時のエラーが発生します。そこで、テンプレート引数を 1 つ含んだクラスで、独自のメッセージ マップを宣言できるようにするために、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 つ宣言されています (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 オブジェクトが使用されています。
extern CSyncListBox<CStringList>* pctlStringLB;
void CSyncListBox_Test()
{
// Create a CStringList object and add a few strings
CStringList stringList;
stringList.AddTail( "A" );
stringList.AddTail( "B" );
stringList.AddTail( "C" );
// Send a message to the list box control to synchronize its
// contents with the string list
pctlStringLB->SendMessage( LBN_SYNCHRONIZE, 0, (LPARAM)&stringList );
// Verify the contents of the list box by printing out its contents
INT nCount = pctlStringLB->GetCount();
for( INT n = 0; n < nCount; n++ )
{
CHAR szText[256];
pctlStringLB->GetText( n, szText );
TRACE( "%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
} 参照