チュートリアル : WPF コンテンツをホストする Win32 アプリケーションを作成する

更新 : 2007 年 11 月

Win32 アプリケーション内に WPF を配置するには、HwndSource を使用します。HwndSource は、WPF コンテンツを格納する HWND を提供します。まず、HwndSource を作成し、CreateWindow に似たパラメータを渡します。次に、HwndSource 内に格納する WPF コンテンツについて HwndSource に通知します。最後に、HwndSource から HWND を取得します。このチュートリアルでは、オペレーティング システムの [日付と時刻のプロパティ] ダイアログを再実装する混在 WPF を Win32 アプリケーション内に作成する方法について説明します。

前提条件

WPF と Win32 の相互運用性に関する概要」を参照してください。

このチュートリアルの使用方法

このチュートリアルでは、相互運用アプリケーションを作成するための重要な手順に重点を置いて説明します。このチュートリアルは、Win32 クロックの相互運用のサンプルによって補足されます。ただし、このサンプルは最終結果を反映しています。このチュートリアルでは、開発者が独自の既存の Win32 プロジェクト (おそらく以前から存在するプロジェクト) から開始し、ホストされる WPF をアプリケーションに追加すると仮定して手順を説明します。最終結果と Win32 クロックの相互運用のサンプルを比較できます。

Win32 (HwndSource) 内の Windows Presentation Framework のチュートリアル

このチュートリアルの対象となるダイアログを次の図に示します。

[日付と時刻のプロパティ] ダイアログ ボックス

Microsoft Visual Studio で C++ Win32 プロジェクトを作成し、ダイアログ エディタを使用して次のダイアログを作成することにより、このダイアログを再作成することができます。

[日付と時刻のプロパティ] ダイアログ ボックス

(HwndSource を使用するのに Microsoft Visual Studio を使用する必要はなく、Win32 プログラムを作成するのに C++ を使用する必要もありません。ただし、これらを使用するのが一般的なやり方であり、チュートリアルの段階的な説明に役立ちます。)

WPF クロックをダイアログ内に配置するには、次の 5 つのサブステップを実行する必要があります。

  1. Microsoft Visual Studio でプロジェクト設定を変更して、Win32 プロジェクトがマネージ コード (/clr) を呼び出すことができるようにします。

  2. 別の DLL 内に WPFPage を作成します。

  3. 作成した WPFPageHwndSource 内に配置します。

  4. Handle プロパティを使用して、この Page の HWND を取得します。

  5. Win32 を使用して、より大きなWin32 アプリケーション内の HWND の配置場所を決定します。

/clr

最初の手順は、このアンマネージ Win32 プロジェクトを、マネージ コードを呼び出すことができるプロジェクトに変更することです。/clr コンパイラ オプションを使用して必要な DLL にリンクし、WPF で使用する Main メソッドを変更します。

C++ プロジェクト内でマネージ コードを使用できるようにするには、win32clock プロジェクトを右クリックして [プロパティ] をクリックします。[全般] プロパティ ページ (既定) で、共通言語ランタイム サポートを /clr に変更します。

次に、WPF で必要な DLL (PresentationCore.dll、PresentationFramework.dll、System.dll、WindowsBase.dll、UIAutomationProvider.dll、および UIAutomationTypes.dll) への参照を追加します (次の手順では、オペレーティング システムが C: ドライブにインストールされていると仮定します)。

  1. win32clock プロジェクトを右クリックして [参照設定] をクリックし、表示されるダイアログで次の手順を実行します。

  2. win32clock プロジェクトを右クリックして [参照設定] をクリックします。

  3. [新しい参照の追加] をクリックして [参照] タブをクリックし、「C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationCore.dll」と入力して [OK] をクリックします。

  4. PresentationFramework.dll (C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationFramework.dll) について同様の手順を繰り返します。

  5. WindowsBase.dll (C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll) について同様の手順を繰り返します。

  6. UIAutomationTypes.dll (C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationTypes.dll) について同様の手順を繰り返します。

  7. UIAutomationProvider.dll (C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationProvider.dll) について同様の手順を繰り返します。

  8. [新しい参照の追加] をクリックし、System.dll を選択して [OK] をクリックします。

  9. [OK] をクリックして、参照を追加するための win32clock プロパティ ページを閉じます。

最後に、WPF で使用する _tWinMain メソッドに STAThreadAttribute を追加します。

[System::STAThreadAttribute]
int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)

この属性は、コンポーネント オブジェクト モデル (COM) を初期化するタイミングと、WPF (および Windows フォーム) で必要なシングル スレッド アパートメント モデル (STA) を使用する必要があることを共通言語ランタイム (CLR) に通知します。

Windows Presentation Framework ページの作成

次に、WPF Page を定義する DLL を作成します。通常は、WPF Page をスタンドアロン アプリケーションとして作成し、WPF 部分を単独で記述およびデバッグするのが最も簡単です。作成が完了したら、そのプロジェクトを DLL に変更することができます。DLL に変更するには、プロジェクトを右クリックして [プロパティ] をクリックし、アプリケーションに移動して出力の種類を Windows クラス ライブラリに変更します。

その後、WPF dll プロジェクトと Win32 プロジェクトを結合できます (2 つのプロジェクトを含むソリューション)。これを行うには、ソリューションを右クリックして [既存プロジェクトの追加] をクリックします。

この WPF dll を Win32 プロジェクトから使用するには、次の手順を実行して参照を追加する必要があります。

  1. win32clock プロジェクトを右クリックして [参照設定] をクリックします。

  2. [新しい参照の追加] をクリックします。

  3. [プロジェクト] タブをクリックします。WPFClock を選択して [OK] をクリックします。

  4. [OK] をクリックして、参照を追加するための win32clock プロパティ ページを閉じます。

HwndSource

次に、HwndSource を使用して、WPFPage を HWND に似た外観にします。次のコード ブロックを 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 のサイズと位置を渡すだけで済みます。ただし、ここではリソース ファイルを使用してダイアログを定義したため、HWND の正確な配置場所がわかりません。Microsoft 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="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://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 クロックの相互運用のサンプル」を参照してください。

参照

処理手順

Win32 クロックの相互運用のサンプル

概念

WPF と Win32 の相互運用性に関する概要

参照

HwndSource