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 呼び出しをシームレスに混在させることができる、マネージドとアンマネージドの混在プログラムを作成することができます。
プロジェクト レベルで、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 のマネージド機能の詳細については、「ランタイム プラットフォームのコンポーネントの拡張機能」を参照してください。
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 から Win32 に対して、新しい子 HWND を WPF Window HWND に対して相対的に配置する方法が通知されます。
HWND の関連概念として、各 HWND 内および各 HWND 間の透過性が挙げられます。 これについても「技術領域の概要」のトピックで解説されています。
Microsoft Win32 ウィンドウでの WPF コンテンツのホスト
Win32 ウィンドウで WPF をホストするキーとなるのは、HwndSource クラスです。 このクラスは Win32 ウィンドウの WPF コンテンツをラップし、その WPF コンテンツを子ウィンドウとして UI に組み込むことができます。 次の方法では、Win32 と WPF を 1 つのアプリケーションに統合します。
WPF コンテンツ (コンテンツのルート要素) をマネージド クラスとして実装します。 通常、このクラスは、複数の子要素を格納できるクラスやルート要素として使用されるクラスのいずれか (DockPanel や Page など) から継承されます。 以降の手順では、このクラスを WPF コンテンツ クラスと呼び、そのクラスのインスタンスを WPF コンテンツ オブジェクトと呼びます。
C++ または CLI を使用して Windows アプリケーションを実装します。 既存のアンマネージド C++ アプリケーションから始める場合、通常、
/clr
コンパイラ フラグが含まれるようにプロジェクトの設定を変更して、アプリケーションでマネージド コードを呼び出すようにできます (/clr
コンパイルをサポートするための要件の詳細については、ここでは説明しません)。スレッド処理モデルをシングル スレッド アパートメント (STA: Single Threaded Apartment) に設定します。 WPF では、このスレッド処理モデルが使用されます。
ウィンドウ プロシージャで WM_CREATE 通知を処理します。
ハンドラー (またはハンドラーが呼び出す関数) で、次の手順を実行します。
HwndSource パラメーターとして親ウィンドウ HWND を持つ新しい
parent
オブジェクトを作成します。WPF コンテンツ クラスのインスタンスを作成します。
WPF コンテンツ オブジェクトへの参照を HwndSource オブジェクトの RootVisual プロパティに割り当てます。
HwndSource オブジェクトの Handle プロパティにウィンドウ ハンドル (HWND) が格納されます。 アプリケーションのアンマネージ部分で使用できる HWND を取得するには、
Handle.ToPointer()
を HWND にキャストします。
WPF コンテンツ オブジェクトへの参照を保持する静的フィールドを含むマネージド クラスを実装します。 このクラスを使用すると、Win32 コードから WPF コンテンツ オブジェクトへの参照を取得できますが、さらに、HwndSource が不注意によりガベージ コレクトされることを回避できます。
ハンドラーを 1 つ以上の WPF コンテンツ オブジェクト イベントにアタッチして、WPF コンテンツ オブジェクトから通知を受信します。
静的フィールドに格納した参照を使用してプロパティの設定やメソッドの呼び出しなどを行い、WPF コンテンツ オブジェクトと通信します。
注意
個別のアセンブリを生成して参照する場合は、手順 1 の WPF コンテンツ クラスの一部またはすべてについて、XAML でコンテンツ クラスの既定の部分クラスを使用して定義できます。 通常は、Application オブジェクトを XAML のコンパイルの一部としてアセンブリに含めますが、その 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 メソッドのオーバーライドで、シャットダウン メッセージだけでなくすべてのメッセージのメッセージ処理を実装します。
MessageHook イベントを処理して、ホスト WPF 要素でメッセージを処理します。 このイベントは、ホストされたウィンドウのメイン ウィンドウ プロシージャに送信されるすべてのメッセージに対して発生します。
メッセージは、WndProc を使用するプロセスで発生したウィンドウからは処理できません。
プラットフォーム呼び出しを使用してアンマネージ
SendMessage
関数を呼び出し、ホストされたウィンドウと通信します。
次の手順に従って、マウス入力で動作するアプリケーションを作成します。 IKeyboardInputSink インターフェイスを実装して、ホストされたウィンドウで Tab キーによる移動がサポートされるようにすることができます。
これらの各手順のコードを使用した説明については、「チュートリアル: WPF での Win32 コントロールのホスト」を参照してください。
WPF 内の HWND
HwndHost は特殊なコントロールであると考えることができます (技術的には、HwndHost は Control 派生クラスではなく、FrameworkElement 派生クラスですが、相互運用のためのコントロールと見なすことができます)。HwndHost は、WPF の残りの部分が、ホストされたコンテンツは入力を描画および処理する別のコントロールに似たオブジェクトであると見なすように、ホストされたコンテンツの基になる Win32 の特性を抽出します。 通常、HwndHost は他の WPF FrameworkElement と同様に動作しますが、出力 (描画とグラフィックス) および入力 (マウスとキーボード) には、基になる HWND のサポート内容の制限に基づく重要な違いがいくつかあります。
出力動作の顕著な違い
FrameworkElement の基底クラスである HwndHost には、UI の変更に関係する多くのプロパティが用意されています。 これには、親要素内の要素のレイアウトを変更する FrameworkElement.FlowDirection などのプロパティが含まれます。 ただし、これらのプロパティの多くは、Win32 で等価機能が存在し得るとしても、それに対応付けられていません。 非常に多くのプロパティとその意味がレンダリング テクノロジ固有でありすぎるために、対応付けをしても役に立ちません。 そのため、HwndHost で FlowDirection などのプロパティを設定しても効果がありません。
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 コンポーネント間のタブ移動
フォーカスが Win32 コンポーネント内にある場合と WPF コンポーネント内にある場合の両方で動作するニーモニックおよびアクセラレータ
HwndHost クラスと HwndSource クラスはどちらも IKeyboardInputSink の実装を提供しますが、より高度なシナリオで必要な入力メッセージが一部処理されない場合があります。 適切なメソッドをオーバーライドして、必要なキーボード動作を確保してください。
インターフェイスは、WPF 領域と Win32 領域間の遷移で発生する処理をサポートするだけです。 Win32 領域では、Tab キーによる移動動作は、Tab キーによる移動の Win32 実装ロジック (ある場合) によって完全に制御されます。
関連項目
.NET Desktop feedback