テクニカル ノート 6: メッセージ マップ
このノートでは、MFC メッセージ マップ機能について説明します。
問題
Microsoft Windows では、メッセージング機能を使用するウィンドウ クラス内で、仮想関数が実装されています。 関連メッセージの数が多いため、Windows メッセージごとに個別の仮想関数を提供すると、作成される vtable は非常に大きなものになります。
システム定義 Windows メッセージの数は時間の経過と共に変化し、アプリケーションは独自の Windows メッセージを定義することができるため、メッセージ マップには、インターフェイスの変更による既存のコードの破損を防ぐ間接参照レベルが用意されています。
概要
MFC には、ウィンドウに送信されたメッセージを処理するために、従来の Windows ベースのプログラム内で使用されていた switch ステートメントの代替手段が用意されています。 ウィンドウがメッセージを受信したとき、適切なメソッドが自動的に呼び出されるように、メッセージからメソッドへのマッピングを定義することができます。 この message-map 機能の設計は仮想関数と似ていますが、C++ 仮想関数では実現できない追加のメリットがあります。
メッセージ マップの定義
DECLARE_MESSAGE_MAP マクロは、クラスに対して 3 つのメンバーを宣言します。
_messageEntries と呼ばれる AFX_MSGMAP_ENTRY エントリのプライベート配列。
_messageEntries 配列を指す messageMap と呼ばれる保護された AFX_MSGMAP 構造体。
messageMap のアドレスを返す
GetMessageMap
と呼ばれる保護された仮想関数。
このマクロは、メッセージ マップを使用して、任意のクラスの宣言に挿入する必要があります。 慣例に従って、これはクラスの宣言の末尾に配置されます。 次に例を示します。
class CMyWnd : public CMyParentWndClass
{
// my stuff...
protected:
//{{AFX_MSG(CMyWnd)
afx_msg void OnPaint();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
これは、AppWizard と ClassWizard が新しいクラスを作成するときに生成される形式です。 ClassWizard には、//{{ および //}} ブラケットが必要です。
メッセージ マップのテーブルは、メッセージ マップ エントリに展開される一連のマクロを使用して定義されます。 テーブルは BEGIN_MESSAGE_MAP マクロ呼び出しで始まります。このマクロ呼び出しにより、このメッセージ マップによって処理されるクラスと、未処理のメッセージが渡される親クラスが定義されます。 テーブルの末尾は END_MESSAGE_MAP マクロ呼び出しです。
この 2 つのマクロ呼び出しの間には、このメッセージ マップによって処理されるメッセージごとにエントリがあります。 Windows の標準メッセージには、そのメッセージのエントリを生成するON_WM_MESSAGE_NAME という形式のマクロが含まれています。
各 Windows メッセージのパラメーターをアンパックし、タイプ セーフを提供するために、標準の関数シグネチャが定義されています。 これらのシグネチャは、CWnd の宣言内の Afxwin.h ファイル内で見つけることができます。 それぞれにキーワード afx_msg が付いているため、簡単に特定できます。
Note
ClassWizard では、ご自身のメッセージ マップ ハンドラー宣言内で、afx_msg キーワードを使用する必要があります。
これらの関数シグネチャは、シンプルな規則に基づいて派生したものです。 関数の名前は必ず "On
" で始まります。 その後に "WM_" を除いた Windows メッセージの名前が続きます。名前の各単語の最初の文字は大文字です。 パラメーターの順序は、wParam、LOWORD
(lParam)、HIWORD
(lParam) です。 使用されていないパラメーターは渡されません。 MFC クラスによってラップされたハンドルは、適切な MFC オブジェクトへのポインターに変換されます。 次の例は、WM_PAINT メッセージを処理し、CMyWnd::OnPaint
関数が呼び出されるようにする方法を示しています。
BEGIN_MESSAGE_MAP(CMyWnd, CMyParentWndClass)
//{{AFX_MSG_MAP(CMyWnd)
ON_WM_PAINT()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
メッセージ マップ テーブルは、関数またはクラスの定義のスコープ外で定義する必要があります。 extern "C" ブロック内には入れないでください。
Note
ClassWizard により、コメント ブラケット //{{ と //}} の間のメッセージ マップ エントリが修正されます。
ユーザー定義 Windows メッセージ
ユーザー定義メッセージをメッセージ マップに含めるには、ON_MESSAGE マクロを使用します。 このマクロは、次の形式でメッセージ番号とメソッドを受け入れます。
// inside the class declaration
afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam);
#define WM_MYMESSAGE (WM_USER + 100)
BEGIN_MESSAGE_MAP(CMyWnd, CMyParentWndClass)
ON_MESSAGE(WM_MYMESSAGE, OnMyMessage)
END_MESSAGE_MAP()
この例では、このユーザー定義メッセージの標準 WM_USER ベースから派生した、Windows メッセージ ID を持つカスタム メッセージ用のハンドラーを確立します。 次の例は、このハンドラーを呼び出す方法を示しています。
CWnd* pWnd = ...;
pWnd->SendMessage(WM_MYMESSAGE);
この方法を使用するユーザー定義メッセージの範囲は、WM_USER から 0x7fff の範囲内である必要があります。
Note
ClassWizard は、ClassWizard ユーザー インターフェイスからの ON_MESSAGE ハンドラー ルーチンの入力をサポートしていません。 これらは Visual C++ エディターから手動で入力する必要があります。 ClassWizard は、これらのエントリを解析して、他のメッセージ マップ エントリと同じように参照できるようにします。
登録済み Windows メッセージ
RegisterWindowMessage 関数は、システム全体で一意であることが保証されている新しいウィンドウ メッセージの定義に使用されます。 これらのメッセージの処理には、マクロ ON_REGISTERED_MESSAGE が使用されます。 このマクロは、登録済み Winsows メッセージ ID を含む UINT NEAR 変数の名前を受け入れます。 次に例を示します。
class CMyWnd : public CMyParentWndClass
{
public:
CMyWnd();
//{{AFX_MSG(CMyWnd)
afx_msg LRESULT OnFind(WPARAM wParam, LPARAM lParam);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
static UINT NEAR WM_FIND = RegisterWindowMessage("COMMDLG_FIND");
BEGIN_MESSAGE_MAP(CMyWnd, CMyParentWndClass)
//{{AFX_MSG_MAP(CMyWnd)
ON_REGISTERED_MESSAGE(WM_FIND, OnFind)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
登録済み Windows メッセージ ID 変数 (この例では WM_FIND) は NEAR 変数でなければなりません。これは、ON_REGISTERED_MESSAGE の実装方法によるものです。
この方法を使用するユーザー定義メッセージの範囲は、0xC000 から 0xFFFF の範囲になります。
Note
ClassWizard は、ClassWizard ユーザー インターフェイスからの ON_REGISTERED_MESSAGE ハンドラー ルーチンの入力をサポートしていません。 これらはテキスト エディターから手動で入力する必要があります。 ClassWizard は、これらのエントリを解析して、他のメッセージ マップ エントリと同じように参照できるようにします。
コマンド メッセージ
メニューやアクセラレータからのコマンド メッセージは、ON_COMMAND マクロを使用してメッセージ マップ内で処理されます。 このマクロは、コマンド ID とメソッドを受け入れます。 指定されたコマンド ID に等しい wParam を持つ特定のWM_COMMAND メッセージのみが、メッセージ マップ エントリで指定されたメソッドによって処理されます。 コマンド ハンドラーのメンバー関数はパラメーターを取らず、void
を返します。 マクロの形式は次のとおりです。
ON_COMMAND(id, memberFxn)
コマンド更新メッセージは、同じメカニズムを使用してルーティングされますが、代わりに ON_UPDATE_COMMAND_UI マクロが使用されます。 コマンド更新ハンドラーのメンバー関数は、1 つのパラメーター、CCmdUI オブジェクトへのポインターを取り、void
を返します。 マクロの形式は次のとおりです
ON_UPDATE_COMMAND_UI(id, memberFxn)
上級ユーザーは、コマンド メッセージ ハンドラーの拡張形式、ON_COMMAND_EX マクロを使用できます。 このマクロは、ON_COMMAND 機能のスーパーセットを提供します。 拡張コマンド ハンドラー メンバー関数は、単一のパラメーター、つまりコマンド ID を含む UINT を取り、BOOL を返します。 戻り値は、コマンドが処理されたことを示す TRUE である必要があります。 それ以外の場合、他のコマンド ターゲット オブジェクトに対するルーティングが引き続き行われます。
形式の例を次に示します。
Resource.h 内 (通常は Visual C++ で生成)
#define ID_MYCMD 100 #define ID_COMPLEX 101
クラス宣言内
afx_msg void OnMyCommand(); afx_msg void OnUpdateMyCommand(CCmdUI* pCmdUI); afx_msg BOOL OnComplexCommand(UINT nID);
メッセージ マップ定義内
ON_COMMAND(ID_MYCMD, OnMyCommand) ON_UPDATE_COMMAND_UI(ID_MYCMD, OnUpdateMyCommand) ON_COMMAND_EX(ID_MYCMD, OnComplexCommand)
実装ファイル内
void CMyClass::OnMyCommand() { // handle the command } void CMyClass::OnUpdateMyCommand(CCmdUI* pCmdUI) { // set the UI state with pCmdUI } BOOL CMyClass::OnComplexCommand(UINT nID) { // handle the command return TRUE; }
上級ユーザーは、単一コマンド ハンドラー ON_COMMAND_RANGE または ON_COMMAND_RANGE_EX を使用することで、コマンドの範囲を処理できます。 これらのマクロの詳細については、製品ドキュメントを参照してください。
Note
ClassWizard は、ON_COMMAND および ON_UPDATE_COMMAND_UI ハンドラーの作成をサポートしていますが、ON_COMMAND_EX およびON_COMMAND_RANGE ハンドラーの作成はサポートしていません。 ただし、Class Wizard は、4 つのコマンド ハンドラー バリアントをすべて解析し、閲覧できるようにします。
コントロール通知メッセージ
子コントロールからウィンドウに送信されるメッセージには、そのメッセージ マップ エントリ内にコントロール ID という追加情報が含まれます。 メッセージ マップ エントリ内で指定されたメッセージ ハンドラーは、次の条件が満たされている場合にのみ呼び出されます。
BN_CLICKED などのコントロール通知コード (lParam の上位ワード) が、メッセージ マップ エントリ内で指定された通知コードと一致する。
コントロール ID (wParam) が、メッセージ マップ エントリ内で指定されたコントロール ID と一致する。
カスタム コントロール通知メッセージは、ON_CONTROL マクロを使用して、カスタム通知コードを含むメッセージ マップ エントリを定義する場合があります。 このマクロの形式は次のとおりです
ON_CONTROL(wNotificationCode, id, memberFxn)
高度な使用として、ON_CONTROL_RANGE を使って、同じハンドラーを持つコントロールの範囲から特定のコントロール通知を処理することができます。
Note
ClassWizard は、ユーザー インターフェイス内での ON_CONTROL または ON_CONTROL_RANGE ハンドラーの作成をサポートしていません。 これらはテキスト エディターを使用して、手動で入力する必要があります。 ClassWizard は、これらのエントリを解析して、他のメッセージ マップ エントリと同じように参照できるようにします。
Windows コモンコントロールは、複雑なコントロール通知に対して、より強力な WM_NOTIFY を使用します。 このバージョンの MFC は、ON_NOTIFY マクロと ON_NOTIFY_RANGE マクロを使って、この新しいメッセージを直接サポートしています。 これらのマクロの詳細については、製品ドキュメントを参照してください。