次の方法で共有


TN062: Windows コントロールのメッセージ リフレクション

次のテクニカル ノートは、最初にオンライン ドキュメントに含まれてから更新されていません。 その結果、一部の手順やトピックが古くなっているか、正しくない可能性があります。 最新情報については、オンライン ドキュメント インデックスで関心のあるトピックを検索することをお勧めします。

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

このテクニカル ノートでは、ActiveX コントロール (旧称 OLE コントロール) に適用されるメッセージリフレクションについては説明しません。 「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 メッセージについては、「TN061: ON_NOTIFYおよびWM_NOTIFY メッセージ」を参照してください。

Message-Map エントリとハンドラー関数のプロトタイプ (反射メッセージ)

反映された制御通知メッセージを処理するには、次の表に示すメッセージ マップ マクロと関数プロトタイプを使用します。

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) BOOL をafx_msgしますmemberFxn( NMHDR*pNotifyStruct、LRESULT*result);
ON_CONTROL_REFLECT_EX(wNotifyCode,memberFxn) BOOL をafx_msgしますmemberFxn( );

反映されたメッセージの処理: 再利用可能なコントロールの例

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

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

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

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

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

  3. CYellowEdit クラスに 3 つのメンバー変数を追加します。 最初の 2 つは、テキストの色と背景色を保持する COLORREF 変数です。 3 つ目は、背景を描画するためのブラシを保持する CBrush オブジェクトです。 CBrush オブジェクトを使用すると、ブラシを 1 回作成し、その後に参照するだけで、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. ダイアログ ボックスで編集コントロールを作成し、コントロール キーを押しながら編集コントロールをダブルクリックしてメンバー変数にアタッチします。 [メンバー変数の追加] ダイアログ ボックスで、変数名を終了し、カテゴリの [コントロール] を選択し、変数の型として "CYellowEdit" を選択します。 ダイアログ ボックスでタブ オーダーを設定することを忘れないでください。 また、ダイアログ ボックスのヘッダー ファイルには、 CYellowEdit コントロールのヘッダー ファイルを含めるようにしてください。

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

こちらも参照ください

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