テクニカル ノート 6: メッセージ マップ
ここでは MFC のメッセージ マップ機能について説明します。
問題
Microsoft Windows では、メッセージ機能を使用するウィンドウ クラスに仮想関数を実装しています。 多数のメッセージを扱うため、Windows メッセージごとに仮想関数を用意すると、使用できないほど大きい vtable が作成されることになります。
システム定義の Windows メッセージの数は時と共に変化します。また、アプリケーションで独自の Windows メッセージを定義する場合もあります。このため、インターフェイスが変更されても既存のコードに影響が及ばないように、間接操作を可能にするメッセージ マップ機構が用意されています。
概要
従来の Windows ベースのプログラムでは、ウィンドウに送られたメッセージを処理するために switch ステートメントを使っていましたが、MFC では代わりにマップを使用します。 ウィンドウにメッセージが届いたら適切なメソッドが自動的に呼び出されるように、メッセージからメソッドへのマップを定義できます。 このメッセージ マップ機能は仮想関数に似ていますが、C++ の仮想関数にはない利点もあります。
メッセージ マップを定義する
DECLARE_MESSAGE_MAP マクロによってクラスの 3 つのメンバーを宣言します。
AFX_MSGMAP_ENTRY エントリのプライベート配列 _messageEntries。
プロテクトされた AFX_MSGMAP 構造体 messageMap。_messageEntries 配列を指します。
プロテクト仮想関数 GetMessageMap。messageMap のアドレスを返します。
このマクロはメッセージ マップを使うクラスの宣言に必ず記述してください。 規約上、クラス宣言の最後に記述することになっています。 次に例を示します
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 メッセージのパラメーターをアンパックし、型を保証するために、標準の関数シグネチャが定義されています。 これらのシグネチャは、ファイル Afxwin.h の CWnd 宣言にあります。 それぞれに afx_msg というキーワードが付いているので、すぐにわかります。
注意
ClassWizard では、メッセージ マップ ハンドラーの宣言に必ずキーワード afx_msg を使ってください。
これらの関数シグネチャは、単純な規約に基づいて決定されています。 関数名は常に "On" で始まります。 この後に、Windows メッセージ名から "WM_" を取り、各語の先頭を大文字にした名前が続きます。 パラメーターは、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" ブロック内には入れないでください。
注意
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()
この例では、カスタム メッセージ用のハンドラーを設定しています。カスタム メッセージの Windows メッセージ ID は、ユーザー定義メッセージ用の標準ベース WM_USER に基づきます。 このハンドラーを呼び出す例を次に示します。
CWnd* pWnd = ...;
pWnd->SendMessage(WM_MYMESSAGE);
この手法を使うユーザー定義メッセージの範囲は、WM_USER から 0x7fff にする必要があります。
注意
ClassWizard では、ON_MESSAGE ハンドラー ルーチンを ClassWizard ユーザー インターフェイスから入力することはできません。 これらは Visual C++ エディターから手動で入力する必要があります。 入力したエントリは ClassWizard で解析され、他のメッセージ マップ エントリと同じように参照できます。
登録 Windows メッセージ
RegisterWindowMessage 関数を使用すると、システム内で一意な新しいウィンドウ メッセージを定義できます。 これらのメッセージはマクロ ON_REGISTERED_MESSAGE で処理されます。 このマクロは、登録された Windows メッセージ 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) は、ON_REGISTERED_MESSAGE の実装に合わせて NEAR 変数にする必要があります。
この手法を使うユーザー定義メッセージの範囲は、0xC000 から 0xFFFF になります。
注意
ClassWizard では、ON_REGISTERED_MESSAGE ハンドラー ルーチンを ClassWizard ユーザー インターフェイスから入力することはできません。 これらはテキスト エディターから手動で入力する必要があります。 入力したエントリは ClassWizard で解析され、他のメッセージ マップ エントリと同じように参照できます。
コマンド メッセージ
メニューおよびアクセラレータからのコマンド メッセージは ON_COMMAND マクロによってメッセージ マップで処理されます。 このマクロは、コマンド ID とメソッドを受け取ります。 指定されたコマンド ID に wParam が等しい WM_COMMAND メッセージだけが、メッセージ マップ エントリで指定されたメソッドによって処理されます。 コマンド ハンドラーのメンバー関数はパラメーターを受け取らず、void を返します。 このマクロの形式を次に示します。
ON_COMMAND(id, memberFxn)
コマンド更新メッセージも同じ仕組みで送られますが、代わりに ON_UPDATE_COMMAND_UI マクロを使用します。 コマンド更新ハンドラーのメンバー関数は、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; }
高度なユーザー コマンドの範囲は、1 つのコマンド ハンドラーを使用して処理できます。ON_COMMAND_RANGEまたはON_COMMAND_RANGE_EX。 これらのマクロの詳細については、製品のドキュメントを参照してください。
注意
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 マクロもあります。このマクロを使うと、一連のコントロールからの特定のコントロール通知を同一のハンドラーで処理できます。
注意
ClassWizard のユーザー インターフェイスでは、ON_CONTROL ハンドラーや ON_CONTROL_RANGE ハンドラーを作成することはできません。 これらはテキスト エディターから手動で入力する必要があります。 入力したエントリは ClassWizard で解析され、他のメッセージ マップ エントリと同じように参照できます。
Windows コモン コントロールでは、複雑なコントロール通知のために、さらに強力な WM_NOTIFY が使われます。 このバージョンの MFC では、マクロ ON_NOTIFY および ON_NOTIFY_RANGE を使用することで、この新しいメッセージを直接サポートしています。 これらのマクロの詳細については、製品のドキュメントを参照してください。