次の方法で共有


テクニカル ノート 6: メッセージ マップ

更新 : 2007 年 11 月

ここでは 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 というキーワードが付いているので、すぐにわかります。

0812b0wa.alert_note(ja-jp,VS.90).gifメモ :

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" ブロック内には入れないでください。

0812b0wa.alert_note(ja-jp,VS.90).gifメモ :

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 にする必要があります。

0812b0wa.alert_note(ja-jp,VS.90).gifメモ :

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 になります。

0812b0wa.alert_note(ja-jp,VS.90).gifメモ :

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;
    }
    

上級ユーザーは、単一のコマンド ハンドラ ON_COMMAND_RANGE または ON_COMMAND_RANGE_EX を使用して、連続したコマンドの範囲を処理できます。これらのマクロの詳細については、製品のドキュメントを参照してください。

0812b0wa.alert_note(ja-jp,VS.90).gifメモ :

ClassWizard では、ON_COMMANDON_UPDATE_COMMAND_UI の両ハンドラを作成できますが、ON_COMMAND_EXON_COMMAND_RANGE はどちらも作成できません。ただし、Class Wizard はコマンド ハンドラの 4 形式をすべて解析できるので、いずれも参照は可能になります。

コントロール通知メッセージ

子コントロールからウィンドウに送られるメッセージでは、コントロールの ID を示す追加情報がメッセージ マップ エントリに付加されます。メッセージ マップ エントリで指定されたメッセージ ハンドラは、次の条件が満たされた場合にのみ呼び出されます。

  • BN_CLICKED などのコントロール通知コード (lParam の上位ワード) が、メッセージ マップ エントリで指定されている通知コードに一致した場合。

  • コントロール ID (wParam) が、メッセージ マップ エントリで指定されているコントロール ID に一致した場合。

カスタム コントロール通知メッセージでも ON_CONTROL マクロを使って、カスタム通知コード付きのメッセージ マップ エントリを定義できます。次に、このマクロの形式を示します。

ON_CONTROL(wNotificationCode, id, memberFxn)

上級プログラマ用には ON_CONTROL_RANGE マクロもあります。このマクロを使うと、一連のコントロールからの特定のコントロール通知を同一のハンドラで処理できます。

0812b0wa.alert_note(ja-jp,VS.90).gifメモ :

ClassWizard のユーザー インターフェイスでは、ON_CONTROL ハンドラや ON_CONTROL_RANGE ハンドラを作成することはできません。これらはテキスト エディタから手動で入力する必要があります。入力したエントリは ClassWizard で解析され、他のメッセージ マップ エントリと同じように参照できます。

Windows コモン コントロールでは、複雑なコントロール通知のために、さらに強力な WM_NOTIFY が使われます。このバージョンの MFC では、マクロ ON_NOTIFY および ON_NOTIFY_RANGE を使用することで、この新しいメッセージを直接サポートしています。これらのマクロの詳細については、製品のドキュメントを参照してください。

参照

その他の技術情報

番号順テクニカル ノート

カテゴリ別テクニカル ノート