本文將逐步引導您使用 C++/WinRT 建立 WinUI 3 的範本化 XAML 控制件。 樣板化控件繼承自 Microsoft.UI.Xaml.Controls.Control ,並且具有可使用 XAML 控件範本自定義的視覺結構和視覺行為。 本文描述的案例與文章 XAML 自訂(樣板化)控制項中使用 C++/WinRT 的場景相同,但已調整為使用 WinUI 3。
先決條件
創建空白應用程式(BgLabelControlApp)
從在Visual Studio Microsoft建立新項目開始。 在 Create a new project
對話框中,選取 空白應用程式 (UWP 中的 WinUI) 項目範本,請務必選取C++語言版本。 將專案名稱設定為 「BgLabelControlApp」,讓檔名與下列範例中的程式碼一致。 將 [目標版本 ] 設定為 Windows 10 版本 1903(組建 18362),並將 [最低版本 ] 設定為 Windows 10 版本 1803(組建 17134)。 本逐步解說也適用於使用 空白應用程式、已封裝 (WinUI in Desktop) 項目範本建立的桌面應用程式,只要確定執行 BgLabelControlApp (Desktop) 專案中的所有步驟。
將樣板化控件新增至您的應用程式
若要新增樣板化控件,請單擊工具列中的 [專案 ] 功能表,或以滑鼠右鍵按兩下 方案總 管中的項目,然後選取[ 新增專案 ]。 在 Visual C++->WinUI 底下,選取 [自定義控件] [WinUI] 範本。 將新控件命名為 “BgLabelControl”,然後點選 [新增]。 這會將三個新檔案新增至您的專案。
BgLabelControl.h
是包含控件宣告的標頭,包含 BgLabelControl.cpp
控件的C++/WinRT 實作。
BgLabelControl.idl
是介面定義檔案,可讓控件具現化為運行時間類別。
實作 BgLabelControl 自定義控件類別
在下列步驟中,您將更新專案目錄中的 BgLabelControl.idl
、BgLabelControl.h
和 BgLabelControl.cpp
檔案中的程式代碼,以實作執行時類別。
範本控制項類別將從 XAML 標記具現化,因此它會是執行階段類別。 當您建置完成的專案時,MIDL 編譯程式 (midl.exe) 會使用 BgLabelControl.idl
檔案來產生控件的 Windows 運行時間元數據檔案 (.winmd),而元件取用者將會參考該檔案。 如需建立運行時類別的詳細資訊,請參閱 使用 C++/WinRT 撰寫 API。
我們建立的樣板化控件會公開單一屬性,這是將做為控件標籤的字串。 將 BgLabelControl.idl
的內容替換為下列代碼。
// BgLabelControl.idl
namespace BgLabelControlApp
{
runtimeclass BgLabelControl : Microsoft.UI.Xaml.Controls.Control
{
BgLabelControl();
static Microsoft.UI.Xaml.DependencyProperty LabelProperty{ get; };
String Label;
}
}
上述清單顯示宣告相依性屬性 (DP) 時所遵循的模式。 每個 DP 都由兩個部分組成。 首先,您會宣告 DependencyProperty 類型的唯讀靜態屬性。 它的名稱是你的 DP plus Property。 您將在實作中使用這個靜態屬性。 其次,您會使用 DP 的類型和名稱來宣告讀寫實例屬性。 如果您想要撰寫附加屬性(而非 DP),請參閱 自定義附加屬性中的程式碼範例。
請注意,上述程式代碼中所參考的 XAML 類別位於 Microsoft.UI.Xaml 命名空間中。 這是將它們作為 WinUI 控制項區分開來,而不是作為定義在 Windows.UI.XAML 命名空間下的 UWP XAML 控制項。
以下列程序代碼取代 BgLabelControl.h 的內容。
// BgLabelControl.h
#pragma once
#include "BgLabelControl.g.h"
namespace winrt::BgLabelControlApp::implementation
{
struct BgLabelControl : BgLabelControlT<BgLabelControl>
{
BgLabelControl() { DefaultStyleKey(winrt::box_value(L"BgLabelControlApp.BgLabelControl")); }
winrt::hstring Label()
{
return winrt::unbox_value<winrt::hstring>(GetValue(m_labelProperty));
}
void Label(winrt::hstring const& value)
{
SetValue(m_labelProperty, winrt::box_value(value));
}
static Microsoft::UI::Xaml::DependencyProperty LabelProperty() { return m_labelProperty; }
static void OnLabelChanged(Microsoft::UI::Xaml::DependencyObject const&, Microsoft::UI::Xaml::DependencyPropertyChangedEventArgs const&);
private:
static Microsoft::UI::Xaml::DependencyProperty m_labelProperty;
};
}
namespace winrt::BgLabelControlApp::factory_implementation
{
struct BgLabelControl : BgLabelControlT<BgLabelControl, implementation::BgLabelControl>
{
};
}
上述程式代碼會實作 Label 和 LabelProperty 屬性、新增名為 OnLabelChanged 的靜態事件處理程式,以處理相依性屬性值的變更,並新增私人成員來儲存 LabelProperty 的備份欄位。 同樣地,請注意,頭檔中參考的 XAML 類別位於屬於 WinUI 3 架構的 Microsoft.UI.Xaml 命名空間中,而不是 UWP UI 架構所使用的 Windows.UI.Xaml 命名空間。
接下來,以下列程式代碼取代BgLabelControl.cpp的內容。
// BgLabelControl.cpp
#include "pch.h"
#include "BgLabelControl.h"
#if __has_include("BgLabelControl.g.cpp")
#include "BgLabelControl.g.cpp"
#endif
namespace winrt::BgLabelControlApp::implementation
{
Microsoft::UI::Xaml::DependencyProperty BgLabelControl::m_labelProperty =
Microsoft::UI::Xaml::DependencyProperty::Register(
L"Label",
winrt::xaml_typename<winrt::hstring>(),
winrt::xaml_typename<BgLabelControlApp::BgLabelControl>(),
Microsoft::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Microsoft::UI::Xaml::PropertyChangedCallback{ &BgLabelControl::OnLabelChanged } }
);
void BgLabelControl::OnLabelChanged(Microsoft::UI::Xaml::DependencyObject const& d, Microsoft::UI::Xaml::DependencyPropertyChangedEventArgs const& /* e */)
{
if (BgLabelControlApp::BgLabelControl theControl{ d.try_as<BgLabelControlApp::BgLabelControl>() })
{
// Call members of the projected type via theControl.
BgLabelControlApp::implementation::BgLabelControl* ptr{ winrt::get_self<BgLabelControlApp::implementation::BgLabelControl>(theControl) };
// Call members of the implementation type via ptr.
}
}
}
本逐步解說不會使用 OnLabelChanged 回呼,但我們會提供此資訊,以便您了解如何使用屬性變更的回呼來註冊相依性屬性。 OnLabelChanged 的實作也會示範如何從基底投影類型取得衍生的投影類型(在此案例中,基底投影類型為 DependencyObject)。 並展示如何取得實作特定投影型別之類型的指標。 第二個作業自然只能在實作投影型別的項目中執行(也就是實作運行時間類別的專案)。
xaml_typename函式是由 WinUI 3 專案範本中預設不包含的 Windows.UI.Xaml.Interop 命名空間所提供。 將一行加入至您的專案先行編譯的標頭檔 pch.h
,以包含與此命名空間相關的標頭檔。
// pch.h
...
#include <winrt/Windows.UI.Xaml.Interop.h>
...
定義 BgLabelControl 的預設樣式
在其建構函式中,BgLabelControl 自行設定預設風格鍵。 具有模板的控制項必須具有一個默認樣式,包含一個預設控制項模板,當控制項的使用者未設定樣式和/或模板時,可以使用它來呈現自身。 在本節中,我們會將標記檔案新增至包含默認樣式的專案。
請確定 [顯示所有檔案] 仍已切換為開啟狀態(在 [方案總管]中)。 在您的項目節點下,建立新的資料夾(不是篩選,而是資料夾),並將它命名為 “Themes”。 在 [Themes
] 下,新增一個類型為 [] Visual C++ [>] WinUI [>] 資源字典(WinUI)[] 的新項目,並將其命名為 "Generic.xaml"。 資料夾和檔名必須像這樣,XAML 架構才能尋找樣板化控件的預設樣式。 刪除 Generic.xaml 的預設內容,並貼上下列標記。
<!-- \Themes\Generic.xaml -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BgLabelControlApp">
<Style TargetType="local:BgLabelControl" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:BgLabelControl">
<Grid Width="100" Height="100" Background="{TemplateBinding Background}">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{TemplateBinding Label}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
在此情況下,預設樣式集的唯一屬性是控件範本。 此範本包含一個正方形(其背景系結至 XAML 控件類型所有實例的 Background 屬性),以及一個文字元素(其文字系結至 BgLabelControl::Label 相依性屬性)。
將 BgLabelControl 的實體新增至主要 UI 頁面
開啟 MainWindow.xaml
,其中包含我們主要UI頁面的 XAML 標記。 緊接在 Button 元素後面(StackPanel內),新增下列標記。
<local:BgLabelControl Background="Red" Label="Hello, World!"/>
此外,將下列 include 指示詞新增至 MainWindow.h
,讓 MainWindow 類型(編譯 XAML 標記和命令式程式代碼的組合)知道 BgLabelControl 樣板化控件類型。 如果您想從另一個 XAML 頁面使用 BgLabelControl,請將這個相同的 include 指令新增至該頁面的頭檔。 或者,只要將單一 include 指令放在預編譯標頭檔案中即可。
//MainWindow.h
...
#include "BgLabelControl.h"
...
現在建置並執行專案。 您會看到預設控件範本綁定到標記中 BgLabelControl 實例的背景筆刷和標籤。
實現可覆寫的函式,例如 MeasureOverride 和 OnApplyTemplate
您可以從 Control 執行時間類別衍生樣板化控件,而此類別本身會進一步衍生自基底運行時間類別。 而且您可以在衍生類別中覆寫 Control、FrameworkElement和 UIElement 的方法。 以下是示範如何執行此動作的程式代碼範例。
// Control overrides.
void OnPointerPressed(Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& /* e */) const { ... };
// FrameworkElement overrides.
Windows::Foundation::Size MeasureOverride(Windows::Foundation::Size const& /* availableSize */) const { ... };
void OnApplyTemplate() const { ... };
// UIElement overrides.
Microsoft::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer() const { ... };
可覆寫的 函式在不同的語言投影中會有不同的表現方式。 例如,在 C# 中,可覆寫的函式通常會顯示為受保護的虛擬函式。 在 C++/WinRT 中,它們既不是虛擬也不是受保護的,但您仍然可以覆寫它們並提供您自己的實作,如上所示。
產生控件原始程式檔而不使用範本。
本節說明如何在不使用 自定義控件 專案範本的情況下,產生建立自定義控件所需的原始程序檔。
首先,將新的 Midl 檔案 (.idl) 專案新增至專案。 從 [專案] 功能表中,選取 [新增項目...],然後在搜尋方塊中輸入 "MIDL",以尋找 .idl 檔案項目。 將新檔案 BgLabelControl.idl
命名為 ,讓名稱與本文中的步驟一致。 刪除BgLabelControl.idl
中的默認內容,然後貼上上述步驟中顯示的運行時類別宣告。
儲存新的 .idl 檔案之後,下一個步驟是針對您將用來實作樣板化控件的 .cpp 和 .h 實作檔案產生 Windows 運行時間元數據檔案 (.winmd) 和存根。 建置方案以產生這些檔案,這會導致 MIDL 編譯程式 (midl.exe) 編譯您所建立的 .idl 檔案。 請注意,解決方案不會成功建置,而且Visual Studio會在輸出視窗中顯示建置錯誤,但會產生必要的檔案。
將存根檔案 BgLabelControl.h 和BgLabelControl.cpp從 \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\ 複製到項目資料夾。 在 [方案總管]中,確保[顯示所有檔案]已開啟。 使用滑鼠右鍵按下您複製的存根檔案,然後選擇 [包含在專案中]。
編譯程式會將 static_assert 行放在 BgLabelControl.h 和 BgLabelControl.cpp 的頂端,以防止產生的檔案被編譯。 實作控件時,您應該從您放置於專案目錄中的檔案中移除這些行。 在本逐步解說中,您可以只使用上述程式代碼覆寫檔案的全部內容。