次の方法で共有


テクニカル ノート 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_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 マクロもあります。このマクロを使うと、一連のコントロールからの特定のコントロール通知を同一のハンドラーで処理できます。

注意

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

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

参照

その他の技術情報

番号順テクニカル ノート

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