WPF と Win32 相互運用
このトピックでは、Windows Presentation Foundation (WPF) と Win32 コードを相互運用する方法の概要について説明します。 WPF は、アプリケーションを作成するための豊富な環境を提供します。 ただし、Win32 コードに多額の投資がある場合は、そのコードの一部を再利用する方が効果的な場合があります。
WPF と Win32 相互運用の基本
WPF と Win32 コードの間の相互運用には、2 つの基本的な手法があります。
Win32 ウィンドウで WPF コンテンツをホストします。 この手法では、標準の Win32 ウィンドウとアプリケーションのフレームワーク内で WPF の高度なグラフィックス機能を使用できます。
WPF コンテンツで Win32 ウィンドウをホストします。 この手法では、既存のカスタム Win32 コントロールを他の WPF コンテンツのコンテキストで使用し、境界を越えてデータを渡すことができます。
これらの各手法は、概念的にこのトピックで紹介します。 Win32 で WPF をホストするコード指向の図については、「チュートリアル: Win32での WPF コンテンツのホスト」を参照してください。 WPF で Win32 をホストするコード指向の図については、「チュートリアル: WPFでの Win32 コントロールのホスト」を参照してください。
WPF 相互運用プロジェクト
WPF API はマネージド コードですが、ほとんどの既存の Win32 プログラムはアンマネージド C++ で記述されています。 真のアンマネージ プログラムから WPF API を呼び出すことはできません。 ただし、Microsoft Visual C++ コンパイラで /clr
オプションを使用すると、マネージドとアンマネージドの混合プログラムを作成して、マネージド API 呼び出しとアンマネージド API 呼び出しをシームレスに混在させることができます。
プロジェクト レベルの複雑さの 1 つは、拡張アプリケーション マークアップ言語 (XAML) ファイルを C++ プロジェクトにコンパイルできないことです。 これを補うために、いくつかのプロジェクト分割手法があります。
コンパイル済みアセンブリとしてすべての XAML ページを含む C# DLL を作成し、その DLL を参照として C++ 実行可能ファイルに含めます。
WPF コンテンツの C# 実行可能ファイルを作成し、Win32 コンテンツを含む C++ DLL を参照させます。
Load を使用して、XAML をコンパイルするのではなく、実行時に任意の XAML を読み込みます。
XAML をまったく使用せず、すべての WPF をコードに記述し、Applicationから要素ツリーを構築します。
最適な方法を使用します。
手記
以前に C++/CLI を使用したことがない場合は、相互運用コード例の gcnew
や nullptr
など、いくつかの "新しい" キーワードに気付く場合があります。 これらのキーワードは、古い二重アンダースコア構文 (__gc
) よりも優先され、C++ のマネージド コードに対してより自然な構文が提供されます。 C++/CLI のマネージド機能の詳細については、「ランタイム プラットフォームのコンポーネント拡張機能の 」を参照してください。
WPF での Hwnds の使用方法
WPF の "HWND 相互運用" を最大限に活用するには、WPF で HWND がどのように使用されるかを理解する必要があります。 HWND の場合、WPF レンダリングと DirectX レンダリングまたは GDI/GDI+ レンダリングを混在させることはできません。 これには多くの影響があります。 主に、これらのレンダリング モデルをまったく混在させるには、相互運用ソリューションを作成し、使用するレンダリング モデルごとに指定された相互運用セグメントを使用する必要があります。 また、レンダリング動作により、相互運用ソリューションで実現できる内容に対する "空域" 制限が作成されます。 「空域」の概念については、「技術領域の概要」トピック
画面上のすべての WPF 要素は、最終的には HWND によってサポートされます。 WPF Windowを作成すると、WPF は最上位の HWND を作成し、HwndSource を使用して、Window とその WPF コンテンツを HWND 内に配置します。 アプリケーション内の残りの WPF コンテンツは、その単数形 HWND を共有します。 例外は、メニュー、コンボ ボックスのドロップダウン、およびその他のポップアップです。 これらの要素は独自のトップレベル ウィンドウを作成するため、WPF メニューは、それを含むウィンドウ HWND の端を越える可能性があります。 HwndHost を使用して WPF 内に HWND を配置すると、WPF は、WPF Window HWND を基準にして新しい子 HWND を配置する方法を Win32 に通知します。
HWND に関連する概念は、各 HWND 内および各 HWND 間の透過性です。 これについては、テクノロジリージョンの概要のトピックでも説明します。
Microsoft Win32 ウィンドウでの WPF コンテンツのホスト
Win32 ウィンドウで WPF をホストするキーは、HwndSource クラスです。 このクラスは、WPF コンテンツを子ウィンドウとして UI に組み込むことができるように、WPF コンテンツを Win32 ウィンドウでラップします。 次の方法では、Win32 と WPF を 1 つのアプリケーションに結合します。
WPF コンテンツ (コンテンツ ルート要素) をマネージド クラスとして実装します。 通常、このクラスは、複数の子要素を含めたり、ルート要素として使用したりできるクラスの 1 つ (DockPanel や Pageなど) から継承します。 以降の手順では、このクラスを WPF コンテンツ クラスと呼び、クラスのインスタンスを WPF コンテンツ オブジェクトと呼びます。
C++/CLI を使用して Windows アプリケーションを実装します。 既存のアンマネージド C++ アプリケーションを使い始める場合は、通常、プロジェクト設定を変更して
/clr
コンパイラ フラグを含めることでマネージド コードを呼び出すことができます (/clr
コンパイルをサポートするために必要な内容の完全な範囲については、このトピックでは説明しません)。スレッド モデルをシングル スレッド アパートメント (STA) に設定します。 WPF では、このスレッド モデルが使用されます。
ウィンドウ プロシージャでWM_CREATE通知を処理します。
ハンドラー (またはハンドラーが呼び出す関数) 内で、次の操作を行います。
親ウィンドウ HWND を
parent
パラメーターとして使用して、新しい HwndSource オブジェクトを作成します。WPF コンテンツ クラスのインスタンスを作成します。
WPF コンテンツ オブジェクトへの参照を HwndSource オブジェクト RootVisual プロパティに割り当てます。
HwndSource オブジェクト Handle プロパティには、ウィンドウ ハンドル (HWND) が含まれています。 アプリケーションのアンマネージ部分で使用できる HWND を取得するには、
Handle.ToPointer()
を HWND にキャストします。
WPF コンテンツ オブジェクトへの参照を保持する静的フィールドを含むマネージド クラスを実装します。 このクラスを使用すると、Win32 コードから WPF コンテンツ オブジェクトへの参照を取得できますが、さらに重要なのは、HwndSource が誤ってガベージ コレクションされないようにすることです。
ハンドラーを 1 つ以上の WPF コンテンツ オブジェクト イベントにアタッチして、WPF コンテンツ オブジェクトから通知を受け取ります。
静的フィールドに格納した参照を使用して WPF コンテンツ オブジェクトと通信し、プロパティの設定、メソッドの呼び出しなどを行います。
手記
別のアセンブリを作成して参照する場合は、コンテンツ クラスの既定の部分クラスを使用して、XAML の手順 1 の WPF コンテンツ クラス定義の一部またはすべてを実行できます。 通常、アセンブリへの XAML のコンパイルの一部として Application オブジェクトを含めますが、その Application を相互運用の一部として使用することはありません。アプリケーションによって参照される XAML ファイルに対して 1 つ以上のルート クラスを使用し、その部分クラスを参照するだけです。 この手順の残りの部分は、上で説明した手順と基本的に似ています。
これらの各手順は、「チュートリアル: Win32での WPF コンテンツのホスト」のコードで示されています。
WPF での Microsoft Win32 ウィンドウのホスト
他の WPF コンテンツ内で Win32 ウィンドウをホストする鍵は、HwndHost クラスです。 このクラスは、WPF 要素ツリーに追加できる WPF 要素でウィンドウをラップします。 HwndHost では、ホストされたウィンドウのメッセージの処理などのタスクを実行できる API もサポートされています。 基本的な手順は次のとおりです。
WPF アプリケーションの要素ツリーを作成します (コードまたはマークアップを使用できます)。 HwndHost 実装を子要素として追加できる、要素ツリー内の適切で許容されるポイントを見つけます。 以降の手順では、この要素を予約要素と呼びます。
HwndHost から派生して、Win32 コンテンツを保持するオブジェクトを作成します。
そのホスト クラスで、HwndHost メソッドの BuildWindowCoreをオーバーライドします。 ホストされているウィンドウの HWND を返します。 返されたウィンドウの子ウィンドウとして、実際のコントロールをラップすることもできます。コントロールをホスト ウィンドウにラップすると、WPF コンテンツがコントロールから通知を受け取る簡単な方法が提供されます。 この手法は、ホストされた制御境界でのメッセージ処理に関するいくつかの Win32 の問題を修正するのに役立ちます。
HwndHost メソッドの DestroyWindowCore と WndProcをオーバーライドします。 ここでの目的は、特にアンマネージド オブジェクトへの参照を作成した場合に、ホストされているコンテンツへの参照のクリーンアップと削除を処理することです。
分離コード ファイルで、コントロール ホスティング クラスのインスタンスを作成し、予約要素の子にします。 通常は、Loadedなどのイベント ハンドラーを使用するか、部分クラス コンストラクターを使用します。 ただし、ランタイム動作を使用して相互運用コンテンツを追加することもできます。
コントロール通知など、選択したウィンドウ メッセージを処理します。 2 つの方法があります。 両方ともメッセージストリームへの同じアクセスを提供するため、選択は主にプログラミングの利便性に関するものです。
HwndHost メソッド WndProcのオーバーライドで、(シャットダウン メッセージだけでなく) すべてのメッセージのメッセージ処理を実装します。
ホスト WPF 要素で、MessageHook イベントを処理してメッセージを処理します。 このイベントは、ホストされているウィンドウのメイン ウィンドウ プロシージャに送信されるすべてのメッセージに対して発生します。
WndProcを使用して、プロセス外のウィンドウからのメッセージを処理することはできません。
プラットフォーム呼び出しを使用してアンマネージ
SendMessage
関数を呼び出すことによって、ホストされたウィンドウと通信します。
次の手順に従って、マウス入力で動作するアプリケーションを作成します。 IKeyboardInputSink インターフェイスを実装することで、ホストされたウィンドウのタブ表示のサポートを追加できます。
これらの各手順は、「チュートリアル: WPFでの Win32 コントロールのホスト
WPF 内の HWND
HwndHost は特別なコントロールと考えることができます。 (技術的には、HwndHost は Control 派生クラスではなく、FrameworkElement 派生クラスですが、相互運用のためにコントロールと見なすことができます)。HwndHost は、ホストされるコンテンツの基になる Win32 の性質を抽象化します。そのため、WPF の残りの部分では、ホストされたコンテンツが別のコントロールに似たオブジェクトと見なされ、入力をレンダリングして処理する必要があります。 HwndHost は、一般的に他の WPF FrameworkElementと同様に動作しますが、基になる HWND でサポートできるものの制限に基づいて、出力 (描画とグラフィックス) と入力 (マウスとキーボード) に関していくつかの重要な違いがあります。
出力動作の主な違い
FrameworkElement(HwndHost 基底クラス) には、UI に変更を示すプロパティが非常に多く含まれています。 これには、その要素内の要素のレイアウトを親として変更する FrameworkElement.FlowDirectionなどのプロパティが含まれます。 ただし、これらのプロパティのほとんどは、そのような同等のプロパティが存在する可能性がある場合でも、可能な Win32 に対応付けされません。 これらのプロパティとその意味は、レンダリング技術に特化しすぎていて、マッピングには実用的ではありません。 そのため、HwndHost で FlowDirection などのプロパティを設定しても効果はありません。
HwndHost 回転、拡大縮小、傾斜、またはその他の変換の影響を受けることはできません。
HwndHost では、Opacity プロパティ (アルファ ブレンド) はサポートされていません。 HwndHost 内のコンテンツがアルファ情報を含む System.Drawing 操作を実行する場合、それ自体は違反ではありませんが、HwndHost 全体では Opacity = 1.0 (100%) のみがサポートされます。
HwndHost は、同じ最上位ウィンドウ内の他の WPF 要素の上に表示されます。 ただし、ToolTip または ContextMenu 生成されたメニューは別のトップレベル ウィンドウであるため、HwndHostで正しく動作します。
HwndHost は親要素 UIElementのクリッピング領域を無視しています。 これは、スクロール領域内または Canvas内に HwndHost クラスを配置しようとすると、問題になる可能性があります。
入力動作の主な違い
一般に、入力デバイスはホストされている Win32 リージョン HwndHost 内でスコープ設定されますが、入力イベントは Win32 に直接移動します。
マウスが HwndHostの上にある間、アプリケーションは WPF マウス イベントを受け取らず、WPF プロパティ IsMouseOver の値が
false
されます。HwndHost にキーボード フォーカスがある間、アプリケーションは WPF キーボード イベントを受信せず、WPF プロパティ IsKeyboardFocusWithin の値は
false
になります。フォーカスが HwndHost 内にあり、HwndHost内の別のコントロールに変更された場合、アプリケーションは WPF イベント GotFocus または LostFocusを受け取りません。
関連するスタイラスのプロパティとイベントは類似しており、スタイラスが HwndHostを超えている間は情報を報告しません。
Tab キーによる移動、ニーモニック、およびアクセラレータ
IKeyboardInputSink インターフェイスと IKeyboardInputSite インターフェイスを使用すると、WPF アプリケーションと Win32 アプリケーションを混在させるためにシームレスなキーボード エクスペリエンスを作成できます。
Win32 コンポーネントと WPF コンポーネント間のタブ移動
フォーカスが Win32 コンポーネント内にある場合と WPF コンポーネント内にある場合の両方で機能するニーモニックとアクセラレータ。
HwndHost クラスと HwndSource クラスはどちらも IKeyboardInputSinkの実装を提供しますが、より高度なシナリオに必要なすべての入力メッセージを処理できない場合があります。 適切なメソッドをオーバーライドして、目的のキーボード動作を取得します。
インターフェイスは、WPF リージョンと Win32 リージョン間の移行時に何が起こるかをサポートするだけです。 Win32 領域内では、タブ処理の動作は、Win32 で実装されているタブ処理ロジック (存在する場合) によって完全に制御されます。
関連項目
- HwndHost
- HwndSource
- System.Windows.Interop
- チュートリアル: WPF での Win32 コントロールのホスト
- チュートリアル: Win32 での WPF コンテンツのホスト
.NET Desktop feedback