WPF と Win32 の相互運用性

このトピックでは、WPF および Win32 コードを相互運用する方法の概要について説明します。 Windows Presentation Foundation (WPF) は、アプリケーションの作成に適した環境を提供します。 ただし、Win32 コードに多くの投資を行った場合は、そのコードの一部を再利用する方がより効率的である場合があります。

このトピックは、次のセクションで構成されています。

  • WPF と Win32 の相互運用の基本
  • WPF 相互運用プロジェクト
  • WPF での Hwnd の使用方法
  • Microsoft Win32 ウィンドウでの WPF コンテンツのホスト
  • WPF での Microsoft Win32 ウィンドウのホスト
  • Tab キーによる移動、ニーモニック、およびアクセラレータ
  • 関連トピック

WPF と Win32 の相互運用の基本

WPF と Win32 コードを相互運用するための基本的な手法は 2 つあります。

  • Win32 ウィンドウで WPF コンテンツをホストします。 この手法では、標準の Win32 ウィンドウおよびアプリケーションのフレームワーク内で WPF の高度なグラフィックス機能を使用できます。

  • WPF コンテンツで Win32 ウィンドウをホストします。 この手法では、既存のカスタム Win32 コントロールを他の WPF コンテンツのコンテキストで使用し、境界を越えてデータを渡すことができます。

ここでは、これらの各手法について概念的に説明します。 Win32 で WPF をホストする場合のコードを使用した説明については、「チュートリアル: Win32 での WPF コンテンツのホスト」を参照してください。 WPF で Win32 をホストする場合のコードを使用した説明については、「チュートリアル: WPF での Win32 コントロールのホスト」を参照してください。

WPF 相互運用プロジェクト

WPF APIs はマネージ コードですが、ほとんどの既存の Win32 プログラムはアンマネージ C++ で記述されています。 純粋なアンマネージ プログラムから WPF APIs を呼び出すことはできません。 ただし、Microsoft Visual C++ コンパイラで /clr オプションを使用すると、マネージとアンマネージの API 呼び出しをシームレスに混在させることができる、マネージとアンマネージの混在プログラムを作成することができます。

プロジェクト レベルで、Extensible Application Markup Language (XAML) ファイルを C++ プロジェクトにコンパイルできないという問題があります。 これを解決するために、プロジェクトを分割する手法がいくつかあります。

  • すべての XAML ページを含む C# DLL をコンパイル済みのアセンブリとして作成し、C++ 実行可能ファイルにその DLL を参照として含めます。

  • WPF コンテンツの C# 実行可能ファイルを作成し、そのファイルが Win32 コンテンツを含む C++ DLL を参照するようにします。

  • XAML をコンパイルする代わりに、Load を使用して、XAML を実行時に読み込みます。

  • XAML を使用せずに、すべての WPF をコードで記述し、Application から要素ツリーを構築します。

これらの中で最適な方法を使用してください。

メモメモ

C++/CLI を使用したことがない場合は、相互運用のコード例に gcnew や nullptr などの "見慣れない" キーワードがあるかもしれません。古い二重アンダースコアの構文 (__gc) がこれらのキーワードによって置き換えられています。これらのキーワードを使用すると、C++ のマネージ コードの構文がより自然になります。C++/CLI マネージ機能の詳細については、「Language Features for Targeting the CLR」および「Hello, C++/CLI」を参照してください。

WPF での Hwnd の使用方法

ほとんどの WPF "HWND 相互運用" を行うには、WPF での HWND の使用方法を理解する必要があります。 HWND では、WPF レンダリングと DirectX レンダリングまたは GDI/GDI+ レンダリングを混在させることはできません。 混在させると、さまざまな影響が生じます。 主に、これらのレンダリング モデルを混在させるには、相互運用ソリューションを作成し、使用するレンダリング モデルごとに指定された相互運用セグメントを使用する必要があります。 また、相互運用ソリューションで実現できる動作に対する "空域" 制限がレンダリング動作によって生じます。 "空域" の概念の詳細については、「技術領域の概要」を参照してください。

画面上のすべての WPF 要素が最終的に HWND によってサポートされます。 WPF Window を作成すると、WPF によってトップレベルの HWND が作成され、HwndSource を使用して Window とその WPF コンテンツが HWND 内に格納されます。 アプリケーション内の残りの WPF コンテンツは、その単一の HWND を共有します。 ただし、メニュー、コンボ ボックス ドロップダウン、およびその他のポップアップは例外です。 これらの要素では独自のトップレベル ウィンドウが作成されます。このため、WPF メニューは、そのメニューを格納するウィンドウ HWND の端を越える可能性があります。 HwndHost を使用して HWND を WPF 内に格納すると、WPF Window HWND に対して新しい子 HWND を配置する方法が WPF から Win32 に通知されます。

HWND の関連概念として、各 HWND 内および各 HWND 間の透過性が挙げられます。 これは、「技術領域の概要」でも解説されています。

Microsoft Win32 ウィンドウでの WPF コンテンツのホスト

Win32 ウィンドウで WPF をホストするキーとなるのは、HwndSource クラスです。 このクラスは Win32 ウィンドウの WPF コンテンツをラップし、その WPF コンテンツを子ウィンドウとしてuser interface (UI) に組み込むことができます。 次の方法では、Win32 および WPF を単一のアプリケーションに統合します。

  1. WPF コンテンツ (コンテンツのルート要素) をマネージ クラスとして実装します。 通常、このクラスは、複数の子要素を格納できるクラスやルート要素として使用されるクラスのいずれか (DockPanelPage など) から継承されます。 以降の手順では、このクラスを WPF コンテンツ クラスと呼び、そのクラスのインスタンスを WPF コンテンツ オブジェクトと呼びます。

  2. Win32 アプリケーションを C++/CLI で実装します。 既存のアンマネージ C++ アプリケーションから始める場合、通常、/clr コンパイラ フラグが含まれるようにプロジェクトの設定を変更して、アプリケーションでマネージ コードを呼び出すことができるようにできます (/clr コンパイルをサポートするための要件の詳細については、ここでは説明しません)。

  3. スレッド処理モデルをシングル スレッド アパートメント (STA: Single Threaded Apartment) に設定します。 このスレッド処理モデルを使用するのは WPF のみです。

  4. ウィンドウ プロシージャで WM_CREATE 通知を処理します。

  5. ハンドラー (またはハンドラーが呼び出す関数) で、次の手順を実行します。

    1. parent パラメーターとして親ウィンドウ HWND を持つ新しい HwndSource オブジェクトを作成します。

    2. WPF コンテンツ クラスのインスタンスを作成します。

    3. WPF コンテンツ オブジェクトへの参照を HwndSource オブジェクトの RootVisual プロパティに割り当てます。

    4. HwndSource オブジェクトの Handle プロパティにウィンドウ ハンドル (HWND) が格納されます。 アプリケーションのアンマネージ部分で使用できる HWND を取得するには、Handle.ToPointer() を HWND にキャストします。

  6. WPF コンテンツ オブジェクトへの参照を保持する静的フィールドを含むマネージ クラスを実装します。 このクラスを使用すると、Win32 コードから WPF コンテンツ オブジェクトへの参照を取得できますが、さらに、HwndSource が不注意によりガベージ コレクトされることを回避できます。

  7. ハンドラーを 1 つ以上の WPF コンテンツ オブジェクト イベントにアタッチして、WPF コンテンツ オブジェクトから通知を受信します。

  8. 静的フィールドに格納した参照を使用してプロパティの設定やメソッドの呼び出しなどを行い、WPF コンテンツ オブジェクトと通信します。

メモメモ

個別のアセンブリを生成して参照する場合、手順 1. の WPF コンテンツ クラスの定義の一部またはすべてを、XAML でコンテンツ クラスの既定の部分クラスを使用して行うことができます。通常は、Application オブジェクトを XAML のコンパイルの一部としてアセンブリに含めますが、その Application を相互運用の一部として使用することにはなりません。単に、アプリケーションによって参照される XAML ファイルの 1 つ以上のルート クラスを使用し、その部分クラスを参照します。手順の残りの部分は、基本的に前述の手順と同様です。

これらの各手順のコードを使用した説明については、「チュートリアル: Win32 での WPF コンテンツのホスト」を参照してください。

WPF での Microsoft Win32 ウィンドウのホスト

他の WPF コンテンツ内で Win32 ウィンドウをホストするキーとなるのは、HwndHost クラスです。 このクラスは、WPF 要素ツリーに追加できる WPF 要素でウィンドウをラップします。 また、HwndHost は、ホストされたウィンドウのメッセージの処理などのタスクを実行できるようにする APIs もサポートします。 基本手順は次のとおりです。

  1. WPF アプリケーションの要素ツリーを作成します (コードまたはマークアップで作成できます)。 HwndHost の実装を子要素として追加できる適切な許可ポイントを要素ツリーで検索します。 この手順では以後、この要素を予約要素と呼びます。

  2. HwndHost からの派生クラスを作成し、Win32 コンテンツを保持するオブジェクトを作成します。

  3. そのホスト クラスで、HwndHostBuildWindowCore メソッドをオーバーライドします。 ホストされたウィンドウの HWND を返します。 返されたウィンドウの子ウィンドウとして実際のコントロールをラップすることもできます。ホスト ウィンドウでコントロールをラップすると、WPF コンテンツでコントロールからの通知を簡単に受信できます。 この手法により、ホストされたコントロールの境界でのメッセージ処理に関するいくつかの Win32 問題を修正できます。

  4. HwndHostDestroyWindowCore メソッドと WndProc メソッドをオーバーライドします。 これは、クリーンアップの処理およびホストされたコンテンツへの参照の削除を行うためです (特に、アンマネージ オブジェクトへの参照を作成した場合)。

  5. 分離コード ファイルで、コントロール ホスト クラスのインスタンスを作成し、予約要素の子にします。 通常は、Loaded などのイベント ハンドラーを使用するか、部分クラス コンストラクターを使用します。 ただし、ランタイム動作によって相互運用コンテンツを追加することもできます。

  6. コントロールの通知などの選択されたウィンドウ メッセージを処理します。 処理方法は 2 つあります。 どちらも同様にメッセージ ストリームにアクセスできるようにするため、どちらを選択するかは、多くの場合、プログラミングの便宜上の理由によって決まります。

    • HwndHostWndProc メソッドのオーバーライドで、シャットダウン メッセージだけでなくすべてのメッセージのメッセージ処理を実装します。

    • MessageHook イベントを処理して、ホスト WPF 要素でメッセージを処理します。 このイベントは、ホストされたウィンドウのメイン ウィンドウ プロシージャに送信されるすべてのメッセージに対して発生します。

    • メッセージは、WndProc を使用するプロセスで発生したウィンドウからは処理できません。

  7. プラットフォーム呼び出しを使用してアンマネージ SendMessage 関数を呼び出し、ホストされたウィンドウと通信します。

次の手順に従って、マウス入力で動作するアプリケーションを作成します。 IKeyboardInputSink インターフェイスを実装して、ホストされたウィンドウで Tab キーによる移動がサポートされるようにすることができます。

これらの各手順のコードを使用した説明については、「チュートリアル: WPF での Win32 コントロールのホスト」を参照してください。

WPF 内の HWND

HwndHost は特殊なコントロールであると考えることができます (技術的には、HwndHostControl 派生クラスではなく、FrameworkElement 派生クラスですが、相互運用のためのコントロールと見なすことができます)。HwndHost は、WPF の残りの部分が、ホストされたコンテンツは入力を描画および処理する別のコントロールに似たオブジェクトであると見なすように、ホストされたコンテンツの基になる Win32 特性を抽出します。 通常、HwndHost は他の WPF FrameworkElement と同様に動作しますが、出力 (描画とグラフィックス) および入力 (マウスとキーボード) には、基になる HWND のサポート内容の制限に基づく重要な違いがいくつかあります。

出力動作の顕著な違い

  • HwndHost の基本クラスである FrameworkElement には、UI の変更に関係する多くのプロパティが用意されています。 これには、親要素内の要素のレイアウトを変更する FrameworkElement.FlowDirection などのプロパティが含まれます。 ただし、これらのプロパティの多くは、Win32 で有効な等価機能が存在しても、それに対応付けられていません。 非常に多くのプロパティとその意味がレンダリング テクノロジ固有でありすぎるために、対応付けをしても役に立ちません。 そのため、HwndHostFlowDirection などのプロパティを設定しても効果がありません。

  • HwndHost は、回転、拡大縮小、傾斜、または変換の影響を受けません。

  • HwndHost では、Opacity プロパティ (アルファ ブレンディング) はサポートされません。 HwndHost 内のコンテンツでアルファ情報を含む System.Drawing 操作が実行される場合、それ自体は違反ではありませんが、HwndHost 全体では不透明度 = 1.0 (100%) のみがサポートされます。

  • HwndHost は、同じトップレベル ウィンドウの他の WPF 要素の上に表示されます。 ただし、ToolTip または ContextMenu で生成されるメニューは独立したトップレベル ウィンドウであるため、HwndHost で正しく動作します。

  • HwndHost は、親 UIElement のクリッピング領域に対応しません。 これは、HwndHost クラスをスクロール領域または Canvas に格納しようとする場合に問題となる可能性があります。

入力動作の顕著な違い

  • 通常、入力デバイスは、HwndHost がホストする Win32 領域内がスコープとなり、入力イベントは Win32 に直接移動します。

  • HwndHost の上にマウスがあるとき、アプリケーションは WPF マウス イベントを受け取らず、WPF の IsMouseOver プロパティの値は false になります。

  • HwndHost にキーボード フォーカスがあるとき、アプリケーションは WPF キーボード イベントを受け取らず、WPF の IsKeyboardFocusWithin プロパティの値は false になります。

  • HwndHost 内にあるフォーカスが HwndHost 内の別のコントロールに移動したとき、アプリケーションは WPF の GotFocus イベントまたは LostFocus イベントを受け取りません。

  • 関連するスタイラス プロパティおよびイベントは類似しており、スタイラスが HwndHost 上にあるときは情報を報告しません。

Tab キーによる移動、ニーモニック、およびアクセラレータ

IKeyboardInputSink インターフェイスおよび IKeyboardInputSite インターフェイスを使用すると、WPF と Win32 が混在するアプリケーションでシームレスなキーボード操作を実現できます。

  • Win32 コンポーネントと WPF コンポーネント間の Tab キーによる移動

  • フォーカスが Win32 コンポーネント内にある場合と WPF コンポーネント内にある場合の両方で動作するニーモニックおよびアクセラレータ

HwndHost クラスと HwndSource クラスはどちらも IKeyboardInputSink の実装を提供しますが、より高度なシナリオで必要な入力メッセージが一部処理されない場合があります。 適切なメソッドをオーバーライドして、必要なキーボード動作を確保してください。

インターフェイスは、WPF 領域と Win32 領域間の遷移で発生する処理をサポートするだけです。 Win32 領域では、Tab キーによる移動動作は、Tab キーによる移動の Win32 実装ロジック (ある場合) によって完全に制御されます。

参照

参照

HwndHost

HwndSource

System.Windows.Interop

概念

チュートリアル: WPF での Win32 コントロールのホスト

チュートリアル: Win32 での WPF コンテンツのホスト