Win32 と WPF 間でのメッセージ ループの共有
更新 : 2007 年 11 月
ここでは、Windows Presentation Foundation (WPF) と相互運用するようにメッセージ ループを実装する方法について説明します。Dispatcher で公開されている既存のメッセージ ループを使用する方法と、相互運用コードの Win32 側で別のメッセージ ループを作成する方法があります。
ComponentDispatcher とメッセージ ループ
相互運用とキーボード イベント サポートの一般的なシナリオでは、IKeyboardInputSink を実装するか、IKeyboardInputSink を既に実装している HwndSource や HwndHost などのクラスからサブクラス化します。しかし、キーボード シンク サポートは、相互運用の境界にまたがるメッセージ送受信時のメッセージ ループの要件のすべてに対応しているとは限りません。アプリケーションのメッセージ ループ アーキテクチャの形式化を支援するために、Windows Presentation Foundation (WPF) には、メッセージ ループが従う単純なプロトコルを定義する ComponentDispatcher クラスが用意されています。
ComponentDispatcher は、複数のメンバを公開する静的クラスです。各メソッドのスコープは、呼び出し元のスレッドに暗黙的に関連付けられます。メッセージ ループでは、重要な局面 (次のセクションで定義されています) で、それらの API のいくつかを呼び出す必要があります。
ComponentDispatcher から提供されるイベントは、他のコンポーネント (キーボード シンクなど) でリッスンできます。Dispatcher クラスは、すべての適切な ComponentDispatcher メソッドを適切な順序で呼び出します。独自のメッセージ ループを実装する場合は、コードで同様に ComponentDispatcher メソッドを呼び出す必要があります。
スレッドで ComponentDispatcher メソッドを呼び出すと、そのスレッドに登録されたイベント ハンドラだけが呼び出されます。
メッセージ ループの記述
独自のメッセージ ループを記述する場合に使用する ComponentDispatcher メンバのチェック リストを次に示します。
PushModal: 独自のメッセージ ループで、スレッドがモーダルであることを示す際に呼び出す必要があります。
PopModal: 独自のメッセージ ループで、スレッドがモーダルでなくなったことを示す際に呼び出す必要があります。
RaiseIdle: 独自のメッセージ ループで、ComponentDispatcher が ThreadIdle イベントを発生させる必要があることを示す際に呼び出す必要があります。IsThreadModal が true の場合、ComponentDispatcher は ThreadIdle を発生させません。しかし、モーダル状態であるために ComponentDispatcher が応答できない場合でも、メッセージ ループは RaiseIdle を呼び出すことができます。
RaiseThreadMessage: 独自のメッセージ ループで、新しいメッセージが使用できることを示す際に呼び出す必要があります。戻り値は、ComponentDispatcher イベントのリスナによってメッセージが処理されたかどうかを示します。RaiseThreadMessage が true (処理済み) を返す場合、ディスパッチャはそのメッセージをこれ以上処理する必要がありません。戻り値が false の場合、ディスパッチャは、Win32 関数を呼び出し、次に TranslateMessageDispatchMessage を呼び出します。
ComponentDispatcher と既存のメッセージ処理の使用
WPF の既存のメッセージ ループを利用する場合に使用する ComponentDispatcher メンバのチェックリストを次に示します。
IsThreadModal: アプリケーションがモーダルになったかどうか (モーダル メッセージ ループがプッシュされたなど) を返します。ComponentDispatcher は、メッセージ ループからの PushModal 呼び出しと PopModal 呼び出しの回数を保持するため、この状態を追跡できます。
ThreadFilterMessage イベントと ThreadPreprocessMessage イベントは、デリゲート呼び出しの標準規則に従います。デリゲートは不特定の順番で呼び出され、最初のデリゲートがメッセージを処理済みとしてマークしても、すべてのデリゲートが呼び出されます。
ThreadIdle: アイドル処理を行うのに適切かつ効率的なタイミング (保留状態の他のメッセージがスレッドに存在しない) を示します。スレッドがモーダルの場合、ThreadIdle は発生しません。
ThreadFilterMessage: メッセージ ポンプが処理するすべてのメッセージに対して発生します。
ThreadPreprocessMessage: ThreadFilterMessage の間に処理されなかったすべてのメッセージに対して発生します。
ThreadFilterMessage イベントまたは ThreadPreprocessMessage イベントの後に参照渡しされる、イベント データの handled パラメータが true の場合、メッセージは処理済みであると見なされます。handled が true の場合は、他のイベント ハンドラが先にそのメッセージを処理したことを意味するため、ハンドラはそのメッセージを無視する必要があります。どちらのイベントに対するイベント ハンドラも、メッセージを変更する可能性があります。ディスパッチャは、変更前の元のメッセージではなく、変更後のメッセージをディスパッチする必要があります。ThreadPreprocessMessage はすべてのリスナに配信されます。ただし、アーキテクチャでは、メッセージの対象の HWND を含むトップレベルのウィンドウだけがメッセージに応じてコードを呼び出すことが意図されています。
HwndSource による ComponentDispatcher イベントの処理方法
HwndSource は、トップレベルのウィンドウである (親 HWND を持たない) 場合、ComponentDispatcher に登録されます。ThreadPreprocessMessage が発生し、そのメッセージの対象が HwndSource または子ウィンドウの場合、HwndSource はその IKeyboardInputSink.TranslateAccelerator、TranslateChar、OnMnemonic のキーボード シンク シーケンスを呼び出します。
HwndSource がトップレベルのウィンドウではない (親 HWND を持つ) 場合、処理は行われません。トップレベルのウィンドウだけが処理の実行を求められます。また、どのようなシナリオの相互運用でも、キーボード シンクをサポートするトップレベルのウィンドウが存在することが求められます。
適切なキーボード シンク メソッドが先に呼び出されずに WndProc が HwndSource に対して呼び出された場合、アプリケーションは KeyDown などの上位のキーボード イベントを受け取ります。ただし、キーボード シンク メソッドは呼び出されません。これにより、アクセス キー サポートなどの適切なキーボード入力モデルが迂回されます。メッセージ ループが ComponentDispatcher の適切なスレッドへの通知を正しく行わなかった場合、または親 HWND が正しいキーボード シンク応答を呼び出さなかった場合に、これが発生することがあります。
キーボード シンクに送られるメッセージに AddHook を使用してフックを追加した場合、このメッセージは HWND に送信されないことがあります。このメッセージは、メッセージ パンプ レベルで直接処理され、DispatchMessage 関数に送信されないことがあります。