Share via


教學課程:建立裝載 WPF 內容的 Win32 應用程式

更新:2007 年 11 月

若要將 WPF 放入 Win32 應用程式內,請使用 HwndSource (其提供內含 WPF 內容的 HWND)。首先請建立 HwndSource,並為其指定類似 CreateWindow 的參數。接著,請告知 HwndSource 您希望它包含的 WPF 內容。最後,請從 HwndSource 取得 HWND。此逐步解說在說明如何在 Win32 應用程式內建立混合的 WPF,以實作作業系統的 [日期和時間內容] 對話方塊。

必要條件

請參閱 WPF 和 Win32 互通性概觀

如何使用本教學課程

本教學課程的重點為產生互通性應用程式的重要步驟。課程中以 Win32 時鐘互通性範例做為教學輔助,不過這個範例反映的是最終的成果。本教學課程記載步驟的方式就像您以自己現有的 Win32 專案 (可能是預先存在的專案) 開始作業,並將裝載 WPF 加入您的應用程式中。您可以比較自己的最終成果與 Win32 時鐘互通性範例

Win32 內的 Windows Presentation Framework 逐步解說 (HwndSource)

下圖顯示本教學課程預定產生的最終成果:

日期和時間內容對話方塊

您可以在 Microsoft Visual Studio 中建立 C++ Win32 專案,並使用對話方塊編輯器建立下列項目,以便重新建立這個對話方塊:

日期和時間內容對話方塊

(您不需要使用 Microsoft Visual Studio 來使用 HwndSource,也不需要使用 C++ 來撰寫 Win32 程式,但這是執行這項工作相當典型的方式,而且可為其本身提供逐步的教學說明)。

您必須完成五個特殊的子步驟,才能將 WPF 時鐘放入對話方塊中。

  1. 透過在 Microsoft Visual Studio 中變更專案設定,使您的 Win32 專案能夠呼叫 Managed 程式碼 (/clr)。

  2. 在個別的 DLL 中建立 WPFPage

  3. 將該 WPFPage 放入 HwndSource 內。

  4. 使用 Handle 屬性取得該 Page 的 HWND。

  5. 使用 Win32,決定在較大的 Win32 應用程式內放置 HWND 的位置。

/clr

第一個步驟是要將這個 Unmanaged Win32 專案變成可以呼叫 Managed 程式碼的專案。請使用 /clr 編譯器 (Compiler) 選項,以連結到您想使用的必要 DLL,並調整 Main 方法以搭配 WPF 使用。

若要啟用在 C++ 專案中使用 Managed 程式碼的功能,請以滑鼠右鍵按一下 win32clock 專案,然後選取 [屬性]。接著在 [一般] 屬性頁 (預設值) 上,將 Common Language Runtime 支援變更為 /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,然後按 [確定]。

  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,然後按一下 [確定]。

  9. 按一下 [確定],結束用來加入參考的 [win32clock 屬性頁]。

最後,將 STAThreadAttribute 加入 _tWinMain 方法中,以搭配 WPF 使用:

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

這個屬性會告知 Common Language Runtime (CLR),當它初始化元件物件模型 (COM) 時,應該使用 WPF (和 Windows Form) 所需的單一執行緒 Apartment Model (STA)。

建立 Windows Presentation Framework 頁面

接著,請建立定義 WPFPage 的 DLL。最簡單的方法通常是建立 WPFPage 做為獨立的應用程式,然後再以這種方式撰寫並偵錯 WPF 的部分。完成之後,可以使用滑鼠右鍵按一下該專案,再按一下 [屬性],移到 [應用程式],然後將 [輸出類型] 改成 [Windows 類別庫],將專案轉換成 DLL。

接著 WPF dll 專案可以和 Win32 專案合併 (一個方案包含兩個專案) – 以滑鼠右鍵按一下方案,並選取 [加入現有專案]。

若要從 Win32 專案使用該 WPF dll,您必須加入參考。

  1. 以滑鼠右鍵按一下 win32clock,並選取 [參考]。

  2. 按一下 [加入新參考]。

  3. 按一下 [專案] 索引標籤。選取 WPFClock,再按一下 [確定]。

  4. 按一下 [確定],結束用來加入參考的 [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) {

首先請建立 HwndSource,其參數與 CreateWindow 類似:

        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 的正確位置。您可以使用 Microsoft Visual Studio 對話方塊編輯器,將 Win32 STATIC 控制項放在要顯示時鐘的位置 (「在此插入時鐘」),並使用該控制項來定位 WPF 時鐘。

處理 WM_INITDIALOG 時,您會使用 GetDlgItem 來擷取預留位置 STATIC 的 HWND:

HWND placeholder = GetDlgItem(hDlg, IDC_CLOCK);

接著再計算預留位置 STATIC 的大小和位置,使您可以將 WPF 時鐘放在該位置:

RECT rectangle;

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 時鐘控制項。您大多可以在標記中執行這項作業,而程式碼後置 (Code-Behind) 中只需包含幾個事件處理常式 (Event Handler)。由於這個教學課程是關於互通性,而不是關於控制項設計,因此這裡提供了 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