テクニカル ノート 61: ON_NOTIFY メッセージと WM_NOTIFY メッセージ
Note
次のテクニカル ノートは、最初にオンライン ドキュメントの一部とされてから更新されていません。 結果として、一部のプロシージャおよびトピックが最新でないか、不正になります。 最新の情報について、オンライン ドキュメントのキーワードで関係のあるトピックを検索することをお勧めします。
このテクニカル ノートでは、新しい WM_NOTIFY メッセージに関する背景情報を提供し、MFC アプリケーションで WM_NOTIFY メッセージを処理する際に推奨される (最も一般的な) 方法について説明します。
Windows 3.x の通知メッセージ
Windows 3.x では、コントロールは親にメッセージを送信することによって、マウスのクリック、コンテンツや選択の変更、コントロールの背景描画などのイベントについて親に通知します。 単純な通知は、特殊な WM_COMMAND メッセージとして送信されますが、このとき、通知コード (BN_CLICKED など) とコントロール ID は wParam にパックされ、コントロールのハンドルは lParam にパックされます。 wParam と lParam は満杯となるので、追加のデータを渡す方法はありません。これらのメッセージは単純な通知にすぎません。 たとえば、BN_CLICKED 通知では、ボタンがクリックされたときの、マウス カーソルの位置に関する情報を送信する方法はありません。
Windows 3.x のコントロールから追加のデータを含む通知メッセージを送信する必要がある場合は、特殊な目的を持つさまざまなメッセージ (WM_CTLCOLOR、WM_VSCROLL、WM_HSCROLL、WM_DRAWITEM、WM_MEASUREITEM、WM_COMPAREITEM、WM_DELETEITEM、WM_CHARTOITEM、WM_VKEYTOITEM など) が使用されます。 これらのメッセージは、その送信元となったコントロールに反映することができます。 詳細については、「テクニカル ノート 62: Windows コントロールへのメッセージ リフレクション」を参照してください。
Win32 の通知メッセージ
Win32 API では、Windows 3.1 に存在したコントロールについては、Windows 3.x で使用された通知メッセージのほとんどが使用されます。 ただし、Win32 では、Windows 3.x でサポートされていたもの以外に、複雑で洗練されたコントロールが追加されています。 多くの場合、これらのコントロールでは、通知メッセージと共に追加のデータを送信する必要があります。 Win32 API の設計者たちは、追加のデータを必要とする新しい通知ごとに新しい WM_* メッセージを追加するのではなく、標準化された方法で任意の量の追加データを渡すことができるメッセージである WM_NOTIFY を 1 つだけ追加することにしました。
WM_NOTIFY メッセージの wParam にはメッセージを送信するコントロールの ID が含まれており、また lParam には構造体へのポインターが含まれています。 この構造体は、NMHDR 構造体か、先頭のメンバーとして NMHDR 構造体を含む、いくぶん大きな構造体のいずれかとなります。 NMHDR メンバーが先頭にあるため、この構造体へのポインターは、キャスト方法に応じて、NMHDR へのポインター、またはより大きな構造体へのポインターのいずれかとして使用できることに注意してください。
ほとんどの場合、ポインターはより大きな構造体を指し、それを使用するときにキャストする必要があります。 実際のところ NMHDR 構造体が使用されるのは、共通の通知 (名前が NM_ で始まる) と、ツール ヒント コントロールの TTN_SHOW 通知や TTN_POP 通知など、いくつかの通知でのみです。
NMHDR 構造体または先頭のメンバーには、メッセージを送信するコントロールのハンドルと ID、および通知コード (TTN_SHOW など) が含まれます。 NMHDR 構造体の形式を次に示します。
typedef struct tagNMHDR {
HWND hwndFrom;
UINT idFrom;
UINT code;
} NMHDR;
TTN_SHOW メッセージであれば、コード メンバーは TTN_SHOW に設定されます。
ほとんどの通知は、先頭のメンバーとして NMHDR 構造体を含むより大きな構造体へのポインターを渡します。 たとえば、リスト ビュー コントロールの LVN_KEYDOWN 通知メッセージで使用される構造体について考えてみましょう。これは、リスト ビュー コントロールでキーが押されたときに送信されます。 ポインターは LV_KEYDOWN 構造体を指します。これは、次に示すように定義されています。
typedef struct tagLV_KEYDOWN {
NMHDR hdr;
WORD wVKey;
UINT flags;
} LV_KEYDOWN;
NMHDR メンバーはこの構造体の先頭にあるので、通知メッセージで渡されるポインターは、NMHDR へのポインターまたは LV_KEYDOWN へのポインターのいずれかにキャストできることに注意してください。
すべての新しい Windows コントロールに共通の通知
一部の通知は、すべての新しい Windows コントロールに共通しています。 これらの通知は、NMHDR 構造体へのポインターを渡します。
通知コード | 送信理由 |
---|---|
NM_CLICK | ユーザーがコントロールでマウスの左ボタンをクリックした |
NM_DBLCLK | ユーザーがコントロールでマウスの左ボタンをダブルクリックした |
NM_RCLICK | ユーザーがコントロールでマウスの右ボタンをクリックした |
NM_RDBLCLK | ユーザーがコントロールでマウスの右ボタンをダブルクリックした |
NM_RETURN | コントロールに入力フォーカスがあるときにユーザーが Enter キーを押した |
NM_SETFOCUS | コントロールに入力フォーカスが与えられた |
NM_KILLFOCUS | コントロールから入力フォーカスが失われた |
NM_OUTOFMEMORY | メモリ不足のために、コントロールで操作を完了できなかった |
ON_NOTIFY: MFC アプリケーションで WM_NOTIFY メッセージを処理する
関数 CWnd::OnNotify
は、通知メッセージを処理します。 既定の実装では、呼び出す通知ハンドラーのメッセージ マップがチェックされます。 一般に、OnNotify
はオーバーライドしません。 代わりに、ハンドラー関数を指定し、そのハンドラーのメッセージ マップ エントリをオーナー ウィンドウのクラスのメッセージ マップに追加します。
ClassWizard では、ClassWizard プロパティ シートを介して ON_NOTIFY メッセージ マップ エントリを作成し、スケルトン ハンドラー関数を提供することができます。 ClassWizard を使用してこれを簡単に行う方法の詳細については、「関数へのメッセージのマッピング」を参照してください。
ON_NOTIFY メッセージ マップ マクロの構文は次のとおりです。
ON_NOTIFY(wNotifyCode, id, memberFxn)
パラメーターは次のとおりです。
wNotifyCode
処理する通知メッセージ (LVN_KEYDOWN など) のコード。
id
通知が送信されるコントロールの子識別子。
memberFxn
この通知を送信したときに呼び出されるメンバー関数。
メンバー関数は、次のプロトタイプを使用して宣言する必要があります。
afx_msg void memberFxn(NMHDR* pNotifyStruct, LRESULT* result);
パラメーターは次のとおりです。
pNotifyStruct
通知構造体へのポインター (上記のセクションを参照)。
result
返す前に設定する結果コードへのポインター。
例
ID が IDC_LIST1
である CListCtrl
からの LVN_KEYDOWN メッセージをメンバー関数 OnKeydownList1
で処理するように指定する場合は、ClassWizard を使用して次の情報をメッセージ マップに追加します。
ON_NOTIFY(LVN_KEYDOWN, IDC_LIST1, OnKeydownList1)
上の例では、ClassWizard によって提供される関数は次のようになります。
void CMessageReflectionDlg::OnKeydownList1(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_KEYDOWN* pLVKeyDow = (LV_KEYDOWN*)pNMHDR;
// TODO: Add your control notification handler
// code here
*pResult = 0;
}
ClassWizard によって、適切な型のポインターが自動的に提供されています。 通知構造体には、pNMHDR または pLVKeyDow を介してアクセスできます。
ON_NOTIFY_RANGE
一連のコントロールに対して同じ WM_NOTIFY メッセージを処理する必要がある場合は、ON_NOTIFY ではなく ON_NOTIFY_RANGE を使用できます。 たとえば、同じアクションを実行して特定の通知メッセージを送信する必要がある一連のボタンがあるとします。
ON_NOTIFY_RANGE を使用すると、範囲の先頭と末尾の子識別子を指定することで、通知メッセージを処理する必要がある連続した子識別子の範囲を指定できます。
ClassWizard では ON_NOTIFY_RANGE の処理は行われません。これを使用するには、自分でメッセージ マップを編集する必要があります。
ON_NOTIFY_RANGE 用のメッセージ マップのエントリと関数プロトタイプを次に示します。
ON_NOTIFY_RANGE(wNotifyCode, id, idLast, memberFxn)
パラメーターは次のとおりです。
wNotifyCode
処理する通知メッセージ (LVN_KEYDOWN など) のコード。
id
連続した識別子の範囲の最初の識別子。
idLast
連続した識別子の範囲の末尾の識別子。
memberFxn
この通知を送信したときに呼び出されるメンバー関数。
メンバー関数は、次のプロトタイプを使用して宣言する必要があります。
afx_msg void memberFxn(UINT id, NMHDR* pNotifyStruct, LRESULT* result);
パラメーターは次のとおりです。
id
通知を送信したコントロールの子識別子。
pNotifyStruct
通知構造体へのポインター (上記を参照)。
result
返す前に設定する結果コードへのポインター。
ON_NOTIFY_EX、ON_NOTIFY_EX_RANGE
通知ルーティング内の複数のオブジェクトでメッセージを処理する場合は、ON_NOTIFY (または ON_NOTIFY_RANGE) ではなく ON_NOTIFY_EX (または ON_NOTIFY_EX_RANGE) を使用できます。 EX バージョンと通常バージョンの唯一の違いは、EX バージョンに対して呼び出されたメンバー関数が、メッセージ処理を続行するかどうかを示す BOOL を返す点にあります。 この関数から FALSE が返ってきた場合、複数のオブジェクトで同じメッセージを処理することができます。
ClassWizard では、ON_NOTIFY_EX や ON_NOTIFY_EX_RANGE の処理は行われません。どちらかを使用する場合は、自分でメッセージ マップを編集する必要があります。
ON_NOTIFY_EX および ON_NOTIFY_EX_RANGE 用のメッセージ マップ エントリと関数プロトタイプを次に示します。 パラメーターの意味は、非 EX バージョンの場合と同じです。
ON_NOTIFY_EX(nCode, id, memberFxn)
ON_NOTIFY_EX_RANGE(wNotifyCode, id, idLast, memberFxn)
上記の両方のプロトタイプは同じです。
afx_msg BOOL memberFxn(UINT id, NMHDR* pNotifyStruct, LRESULT* result);
いずれの場合も、id には通知を送信したコントロールの子識別子が保持されます。
関数では、通知メッセージが完全に処理されている場合は TRUE を返す必要があります。コマンド ルーティング内の他のオブジェクトがメッセージを処理する可能性がある場合は FALSE を返す必要があります。