Share via


逐步解說:在 Win32 中裝載 WPF 時鐘

若要將 WPF 放在 Win32 應用程式內,請使用 HwndSource ,其提供包含 WPF 內容的 HWND。 首先,您會建立 HwndSource ,並提供類似于 CreateWindow 的參數。 然後,您會告訴 HwndSource 它內想要的 WPF 內容。 最後,您會從 HwndSource 取得 HWND。 本逐步解說說明如何在 Win32 應用程式中建立混合 WPF,以重新實作作業系統 [日期和時間屬性 ] 對話方塊。

必要條件

請參閱 WPF 和 Win32 交互操作

如何使用本教學課程

本教學課程著重在產生交互操作應用程式的重要步驟。 本教學課程是以 Win32 時鐘交互操作範例這個範例進行備援,但該範例反映最終產品。 本教學課程記載的步驟,就像您從自己的現有 Win32 專案開始,可能是預先存在的專案,而且您正在將裝載的 WPF 新增至應用程式。 您可以比較最終產品與 Win32 時鐘交互操作範例

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

下圖顯示本教學課程的預期最終產品︰

Screenshot that shows the Date and Time Properties dialog box.

您可以在 Visual Studio 中建立 C++ Win32 專案,並使用對話方塊編輯器來建立下列專案,以重新建立此對話方塊:

Recreated Date and Time Properties dialog box

(您不需要使用 Visual Studio 來使用 HwndSource ,而且您不需要使用 C++ 來撰寫 Win32 程式,但這是相當典型的做法,而且適合進行逐步教學課程說明。

您需要完成五個特定的子步驟,才能將 WPF 時鐘放入對話方塊中:

  1. 藉由變更 Visual Studio 中的專案設定,讓您的 Win32 專案呼叫 Managed 程式碼 ( /clr )。

  2. 在不同的 DLL 中建立 WPF Page

  3. 將該 WPF Page 放在 內 HwndSource

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

  5. 使用 Win32 決定將 HWND 放在較大的 Win32 應用程式中的位置

/clr

第一個步驟是將此 Unmanaged Win32 專案轉換成可呼叫 Managed 程式碼的專案。 您可以使用 /clr 編譯器選項,它會連結至您想要使用的必要 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 Forms) 所需的單一執行緒 Apartment 模型 (STA)。

建立 Windows Presentation Framework 頁面

接下來,您會建立定義 WPF Page 的 DLL。 建立 WPF Page 做為獨立應用程式通常最容易,並以這種方式撰寫和偵錯 WPF 部分。 完成之後,以滑鼠右鍵按一下專案、按一下 [屬性]、移至 [應用程式],並將 [輸出類型] 變更為 [Windows 類別庫],即可將該專案轉換為 DLL。

WPF dll 專案接著可以與 Win32 專案合併(一個包含兩個專案的方案)–以滑鼠右鍵按一下方案,選取 [新增\現有專案 ]。

若要從 Win32 專案使用該 WPF dll,您需要新增參考:

  1. 以滑鼠右鍵按一下 win32clock 專案,然後選取 [參考...]

  2. 按一下 [ 新增參考 ]。

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

  4. 按一下 [確定] 結束 [win32clock 屬性頁] 來新增參考。

HwndSource

接下來,您會使用 HwndSource 來讓 WPF Page 看起來像 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 內容類別別的建構函式來建立 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);
        }
    }
}

最終結果如下:

Final result Date and Time Properties dialog box

若要比較產生此螢幕擷取畫面之程式碼的最後結果,請參閱 Win32 時鐘交互操作範例

另請參閱