次の方法で共有


TN006: メッセージ マップ

このメモでは、MFC メッセージ マップ機能について説明します。

問題

Microsoft Windows は、そのメッセージング機能を使用するウィンドウ クラスに仮想関数を実装します。 関連するメッセージの数が多いため、Windows メッセージごとに個別の仮想関数を指定すると、非常に大きな vtable が作成されます。

システム定義の Windows メッセージの数は時間の経過と同時に変化し、アプリケーションで独自の Windows メッセージを定義できるため、メッセージ マップには、インターフェイスの変更による既存のコードの中断を防ぐ一定の間接参照が用意されています。

概要

MFC は、従来の Windows ベースのプログラムでウィンドウに送信されたメッセージを処理するために使用された switch ステートメントの代替手段を提供します。 メッセージからメソッドへのマッピングを定義して、ウィンドウでメッセージを受信すると、適切なメソッドが自動的に呼び出されるようにすることができます。 このメッセージ マップ機能は仮想関数に似ていますが、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 でマークされます。

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" ブロックに配置しないでください。

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

この方法を使用するユーザー定義メッセージの範囲は、0x7fffするWM_USER範囲内である必要があります。

ClassWizard では、ClassWizard ユーザー インターフェイスからのON_MESSAGEハンドラー ルーチンの入力はサポートされていません。 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 変数である必要があります。

この方法を使用するユーザー定義メッセージの範囲は、0xFFFF 0xC000の範囲内になります。

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 という 1 つのパラメーターを受け取り、 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 の 1 つのコマンド ハンドラーを使用して、さまざまなコマンドを処理できます。 これらのマクロの詳細については、製品ドキュメントを参照してください。

ClassWizard では、ON_COMMANDハンドラーとON_UPDATE_COMMAND_UI ハンドラーの作成がサポートされていますが、ON_COMMAND_EXハンドラーやON_COMMAND_RANGE ハンドラーの作成はサポートされていません。 ただし、クラス ウィザードでは、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 マクロを使用して、この新しいメッセージを直接サポートしています。 これらのマクロの詳細については、製品ドキュメントを参照してください。

こちらも参照ください

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