WIN32 アプリケーション内に WPF を配置するには、WPF コンテンツを含む HWND を提供する HwndSourceを使用します。 最初に、CreateWindow と同様のパラメーターを指定して、 HwndSourceを作成します。 その後、その中に必要な WPF コンテンツについて HwndSource に伝えます。 最後に、HWND を HwndSourceから取得します。 このチュートリアルでは、オペレーティング システムの [日付と時刻のプロパティ ] ダイアログを再実装する、Win32 アプリケーション内に混合 WPF を作成する方法について説明します。
[前提条件]
「WPF と Win32 相互運用」を参照してください。
このチュートリアルの使用方法
このチュートリアルでは、相互運用アプリケーションを生成する重要な手順に重点を置きます。 このチュートリアルはサンプルの Win32 Clock 相互運用サンプルでサポートされていますが、そのサンプルは最終製品を反映しています。 このチュートリアルでは、独自の既存の Win32 プロジェクト (おそらく既存のプロジェクト) を使い始め、ホストされている WPF をアプリケーションに追加する場合と同様の手順について説明します。 エンド 製品を Win32 クロック相互運用サンプルと比較できます。
Win32 内の Windows Presentation Framework のチュートリアル (HwndSource)
次の図は、このチュートリアルの目的の最終製品を示しています。
このダイアログを再作成するには、Visual Studio で C++ Win32 プロジェクトを作成し、ダイアログ エディターを使用して以下を作成します。
( HwndSourceを使用するために Visual Studio を使用する必要はなく、Win32 プログラムを記述するために C++ を使用する必要はありませんが、これはかなり一般的な方法であり、段階的なチュートリアルの説明に役立ちます)。
WPF クロックをダイアログに配置するには、次の 5 つの特定のサブステップを実行する必要があります。
Visual Studio でプロジェクト設定を変更して、Win32 プロジェクトでマネージド コード (/clr) を呼び出せるようにします。
別の DLL に WPFPage を作成します。
その WPFPage を HwndSource内に配置します。
Win32 を使用して、大きな Win32 アプリケーション内で HWND を配置する場所を決定する
/clr
最初の手順では、このアンマネージド Win32 プロジェクトをマネージド コードを呼び出すことができるプロジェクトに変換します。 使用する必要な DLL にリンクする /clr コンパイラ オプションを使用し、WPF で使用する Main メソッドを調整します。
C++ プロジェクト内でマネージド コードの使用を有効にするには:win32clock プロジェクトを右クリックし、[ プロパティ] を選択します。 [ 全般 ] プロパティ ページ (既定) で、共通言語ランタイムのサポートを /clr
に変更します。
次に、WPF に必要な DLL への参照 (PresentationCore.dll、PresentationFramework.dll、System.dll、WindowsBase.dll、UIAutomationProvider.dll、UIAutomationTypes.dll) を追加します。 (次の手順では、オペレーティング システムが C: ドライブにインストールされていることを前提としています)。
win32clock プロジェクトを右クリックし、[ 参照]、[...] の順に選択し、そのダイアログ内で次の操作を行います。
win32clock プロジェクトを右クリックし、[ 参照]... を選択します。
[ 新しい参照の追加] をクリックし、[参照] タブをクリックし、「C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationCore.dll」と入力して、[OK] をクリックします。
PresentationFramework.dllに対して繰り返します: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationFramework.dll.
WindowsBase.dllに対して繰り返します: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll.
UIAutomationTypes.dllに対して繰り返します: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationTypes.dll.
UIAutomationProvider.dllに対して繰り返します: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationProvider.dll.
[ 新しい参照の追加] をクリックし、[System.dll] を選択し、[ OK] をクリックします。
[ OK] を クリックして、参照を追加するための win32clock プロパティ ページを終了します。
最後に、WPF で使用するSTAThreadAttribute
メソッドに_tWinMain
を追加します。
[System::STAThreadAttribute]
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
この属性は、共通言語ランタイム (CLR) に対して、コンポーネント オブジェクト モデル (COM) を初期化するときに、WPF (および Windows フォーム) に必要なシングル スレッド アパートメント モデル (STA) を使用する必要があることを示します。
Windows Presentation Framework ページを作成する
次に、WPF を定義する DLL を作成しますPage。 多くの場合、WPF をスタンドアロン アプリケーションとして作成しPage その方法で WPF 部分を記述してデバッグするのが最も簡単です。 完了したら、プロジェクトを右クリックし、[ プロパティ] をクリックし、[アプリケーション] に移動し、[出力の種類] を [Windows クラス ライブラリ] に変更することで、そのプロジェクトを DLL に変換できます。
その後、WPF dll プロジェクトを Win32 プロジェクト (2 つのプロジェクトを含む 1 つのソリューション) と組み合わせることができます。ソリューションを右クリックし、[ 追加]、[既存のプロジェクト] の順に選択します。
Win32 プロジェクトからその WPF dll を使用するには、参照を追加する必要があります。
win32clock プロジェクトを右クリックし、[ 参照]... を選択します。
[ 新しい参照の追加] をクリックします。
[ プロジェクト ] タブをクリックします。WPFClock を選択し、[OK] をクリックします。
[ OK] を クリックして、参照を追加するための win32clock プロパティ ページを終了します。
HwndSource
次に、 HwndSource を使用して WPF を HWND のようにPage します。 次のコード ブロックを C++ ファイルに追加します。
namespace ManagedCode
{
using namespace System;
using namespace System::Windows;
using namespace System::Windows::Interop;
using namespace System::Windows::Media;
HWND GetHwnd(HWND parent, int x, int y, int width, int height) {
HwndSource^ source = gcnew HwndSource(
0, // class style
WS_VISIBLE | WS_CHILD, // style
0, // exstyle
x, y, width, height,
"hi", // NAME
IntPtr(parent) // parent window
);
UIElement^ page = gcnew WPFClock::Clock();
source->RootVisual = page;
return (HWND) source->Handle.ToPointer();
}
}
}
これは、いくつかの説明を使用できる長いコードです。 最初の部分にはさまざまな文節が含まれているため、すべての呼び出しを完全に指定する必要はありません。
namespace ManagedCode
{
using namespace System;
using namespace System::Windows;
using namespace System::Windows::Interop;
using namespace System::Windows::Media;
次に、WPF コンテンツを作成し、その周囲に HwndSource を配置し、HWND を返す関数を定義します。
HWND GetHwnd(HWND parent, int x, int y, int width, int height) {
最初に、CreateWindow に似たパラメーターを持つ HwndSourceを作成します。
HwndSource^ source = gcnew HwndSource(
0, // class style
WS_VISIBLE | WS_CHILD, // style
0, // exstyle
x, y, width, height,
"hi", // NAME
IntPtr(parent) // parent window
);
次に、そのコンストラクターを呼び出して WPF コンテンツ クラスを作成します。
UIElement^ page = gcnew WPFClock::Clock();
その後、ページを HwndSourceに接続します。
source->RootVisual = page;
最後の行で、 HwndSourceの HWND を返します。
return (HWND) source->Handle.ToPointer();
Hwnd の配置
WPF クロックを含む HWND が作成されたので、その HWND を Win32 ダイアログ内に配置する必要があります。 HWND を配置する場所がわかっていた場合は、先ほど定義した GetHwnd
関数にそのサイズと場所を渡すだけです。 ただし、リソース ファイルを使用してダイアログを定義したので、HWND がどこに配置されているかが正確にはわかりません。 Visual Studio ダイアログ エディターを使用すると、Win32 STATIC コントロールをクロックの移動先に配置し ("ここにクロックを挿入")、これを使用して WPF クロックを配置できます。
WM_INITDIALOGを処理する場合は、 GetDlgItem
を使用してプレースホルダー STATIC の HWND を取得します。
HWND placeholder = GetDlgItem(hDlg, IDC_CLOCK);
次に、そのプレースホルダー STATIC のサイズと位置を計算して、WPF クロックをその場所に配置できるようにします。
RECT 四角形;
GetWindowRect(placeholder, &rectangle);
int width = rectangle.right - rectangle.left;
int height = rectangle.bottom - rectangle.top;
POINT point;
point.x = rectangle.left;
point.y = rectangle.top;
result = MapWindowPoints(NULL, hDlg, &point, 1);
次に、プレースホルダー STATIC を非表示にします。
ShowWindow(placeholder, SW_HIDE);
その場所に WPF クロック HWND を作成します。
HWND clock = ManagedCode::GetHwnd(hDlg, point.x, point.y, width, height);
チュートリアルを興味深いものにし、実際の WPF クロックを生成するには、この時点で WPF クロック コントロールを作成する必要があります。 マークアップでほとんどの場合行うことができ、コードの背後で少数のイベント処理を使用します。 このチュートリアルは相互運用に関するものであり、コントロールの設計に関するものではないため、WPF クロックの完全なコードはコード ブロックとして提供されます。ここでは、それを構築するための個別の手順や各部分の意味を説明しません。 このコードを自由に試して、コントロールの外観や機能を変更してください。
マークアップを次に示します。
<Page x:Class="WPFClock.Clock"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Grid>
<Grid.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#fcfcfe" Offset="0" />
<GradientStop Color="#f6f4f0" Offset="1.0" />
</LinearGradientBrush>
</Grid.Background>
<Grid Name="PodClock" HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.Resources>
<Storyboard x:Key="sb">
<DoubleAnimation From="0" To="360" Duration="12:00:00" RepeatBehavior="Forever"
Storyboard.TargetName="HourHand"
Storyboard.TargetProperty="(Rectangle.RenderTransform).(RotateTransform.Angle)"
/>
<DoubleAnimation From="0" To="360" Duration="01:00:00" RepeatBehavior="Forever"
Storyboard.TargetName="MinuteHand"
Storyboard.TargetProperty="(Rectangle.RenderTransform).(RotateTransform.Angle)"
/>
<DoubleAnimation From="0" To="360" Duration="0:1:00" RepeatBehavior="Forever"
Storyboard.TargetName="SecondHand"
Storyboard.TargetProperty="(Rectangle.RenderTransform).(RotateTransform.Angle)"
/>
</Storyboard>
</Grid.Resources>
<Ellipse Width="108" Height="108" StrokeThickness="3">
<Ellipse.Stroke>
<LinearGradientBrush>
<GradientStop Color="LightBlue" Offset="0" />
<GradientStop Color="DarkBlue" Offset="1" />
</LinearGradientBrush>
</Ellipse.Stroke>
</Ellipse>
<Ellipse VerticalAlignment="Center" HorizontalAlignment="Center" Width="104" Height="104" Fill="LightBlue" StrokeThickness="3">
<Ellipse.Stroke>
<LinearGradientBrush>
<GradientStop Color="DarkBlue" Offset="0" />
<GradientStop Color="LightBlue" Offset="1" />
</LinearGradientBrush>
</Ellipse.Stroke>
</Ellipse>
<Border BorderThickness="1" BorderBrush="Black" Background="White" Margin="20" HorizontalAlignment="Right" VerticalAlignment="Center">
<TextBlock Name="MonthDay" Text="{Binding}"/>
</Border>
<Canvas Width="102" Height="102">
<Ellipse Width="8" Height="8" Fill="Black" Canvas.Top="46" Canvas.Left="46" />
<Rectangle Canvas.Top="5" Canvas.Left="48" Fill="Black" Width="4" Height="8">
<Rectangle.RenderTransform>
<RotateTransform CenterX="2" CenterY="46" Angle="0" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
<Rectangle.RenderTransform>
<RotateTransform CenterX="2" CenterY="46" Angle="30" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
<Rectangle.RenderTransform>
<RotateTransform CenterX="2" CenterY="46" Angle="60" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Canvas.Top="5" Canvas.Left="48" Fill="Black" Width="4" Height="8">
<Rectangle.RenderTransform>
<RotateTransform CenterX="2" CenterY="46" Angle="90" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
<Rectangle.RenderTransform>
<RotateTransform CenterX="2" CenterY="46" Angle="120" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
<Rectangle.RenderTransform>
<RotateTransform CenterX="2" CenterY="46" Angle="150" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Canvas.Top="5" Canvas.Left="48" Fill="Black" Width="4" Height="8">
<Rectangle.RenderTransform>
<RotateTransform CenterX="2" CenterY="46" Angle="180" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
<Rectangle.RenderTransform>
<RotateTransform CenterX="2" CenterY="46" Angle="210" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
<Rectangle.RenderTransform>
<RotateTransform CenterX="2" CenterY="46" Angle="240" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Canvas.Top="5" Canvas.Left="48" Fill="Black" Width="4" Height="8">
<Rectangle.RenderTransform>
<RotateTransform CenterX="2" CenterY="46" Angle="270" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
<Rectangle.RenderTransform>
<RotateTransform CenterX="2" CenterY="46" Angle="300" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
<Rectangle.RenderTransform>
<RotateTransform CenterX="2" CenterY="46" Angle="330" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="HourHand" Canvas.Top="21" Canvas.Left="48"
Fill="Black" Width="4" Height="30">
<Rectangle.RenderTransform>
<RotateTransform x:Name="HourHand2" CenterX="2" CenterY="30" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="MinuteHand" Canvas.Top="6" Canvas.Left="49"
Fill="Black" Width="2" Height="45">
<Rectangle.RenderTransform>
<RotateTransform CenterX="1" CenterY="45" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="SecondHand" Canvas.Top="4" Canvas.Left="49"
Fill="Red" Width="1" Height="47">
<Rectangle.RenderTransform>
<RotateTransform CenterX="0.5" CenterY="47" />
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>
</Grid>
</Grid>
</Page>
付随するコードビハインドをここに示します。
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace WPFClock
{
/// <summary>
/// Interaction logic for Clock.xaml
/// </summary>
public partial class Clock : Page
{
private DispatcherTimer _dayTimer;
public Clock()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(Clock_Loaded);
}
void Clock_Loaded(object sender, RoutedEventArgs e) {
// set the datacontext to be today's date
DateTime now = DateTime.Now;
DataContext = now.Day.ToString();
// then set up a timer to fire at the start of tomorrow, so that we can update
// the datacontext
_dayTimer = new DispatcherTimer();
_dayTimer.Interval = new TimeSpan(1, 0, 0, 0) - now.TimeOfDay;
_dayTimer.Tick += new EventHandler(OnDayChange);
_dayTimer.Start();
// finally, seek the timeline, which assumes a beginning at midnight, to the appropriate
// offset
Storyboard sb = (Storyboard)PodClock.FindResource("sb");
sb.Begin(PodClock, HandoffBehavior.SnapshotAndReplace, true);
sb.Seek(PodClock, now.TimeOfDay, TimeSeekOrigin.BeginTime);
}
private void OnDayChange(object sender, EventArgs e)
{
// date has changed, update the datacontext to reflect today's date
DateTime now = DateTime.Now;
DataContext = now.Day.ToString();
_dayTimer.Interval = new TimeSpan(1, 0, 0, 0);
}
}
}
最終的な結果は次のようになります。
最終的な結果を、このスクリーンショットを生成したコードと比較するには、「 Win32 Clock Interoperation Sample」を参照してください。
こちらも参照ください
.NET Desktop feedback