チュートリアル: Win32 で WPF の時計をホストする

Win32 アプリケーション内に WPF を配置するには、HwndSource を使用して、WPF コンテンツが含まれる HWND を取得します。 まず HwndSource を作成し、CreateWindow のようなパラメーターを指定します。 次に、その中で必要な WPF コンテンツについて HwndSource に指示します。 最後に、HwndSource から HWND を取得します。 このチュートリアルでは、Win32 アプリケーション内に混合 WPF を作成して、オペレーティング システムの [日付と時刻のプロパティ] ダイアログを再実装する方法について説明します。

必須コンポーネント

WPF と Win32 の相互運用性」を参照してください

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

このチュートリアルでは、相互運用アプリケーションを作成するための重要な手順に焦点を当てています。 このチュートリアルはサンプル Win32 の時計の相互運用性サンプルを利用していますが、このサンプルは完成品のお手本です。 このチュートリアルでは、ご自分の既存の Win32 プロジェクト (おそらく、前から存在するプロジェクト) から始めて、ホストされている WPF をアプリケーションに追加する場合を仮定して手順を説明します。 完成品は Win32 の時計の相互運用性サンプルと比較することができます。

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

次の図は、このチュートリアルの完成品を示しています。

Screenshot that shows the Date and Time Properties dialog box.

このダイアログを再現するには、Visual Studio で C++ Win32 プロジェクトを作成し、ダイアログ エディターを使用して次のものを作成します。

Recreated Date and Time Properties dialog box

(HwndSource を使用するために Visual Studio を使用する必要はありません。また、Win32 プログラムを作成するために C++ を使用する必要もありませんが、これはかなり一般的な方法であり、段階的なチュートリアルの説明に適しています)。

ダイアログに WPF の時計を配置するには、さらに次の 5 つの手順を個別に行う必要があります。

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

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

  3. その WPFPageHwndSource 内に配置します。

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

  5. Win32 を使用して、HWND をより大きな Win32 アプリケーション内のどこに配置するかを決定します

/clr

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

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 の [プロパティ] ページを終了します。

最後に、STAThreadAttribute_tWinMain メソッドに追加して、WPF で使用できるようにします。

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

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

Windows Presentation Framework ページを作成する

次に、WPFPage を定義する DLL を作成します。 多くの場合、WPFPage をスタンドアロン アプリケーションとして作成し、それに従って WPF 部分を記述してデバッグする方法が最も簡単です。 完了したら、プロジェクトを右クリックし、 [プロパティ] をクリックして、アプリケーションに移動し、出力の種類を Windows クラス ライブラリに変更することで、そのプロジェクトを DLL に変換することができます。

次に、WPF dll プロジェクトを Win32 プロジェクト (2 つのプロジェクトを含む 1 つのソリューション) と組み合わせることができます。ソリューションを右クリックし、[既存プロジェクトの追加] を選択します。

Win32 プロジェクトから WPF dll を使用するには、参照を追加する必要があります。

  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 のどこに配置されているかは正確にはわかりません。 Visual Studio のダイアログ エディターを使用して、Win32 STATIC コントロールを時計の移動先 ("Insert clock here" (ここに時計を挿入)) に配置し、それを使用して 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);
        }
    }
}

最終的な結果は次のようになります。

Final result Date and Time Properties dialog box

最終的な結果をこのスクリーンショットの生成に使ったコードと比較するには、Win32 の時計の相互運用性サンプルを参照してください。

関連項目