テクニカル ノート 62: Windows コントロールへのメッセージ リフレクション (メッセージ返送)
更新 : 2007 年 11 月
メモ : |
---|
次のテクニカル ノートは、最初にオンライン ドキュメントの一部とされてから更新されていません。結果として、一部のプロシージャおよびトピックが最新でないか、不正になります。最新の情報について、オンライン ドキュメントのキーワードで関係のあるトピックを検索することをお勧めします。 |
このテクニカル ノートでは、MFC 4.0 の新機能であるメッセージ リフレクション (メッセージ返送) について説明します。メッセージ リフレクションを使用する単純で再利用可能なコントロールの作成手順も説明します。
このテクニカル ノートでは、ActiveX コントロールに当てはまるようなメッセージ リフレクションについては触れていません。「MFC ActiveX コントロール : Windows コントロールのサブクラス化」を参照してください。
メッセージ リフレクション (メッセージ返送) とは
Windows コントロールは、頻繁に親ウィンドウに通知メッセージを送ります。たとえば、多くのコントロールは、コントロールの背景を描画するブラシを親ウィンドウが用意できるようにするため、親ウィンドウにコントロール カラー通知メッセージ (WM_CTLCOLOR またはそのバリエーションの 1 つ) を送ります。
Windows および MFC 4.0 以前のバージョンでは、このようなメッセージの処理は親ウィンドウ (多くの場合はダイアログ ボックス) が受け持っています。このため、メッセージの処理コードを親ウィンドウのクラスに置き、そのメッセージの処理が必要なすべてのクラスにおいて複製することが必要です。この場合、独自の背景を持つコントロールを使用するすべてのダイアログ ボックスでは、コントロール カラー通知メッセージを処理する必要があります。独自の背景色を処理するコントロール クラスを作成しておくと、コードの再利用ははるかに簡単になります。
MFC 4.0 では、従来の機構も引き続き動作します。親ウィンドウで通知メッセージを処理できます。ただし、それに加えて、MFC 4.0 では "メッセージ リフレクション" と呼ばれる機能が用意されており、再利用が簡単になっています。この機能によって、通知メッセージを子コントロール ウィンドウ、親ウィンドウ、またはその両方で処理できます。コントロールの背景色の例では、リフレクションされた WM_CTLCOLOR メッセージを処理することで、親ウィンドウにまったく依存せずに、独自の背景色を設定するコントロール クラスを作成できるようになりました。メッセージ リフレクションは Windows ではなく MFC によって実装されるため、メッセージ リフレクションを動作させるには、CWnd から親ウィンドウ クラスを派生させる必要があります。
以前のバージョンの MFC では、オーナーが描画したリスト ボックスに関するメッセージ (WM_DRAWITEM など) のようないくつかのメッセージについて仮想関数を用意することで、メッセージ リフレクションに似た機能がありました。新しいメッセージ リフレクション機構は汎用化されており、一貫性を持っています。
メッセージ リフレクションには、MFC 4.0 より前のバージョン向けに作成されたコードとの下位互換性があります。
特定のメッセージまたはメッセージの範囲について、親ウィンドウのクラスでハンドラを指定した場合は、作成したハンドラ内で基本クラスのハンドラ関数を呼び出さない限り、同じメッセージについてリフレクション メッセージ ハンドラがオーバーライドされます。たとえば、作成したダイアログ ボックス クラスで 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 で始まる関数名を参照してください。たとえば、CtlColor については OnCtlColor を参照してください。リフレクション メッセージ ハンドラの中には、親ウィンドウ内の同様のハンドラよりも必須となるパラメータの数が少ないものがあります。ドキュメントにある正式なパラメータ名と、次の表にあるパラメータ名を突き合わせてください。
マップ エントリ |
関数プロトタイプ |
---|---|
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 コントロールでは、別の色を表示できるようにするためのメンバ関数を簡単に追加できます。
再利用可能なコントロールの作成例を試すには
既存のアプリケーション内で新規のダイアログ ボックスを作成します。詳細については、「ダイアログ エディタ」を参照してください。
再利用可能なコントロールの開発に使用するアプリケーションを用意する必要があります。既存アプリケーションを使用しない場合は、AppWizard を使用してダイアログ ベースのアプリケーションを作成します。
プロジェクトを Visual C++ に読み込み、ClassWizard を使用して、CEdit に基づいて CYellowEdit という新規のクラスを作成します。
CYellowEdit クラスに 3 つのメンバ変数を追加します。最初の 2 つは、文字の色と背景色を保持する COLORREF 変数です。3 番目は、背景を描画するブラシを保持する CBrush オブジェクトです。CBrush オブジェクトを使用すると、ブラシをいったん作成し、その後は参照するだけで、CYellowEdit コントロールが破棄されるときにブラシを自動的に破棄できます。
次のようなコンストラクタを記述し、メンバ変数を初期化します。
CYellowEdit::CYellowEdit() { m_clrText = RGB( 0, 0, 0 ); m_clrBkgnd = RGB( 255, 255, 0 ); m_brBkgnd.CreateSolidBrush( m_clrBkgnd ); }
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; }
関数本体を次のコードに置き換えます。このコードでは文字の色、文字の背景色、およびコントロールの残りの部分の背景色を指定しています。
pDC->SetTextColor( m_clrText ); // text pDC->SetBkColor( m_clrBkgnd ); // text bkgnd return m_brBkgnd; // ctl bkgnd
ダイアログ ボックスのエディット コントロールを作成し、Ctrl キーを押しながらエディット コントロールをダブルクリックし、メンバ変数にアタッチします。[メンバ変数の追加] ダイアログ ボックスで、変数名を入力し、カテゴリに "コントロール"を選択し、変数の種類に "CYellowEdit"を選択します。このダイアログ ボックスでは、タブ オーダーを必ず設定するようにしてください。また、ダイアログ ボックスのヘッダー ファイルに CYellowEdit コントロールのヘッダー ファイルを含める必要があります。
アプリケーションをビルドして実行します。エディット コントロールが黄色の背景になります。