テクニカル ノート 62: Windows コントロールへのメッセージ リフレクション (メッセージ返送)

Note

次のテクニカル ノートは、最初にオンライン ドキュメントの一部とされてから更新されていません。 結果として、一部のプロシージャおよびトピックが最新でないか、不正になります。 最新の情報について、オンライン ドキュメントのキーワードで関係のあるトピックを検索することをお勧めします。

このテクニカル ノートでは、MFC 4.0 の新機能であるメッセージ リフレクションについて説明します。 また、メッセージ リフレクションを使用する単純かつ再利用可能なコントロールを作成するための手順も含まれています。

このテクニカル ノートでは、メッセージ リフレクションについては説明しません。これは ActiveX コントロール (旧称 OLE コントロール) に適用されるためです。 記事「MFC ActiveX コントロール: Windows コントロールのサブクラス化」を参照してください。

メッセージ リフレクションとは

Windows コントロールは、通知メッセージをその親ウィンドウに頻繁に送信します。 たとえば、多くのコントロールでは、その親がコントロールの背景を描画するためのブラシを提供できるように、コントロールの色の通知メッセージ (WM_CTLCOLOR またはそのバリエーションの 1 つ) を親に送信します。

Windows とバージョン4.0 より前の MFC では、親ウィンドウ (多くの場合、ダイアログ ボックス) がこれらのメッセージの処理を担当します。 これは、メッセージを処理するためのコードが親ウィンドウのクラスに存在し、そのメッセージを処理する必要があるすべてのクラス内にそれが複製されている必要があることを意味します。 上記の場合では、カスタムの背景を備えたコントロールを必要とするすべてのダイアログ ボックスで、コントロールの色の通知メッセージを処理する必要があります。 それぞれの背景色を処理するコントロール クラスを記述できる場合は、コードを再利用する方がはるかに簡単です。

MFC 4.0 では、以前のメカニズムが引き続き機能し、親ウィンドウで通知メッセージを処理できます。 ただし、MFC 4.0 では、さらに、これらの通知メッセージを子コントロール ウィンドウ、親ウィンドウ、またはその両方で処理できる "メッセージ リフレクション" と呼ばれる機能を提供することで、再利用が容易になっています。 コントロールの背景色の例では、まったく親に依存せずに、WM_CTLCOLOR リフレクション メッセージを処理することによって、それぞれの背景色を設定するコントロール クラスを記述できるようになりました (メッセージ リフレクションは Windows ではなく MFC によって実装されるため、メッセージ リフレクションを機能させるには、親ウィンドウ クラスを CWnd から派生させる必要があることに注意してください)。

以前のバージョンの MFC では、オーナー描画のリスト ボックス向けのメッセージ (WM_DRAWITEM など) といった、いくつかのメッセージに対して仮想関数を提供することによって、メッセージ リフレクションと同様の処理を行っていました。 新しいメッセージ リフレクション メカニズムは一般化され、一貫性があります。

メッセージ リフレクションには、4.0 より前のバージョンの MFC 用に記述されたコードとの下位互換性があります。

親ウィンドウのクラス内で特定のメッセージまたは一連のメッセージのハンドラーを指定した場合は、独自のハンドラーで基底クラスのハンドラー関数を呼び出さないと、それによって同じメッセージのリフレクション メッセージ ハンドラーがオーバーライドされます。 たとえば、ダイアログ ボックス クラス内で WM_CTLCOLOR を処理する場合、指定した処理によって、すべてのリフレクション メッセージ ハンドラーがオーバーライドされます。

親ウィンドウ クラス内で、特定の WM_NOTIFY メッセージまたは一連の WM_NOTIFY メッセージのハンドラーを指定した場合、それらのメッセージを送信する子コントロールに ON_NOTIFY_REFLECT() によるリフレクション メッセージ ハンドラーがない場合にのみ、指定したハンドラーが呼び出されます。 メッセージ マップ内で ON_NOTIFY_REFLECT_EX() を使用した場合は、指定したメッセージ ハンドラーによって親ウィンドウがメッセージを処理できる場合とできない場合があります。 ハンドラーが FALSE を返す場合、メッセージは親によっても処理されますが、TRUE を返す呼び出しでは、親がそれを処理できません。 リフレクション メッセージは、通知メッセージの前に処理されることに注意してください。

WM_NOTIFY メッセージが送信されると、コントロールにはそれを処理する最初の機会が提供されます。 他のリフレクション メッセージが送信された場合、親ウィンドウにはそれを処理する最初の機会があり、コントロールはリフレクション メッセージを受信します。 これを行うには、ハンドラー関数と、コントロールのクラス メッセージ マップ内の適切なエントリが必要です。

リフレクション メッセージのメッセージマップ マクロは、通常の通知とは少し異なり、その通常の名前に _REFLECT が追加されます。 たとえば、親で WM_NOTIFY メッセージを処理するには、親のメッセージ マップ内で ON_NOTIFY マクロを使用します。 子コントロールでリフレクション メッセージを処理するには、子コントロールのメッセージ マップ内で ON_NOTIFY_REFLECT マクロを使用します。 場合によっては、パラメーターも異なります。 ClassWizard は通常、メッセージ マップのエントリを自動的に追加し、正しいパラメーターを備えたスケルトン関数の実装を提供することに注意してください。

新しい WM_NOTIFY メッセージについては、「テクニカル ノート 61: ON_NOTIFY メッセージと WM_NOTIFY メッセージ」を参照してください。

メッセージマップのエントリとリフレクション メッセージのハンドラー関数のプロトタイプ

コントロールのリフレクション通知メッセージを処理するには、次の表に示すメッセージマップ マクロと関数プロトタイプを使用します。

通常、ClassWizard では、これらのメッセージマップ エントリを自動的に追加し、スケルトン関数の実装を提供できます。 リフレクション メッセージのハンドラーを定義する方法については、「リフレクション メッセージ用のメッセージ ハンドラーの定義」を参照してください。

メッセージ名からリフレクション マクロ名に変換するには、ON_ を先頭に追加し、_REFLECT を末尾に追加します。 たとえば、WM_CTLCOLOR は ON_WM_CTLCOLOR_REFLECT になります (どのメッセージをリフレクションできるかを確認するには、次の表にあるマクロ エントリに対して逆の変換を実行します)。

上記のルールに対する 3 つの例外は次のとおりです。

  • WM_COMMAND 通知のマクロは ON_CONTROL_REFLECT です。

  • WM_NOTIFY リフレクションのマクロは ON_NOTIFY_REFLECT です。

  • ON_UPDATE_COMMAND_UI リフレクションのマクロは ON_UPDATE_COMMAND_UI_REFLECT です。

上記の特殊な各ケースでは、ハンドラー メンバー関数の名前を指定する必要があります。 それ以外のケースでは、実際のハンドラー関数の標準名を使用する必要があります。

関数のパラメーターと戻り値の意味については、関数名または先頭に On を追加した関数名でドキュメント化されています。 たとえば、CtlColorOnCtlColor でドキュメント化されています。 いくつかのリフレクション メッセージ ハンドラーには、親ウィンドウの同様のハンドラーよりも少ないパラメーターが必要です。 次の表に示されている名前をドキュメント内の仮パラメーターの名前と一致させてください。

マップ エントリ 関数プロトタイプ
ON_CONTROL_REFLECT(wNotifyCode,memberFxn) afx_msg voidmemberFxn( );
ON_NOTIFY_REFLECT(wNotifyCode,memberFxn) afx_msg voidmemberFxn( NMHDR*pNotifyStruct, LRESULT*result);
ON_UPDATE_COMMAND_UI_REFLECT(memberFxn) afx_msg voidmemberFxn( CCmdUI*pCmdUI);
ON_WM_CTLCOLOR_REFLECT( ) afx_msg HBRUSH CtlColor ( CDC*pDC, UINTnCtlColor);
ON_WM_DRAWITEM_REFLECT( ) afx_msg void DrawItem ( LPDRAWITEMSTRUCTlpDrawItemStruct);
ON_WM_MEASUREITEM_REFLECT( ) afx_msg void MeasureItem ( LPMEASUREITEMSTRUCTlpMeasureItemStruct);
ON_WM_DELETEITEM_REFLECT( ) afx_msg void DeleteItem ( LPDELETEITEMSTRUCTlpDeleteItemStruct);
ON_WM_COMPAREITEM_REFLECT( ) afx_msg int CompareItem ( LPCOMPAREITEMSTRUCTlpCompareItemStruct);
ON_WM_CHARTOITEM_REFLECT( ) afx_msg int CharToItem ( UINTnKey, UINTnIndex);
ON_WM_VKEYTOITEM_REFLECT( ) afx_msg int VKeyToItem ( UINTnKey, UINTnIndex);
ON_WM_HSCROLL_REFLECT( ) afx_msg void HScroll ( UINTnSBCode, UINTnPos);
ON_WM_VSCROLL_REFLECT( ) afx_msg void VScroll ( UINTnSBCode, UINTnPos);
ON_WM_PARENTNOTIFY_REFLECT( ) afx_msg void ParentNotify ( UINTmessage, LPARAMlParam);

ON_NOTIFY_REFLECT と ON_CONTROL_REFLECT マクロには、複数のオブジェクト (コントロールやその親など) に特定のメッセージの処理を許可するバリエーションがあります。

マップ エントリ 関数プロトタイプ
ON_NOTIFY_REFLECT_EX(wNotifyCode,memberFxn) afx_msg BOOLmemberFxn( NMHDR*pNotifyStruct, LRESULT*result);
ON_CONTROL_REFLECT_EX(wNotifyCode,memberFxn) afx_msg BOOLmemberFxn( );

リフレクション メッセージの処理: 再利用可能なコントロールの例

この簡単な例では、CYellowEdit という再利用可能なコントロールを作成します。 このコントロールは通常の編集コントロールと同じように動作しますが、黄色の背景の上に黒いテキストを表示する点が異なります。 CYellowEdit コントロールに異なる色を表示させるメンバー関数を簡単に追加できます。

再利用可能なコントロールを作成する例を試すには

  1. 既存のアプリケーションに新しいダイアログ ボックスを作成します。 詳細については、「ダイアログ エディター」トピックを参照してください。

    再利用可能なコントロールを開発するアプリケーションが必要です。 使用できる既存のアプリケーションがない場合は、AppWizard を使用してダイアログベースのアプリケーションを作成します。

  2. ご自分のプロジェクトを Visual C++ に読み込み、ClassWizard を使用して、CEdit に基づいた CYellowEdit という新しいクラスを作成します。

  3. CYellowEdit クラスに 3 つのメンバー変数を追加します。 最初の 2 つは、テキストの色と背景色を保持する COLORREF 変数です。 3 つ目は、背景を塗るためのブラシを保持する CBrush オブジェクトです。 CBrush オブジェクトを使用すると、ブラシを作成したら、その後にそれを参照するだけで済み、CYellowEdit コントロールを破棄したときに自動的にブラシを破棄できます。

  4. コンストラクターを次のように記述して、メンバー変数を初期化します。

    CYellowEdit::CYellowEdit()
    {
        m_clrText = RGB(0, 0, 0);
        m_clrBkgnd = RGB(255, 255, 0);
        m_brBkgnd.CreateSolidBrush(m_clrBkgnd);
    }
    
  5. ClassWizard を使用して、WM_CTLCOLOR リフレクション メッセージのハンドラーを CYellowEdit クラスに追加します。 処理できるメッセージの一覧に含まれているメッセージ名の前にある等号はメッセージがリフレクションされていることを示す点に注意してください。 これについては、「リフレクション メッセージ用のメッセージ ハンドラーの定義」で説明しています。

    ClassWizard により、次のメッセージマップ マクロとスケルトン関数が自動的に追加されます。

    ON_WM_CTLCOLOR_REFLECT()
    // Note: other code will be in between....
    
    HBRUSH CYellowEdit::CtlColor(CDC* pDC, UINT nCtlColor)
    {
        // TODO: Change any attributes of the DC here
        // TODO: Return a non-NULL brush if the
        //       parent's handler should not be called
        return NULL;
    }
    
  6. 関数の本体を次のコードに置き換えます。 このコードでは、テキストの色、テキストの背景色、およびコントロールの他の部分の背景色を指定します。

    pDC->SetTextColor(m_clrText);   // text
    pDC->SetBkColor(m_clrBkgnd);    // text bkgnd
    return m_brBkgnd;               // ctl bkgnd
    
  7. ダイアログ ボックス内に編集コントロールを作成し、Ctrl キーを押したまま編集コントロールをダブルクリックして、それをメンバー変数にアタッチします。 [メンバー変数の追加] ダイアログ ボックスで、完全な変数名を入力し、カテゴリとして [コントロール] を選択し、変数の種類として [CYellowEdit] を選択します。 忘れずに、ダイアログ ボックス内でタブ オーダーを設定してください。 また、必ず CYellowEdit コントロールのヘッダー ファイルをダイアログ ボックスのヘッダー ファイルに含めます。

  8. アプリケーションをビルドして実行します。 編集コントロールの背景は黄色になります。

関連項目

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