注
次のテクニカル ノートは、最初にオンライン ドキュメントに含まれてから更新されていません。 その結果、一部の手順やトピックが古くなっているか、正しくない可能性があります。 最新情報については、オンライン ドキュメント インデックスで関心のあるトピックを検索することをお勧めします。
このメモでは、コマンド ルーティングとディスパッチ アーキテクチャと、一般的なウィンドウ メッセージ ルーティングの高度なトピックについて説明します。
ここで説明するアーキテクチャの一般的な詳細、特に Windows メッセージ、制御通知、コマンドの違いについては、Visual C++ を参照してください。 このメモは、印刷されたドキュメントで説明されている問題に精通しており、非常に高度なトピックにのみ対処していることを前提としています。
コマンド ルーティングとディスパッチ MFC 1.0 機能が MFC 2.0 アーキテクチャに進化
Windows には、メニュー コマンド、アクセラレータ キー、ダイアログ コントロール通知の通知を提供するためにオーバーロードされたWM_COMMAND メッセージがあります。
MFC 1.0 は、 CWnd
派生クラスのコマンド ハンドラー ("OnFileNew" など) が特定のWM_COMMANDに応答して呼び出されるようにすることで、その上に少し構築されています。 これはメッセージ マップと呼ばれるデータ構造と一緒に接着され、非常にスペース効率の高いコマンド メカニズムになります。
MFC 1.0 には、コントロール通知をコマンド メッセージから分離するための追加機能も用意されています。 コマンドは 16 ビット ID (コマンド ID とも呼ばれます) で表されます。 コマンドは通常、 CFrameWnd
(メニュー選択または翻訳されたアクセラレータ) から開始され、他のさまざまなウィンドウにルーティングされます。
MFC 1.0 では、マルチ ドキュメント インターフェイス (MDI) の実装に限られた意味でコマンド ルーティングを使用しました。 (MDI フレーム ウィンドウは、アクティブな MDI 子ウィンドウにコマンドを委任します)。
この機能は MFC 2.0 で一般化および拡張されており、ウィンドウ オブジェクトだけでなく、より広範なオブジェクトでコマンドを処理できるようになりました。 メッセージをルーティングするためのより正式で拡張可能なアーキテクチャを提供し、コマンドの処理だけでなく、コマンドの現在の可用性を反映するように UI オブジェクト (メニュー項目やツール バー ボタンなど) を更新するために、コマンド ターゲット ルーティングを再利用します。
コマンド ID
コマンド ルーティングとバインド プロセスの説明については、Visual C++ を参照してください。 テクニカル ノート 20 には、ID の名前付けに関する情報が含まれています。
コマンド ID には、汎用プレフィックス "ID_" を使用します。 コマンド ID は >= 0x8000です。 コマンド ID と同じ ID を持つ STRINGTABLE リソースがある場合は、メッセージ行またはステータス バーにコマンド記述文字列が表示されます。
アプリケーションのリソースでは、コマンド ID が複数の場所に表示される場合があります。
メッセージ行プロンプトと同じ ID を持つ 1 つの STRINGTABLE リソース。
場合によっては、同じコマンドを呼び出すメニュー項目にアタッチされている多くの MENU リソース。
GOSUB コマンドのダイアログ ボタンの (ADVANCED)。
アプリケーションのソース コードでは、コマンド ID が複数の場所に表示される場合があります。
リソース内。アプリケーション固有のコマンド ID を定義するための H (またはその他のメイン シンボル ヘッダー ファイル)。
おそらく、ツールバーの作成に使用される ID 配列内。
ON_COMMAND マクロ内。
おそらく、ON_UPDATE_COMMAND_UIマクロで。
現時点では、コマンド ID を >= 0x8000必要とする MFC の唯一の実装は、GOSUB ダイアログ/コマンドの実装です。
ダイアログでコマンド アーキテクチャを使用する GOSUB コマンド
コマンドのルーティングと有効化のコマンド アーキテクチャは、フレーム ウィンドウ、メニュー項目、ツール バー ボタン、ダイアログ バー ボタン、その他のコントロール バー、その他のユーザー インターフェイス要素を使用して、オンデマンドで更新し、コマンドまたはコントロール ID をメイン コマンド ターゲット (通常はメイン フレーム ウィンドウ) にルーティングするように設計されています。 そのメイン コマンド ターゲットは、必要に応じて、コマンドまたはコントロール通知を他のコマンド ターゲット オブジェクトにルーティングできます。
ダイアログ (モーダルまたはモードレス) は、ダイアログ コントロールのコントロール ID を適切なコマンド ID に割り当てると、コマンド アーキテクチャの一部の機能の恩恵を受けることができます。 ダイアログのサポートは自動ではないため、追加のコードを記述する必要がある場合があります。
これらすべての機能が正常に機能するためには、コマンド ID を >= 0x8000にする必要があります。 多くのダイアログが同じフレームにルーティングされる可能性があるため、共有コマンドは >= 0x8000、特定のダイアログ内の非共有 IDC は <= 0x7FFFにする必要があります。
ボタンの IDC を適切なコマンド ID に設定して、標準のモーダル ダイアログに標準ボタンを配置できます。 ユーザーがボタンを選択すると、ダイアログの所有者 (通常はメイン フレーム ウィンドウ) は、他のコマンドと同様にコマンドを取得します。 通常は別のダイアログ (最初のダイアログの GOSUB) を表示するために使用されるため、これは GOSUB コマンドと呼ばれます。
ダイアログで関数 CWnd::UpdateDialogControls
を呼び出し、メイン フレーム ウィンドウのアドレスを渡すこともできます。 この関数は、フレームにコマンド ハンドラーがあるかどうかに基づいて、ダイアログ コントロールを有効または無効にします。 この関数は、アプリケーションのアイドル ループ内のコントロール バーに対して自動的に呼び出されますが、この機能を使用する通常のダイアログでは直接呼び出す必要があります。
ON_UPDATE_COMMAND_UIが呼び出されたとき
プログラムのすべてのメニュー項目の有効/チェック状態を常に維持することは、計算コストの高い問題になる可能性があります。 一般的な方法は、ユーザーが POPUP を選択した場合にのみ、メニュー項目を有効またはチェックすることです。
CFrameWnd
の MFC 2.0 実装は、WM_INITMENUPOPUP メッセージを処理し、コマンド ルーティング アーキテクチャを使用して、ON_UPDATE_COMMAND_UI ハンドラーを介してメニューの状態を判断します。
CFrameWnd
また、WM_ENTERIDLE メッセージを処理して、ステータス バーで選択されている現在のメニュー項目 (メッセージ行とも呼ばれます) を説明します。
Visual C++ によって編集されたアプリケーションのメニュー構造は、WM_INITMENUPOPUP時に使用可能な潜在的なコマンドを表すために使用されます。 ON_UPDATE_COMMAND_UIハンドラーは、メニューの状態やテキストを変更したり、高度な用途 ([ファイル MRU] リストや [OLE 動詞] ポップアップ メニューなど) に対して、メニューを描画する前にメニュー構造を実際に変更したりできます。
アプリケーションがアイドル ループに入ると、ツールバー (およびその他のコントロール バー) に対して同じ種類のON_UPDATE_COMMAND_UI処理が実行されます。 コントロール バーの詳細については、 クラス ライブラリ リファレンス と テクニカル ノート 31 を参照してください。
入れ子になったポップアップ メニュー
入れ子になったメニュー構造を使用している場合は、ポップアップ メニューの最初のメニュー項目のON_UPDATE_COMMAND_UI ハンドラーが 2 つの異なるケースで呼び出されます。
まず、ポップアップ メニュー自体に対して呼び出されます。 これは、ポップアップ メニューに ID がないために必要です。ポップアップ メニューの最初のメニュー項目の ID を使用して、ポップアップ メニュー全体を参照します。 この場合、CCmdUI
オブジェクトのm_pSubMenuメンバー変数は NULL 以外になり、ポップアップ メニューをポイントします。
次に、ポップアップ メニューのメニュー項目が描画される直前に呼び出されます。 この場合、ID は最初のメニュー項目のみを参照し、CCmdUI
オブジェクトの m_pSubMenu メンバー変数は NULL になります。
これにより、メニュー項目とは異なるポップアップ メニューを有効にすることができますが、メニューに対応するコードを記述する必要があります。 たとえば、次の構造の入れ子になったメニューの場合、
File>
New>
Sheet (ID_NEW_SHEET)
Chart (ID_NEW_CHART)
ID_NEW_SHEETコマンドとID_NEW_CHART コマンドは、個別に有効または無効にすることができます。 2 つのいずれかが有効になっている場合は、[ 新しい ] ポップアップ メニューを有効にする必要があります。
ID_NEW_SHEETのコマンド ハンドラー (ポップアップの最初のコマンド) は次のようになります。
void CMyApp::OnUpdateNewSheet(CCmdUI* pCmdUI)
{
if (pCmdUI->m_pSubMenu != NULL)
{
// enable entire pop-up for "New" sheet and chart
BOOL bEnable = m_bCanCreateSheet || m_bCanCreateChart;
// CCmdUI::Enable is a no-op for this case, so we
// must do what it would have done.
pCmdUI->m_pMenu->EnableMenuItem(pCmdUI->m_nIndex,
MF_BYPOSITION |
(bEnable MF_ENABLED : (MF_DISABLED | MF_GRAYED)));
return;
}
// otherwise just the New Sheet command
pCmdUI->Enable(m_bCanCreateSheet);
}
ID_NEW_CHARTのコマンド ハンドラーは通常の更新コマンド ハンドラーであり、次のようになります。
void CMyApp::OnUpdateNewChart(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_bCanCreateChart);
}
ON_COMMANDとON_BN_CLICKED
ON_COMMANDとON_BN_CLICKEDのメッセージ マップ マクロは同じです。 MFC コマンドおよび制御通知ルーティング メカニズムでは、コマンド ID のみを使用してルーティング先を決定します。 コントロール通知コードが 0 (BN_CLICKED) のコントロール通知は、コマンドとして解釈されます。
注
実際、すべてのコントロール通知メッセージは、コマンド ハンドラー チェーンを通過します。 たとえば、ドキュメント クラスで EN_CHANGE のコントロール通知ハンドラーを記述することは技術的に可能です。 この機能の実用的なアプリケーションは少なく、ClassWizard では機能がサポートされておらず、機能を使用するとコードが脆弱になることがありますので、これは一般的には推奨されません。
ボタン コントロールの自動無効化を無効にする
独自に CWnd::UpdateDialogControls を呼び出す場所を使用してダイアログ バーまたはダイアログにボタン コントロールを配置すると、 ON_COMMAND または ON_UPDATE_COMMAND_UI ハンドラーを持たないボタンは、フレームワークによって自動的に無効になります。 場合によっては、ハンドラーを用意する必要はありませんが、ボタンを有効のままにしておく必要があります。 これを実現する最も簡単な方法は、ダミーのコマンド ハンドラー (ClassWizard で簡単に行うことができます) を追加し、その中で何も行わない方法です。
ウィンドウ メッセージ ルーティング
次に、MFC クラスに関するいくつかのより高度なトピックと、Windows メッセージ ルーティングとその他のトピックがそれらに与える影響について説明します。 ここでの情報は、簡単に説明するだけです。 パブリック API の詳細については、 クラス ライブラリ リファレンス を参照してください。 実装の詳細については、MFC ライブラリのソース コードを参照してください。
すべての CWnd 派生クラスの非常に重要なトピックである Window クリーンアップの詳細については、テクニカル ノート 17 を参照してください。
CWnd の問題
実装メンバー関数 CWnd::OnChildNotify は、子ウィンドウ (コントロールとも呼ばれます) の強力で拡張可能なアーキテクチャを提供し、親 (または "所有者") に送信されるメッセージ、コマンド、および制御通知をフックしたり、通知を受け取ったりします。 子ウィンドウ (/control) が C++ CWnd オブジェクト自体の場合、仮想関数 OnChildNotify は、最初に元のメッセージ (つまり MSG 構造体) のパラメーターを使用して呼び出されます。 子ウィンドウでは、メッセージをそのまま残したり、メッセージを食べたり、親のメッセージを変更したりできます (まれ)。
既定の CWnd 実装は、次のメッセージを処理し、 OnChildNotify フックを使用して、子ウィンドウ (コントロール) がメッセージで最初にアクセスできるようにします。
WM_MEASUREITEM と WM_DRAWITEM (自己描画用)
WM_COMPAREITEM と WM_DELETEITEM (自己描画用)
WM_HSCROLL と WM_VSCROLL
WM_CTLCOLOR
WM_PARENTNOTIFY
OnChildNotify フックは、所有者描画メッセージを自己描画メッセージに変更するために使用されます。
OnChildNotify フックに加えて、スクロール メッセージにはさらにルーティング動作があります。 スクロール バーとWM_HSCROLLメッセージと WM_VSCROLLメッセージの ソースの詳細については、以下を参照してください。
CFrameWnd の問題
CFrameWnd クラスは、ほとんどのコマンド ルーティングとユーザー インターフェイス更新の実装を提供します。 これは主にアプリケーションのメイン フレーム ウィンドウ (CWinApp::m_pMainWnd) に使用されますが、すべてのフレーム ウィンドウに適用されます。
メイン フレーム ウィンドウは、メニュー バーのあるウィンドウであり、ステータス バーまたはメッセージ行の親です。 コマンド ルーティングとWM_INITMENUPOPUPについては、上記の説明を参照してください 。
CFrameWnd クラスは、アクティブなビューの管理を提供します。 次のメッセージは、アクティブなビューを介してルーティングされます。
すべてのコマンド メッセージ (アクティブなビューは、それらに最初にアクセスします)。
兄弟スクロール バーからのメッセージをWM_HSCROLLしてWM_VSCROLLします (下記参照)。
WM_ACTIVATE (および MDI の WM_MDIACTIVATE ) は、仮想関数 CView::OnActivateView の呼び出しに変換されます。
CMDIFrameWnd/CMDIChildWnd の問題
両方の MDI フレーム ウィンドウ クラスは CFrameWnd から派生するため、 CFrameWnd で提供される同じ種類のコマンド ルーティングとユーザー インターフェイスの更新に対して両方が有効になります。 一般的な MDI アプリケーションでは、メイン フレーム ウィンドウ (つまり 、CMDIFrameWnd オブジェクト) のみがメニュー バーとステータス バーを保持するため、コマンド ルーティング実装のメイン ソースになります。
一般的なルーティングスキームは、アクティブな MDI 子ウィンドウがコマンドへの最初のアクセスを取得することです。 既定 の PreTranslateMessage 関数は、MDI 子ウィンドウ (最初) と MDI フレーム (2 番目) の両方のアクセラレータ テーブルと、 TranslateMDISysAccel (最後) によって通常処理される標準の MDI システム コマンド アクセラレータを処理します。
スクロール バーの問題
スクロール メッセージ (WM_HSCROLL/OnHScroll や WM_VSCROLL/OnVScroll) を処理する場合は、スクロール バー メッセージの発生元に依存しないようにハンドラー コードを記述する必要があります。 スクロール メッセージは、実際のスクロール バー コントロールから、またはスクロール バー コントロールではないWS_HSCROLL/WS_VSCROLLスクロール バーから送信される可能性があるため、これは一般的な Windows の問題だけではありません。
MFC は、スクロール バー コントロールをスクロールするウィンドウの子または兄弟のいずれかにできるように拡張します (実際には、スクロール バーとスクロールされるウィンドウの間の親子関係は何でもかまいません)。 これは、分割ウィンドウを使用する共有スクロール バーで特に重要です。 共有スクロール バーの問題の詳細を含む CSplitterWnd の実装の詳細については、テクニカル ノート 29 を参照してください。
サイド ノートには、作成時に指定されたスクロール バー スタイルがトラップされ、Windows に渡されない 2 つの CWnd 派生クラスがあります。 作成ルーチンに渡された場合、 WS_HSCROLL と WS_VSCROLL は個別に設定できますが、作成後は変更できません。 もちろん、作成したウィンドウのWS_SCROLL スタイル ビットを直接テストまたは設定しないでください。
CMDIFrameWnd の場合、Create または LoadFrame に渡すスクロール バー スタイルは、MDICLIENT の作成に使用されます。 スクロール可能な MDICLIENT 領域 (Windows プログラム マネージャーなど) を使用する場合は、CMDIFrameWnd の作成に使用するスタイルの両方のスクロール バー スタイル (WS_HSCROLL | WS_VSCROLL
) を設定してください。
CSplitterWnd の場合、スクロール バーのスタイルは、分割領域の特殊な共有スクロール バーに適用されます。 静的分割ウィンドウの場合、通常、どちらのスクロール バー スタイルも設定されません。 動的分割ウィンドウでは、通常、分割する方向にスクロール バー スタイルが設定されます。つまり、行を分割できる場合は WS_HSCROLL 、列を分割できる場合 はWS_VSCROLL 。