Important
有关帮助你理解如何使用 C++/WinRT 使用和编写运行时类的基本概念和术语,请参阅 使用 C++/WinRT 使用 API 和 使用 C++/WinRT 编写 API。
Windows 应用 SDK最强大的功能之一是用户界面 (UI) 堆栈提供的灵活性,用于基于 XAML 控件类型创建自定义控件。 XAML UI 框架提供 自定义依赖属性 和 附加属性和 控件模板等功能,使创建功能丰富且可自定义的控件变得容易。 本主题指导你完成使用 C++/WinRT 创建自定义(模板化)控件的步骤。
创建一个名为 BgLabelControlApp 的空白应用
首先,在 Microsoft Visual Studio 中创建新项目。 为 C++ 创建一个 空白应用,已打包(桌面版 WinUI 3) 项目,将其名称设置为 BgLabelControlApp,并且(为了使你的文件夹结构与本演练一致)确保未选中 将解决方案和项目放在同一目录中。 以最新版且正式发布的 Windows SDK(即非预览版)为目标。
在本主题的后续部分中,系统会引导你构建项目(但在那之前先不要构建)。
注释
有关为 C++/WinRT 开发设置Visual Studio(包括安装和使用 C++/WinRT Visual Studio 扩展(VSIX)和 NuGet 包(一起提供项目模板和生成支持)的信息,请参阅Visual Studio对 C++/WinRT 的支持。
我们将创作一个新类来表示自定义(模板化)控件。 我们在同一个编译单元中编写并使用该类。 但我们希望能够从 XAML 标记实例化此类,因此它将是运行时类。 我们将使用 C++/WinRT 来编写和使用它。
创作新运行时类的第一步是向项目添加新 的 Midl File (.idl) 项。 将其命名为 BgLabelControl.idl。 删除 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 名称加上 属性。 你将在实现中使用此静态属性。 其次,使用 DP 的类型和名称声明读写实例属性。 如果要创作 附加属性 (而不是 DP),请参阅 自定义附加属性中的代码示例。
注释
如果需要具有浮点类型的 DP,请将其 double 设为(Double 在 MIDL 3.0 中)。 声明并实现一个类型为 float 的 DP(在 MIDL 中为 Single),然后在 XAML 标记中为该 DP 设置值,会导致错误 无法从文本“<NUMBER>”创建“Windows.Foundation.Single”。
保存文件。 该项目目前还无法成功完成生成,不过现在进行生成仍然很有用,因为这会生成源代码文件,你将在这些文件中实现 BgLabelControl 运行时类。 因此,现在继续生成(你预计在此阶段看到的生成错误与“未解决的外部符号”有关)。
在构建过程中,会运行 midl.exe 工具来创建用于描述运行时类的 Windows 运行时 元数据文件(\BgLabelControlApp\Debug\BgLabelControlApp\Unmerged\BgLabelControl.winmd)。 然后,该工具 cppwinrt.exe 将运行以生成源代码文件,以支持你创作和使用运行时类。 这些文件包括用于开始实现在 IDL 中声明的 BgLabelControl 运行时类的存根。 这些存根是 \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\BgLabelControl.h 和 BgLabelControl.cpp。
将存根文件BgLabelControl.hBgLabelControl.cpp复制到\BgLabelControlApp\BgLabelControlApp\Generated Files\sources\项目文件夹中,即 \BgLabelControlApp\BgLabelControlApp\。 在解决方案资源管理器中,确保打开“显示所有文件”。 右键单击复制的存根文件,然后单击加入项目。
你会在BgLabelControl.h和BgLabelControl.cpp的顶部看到一个static_assert,你需要将其删除。 现在项目可以构建了。
实现 BgLabelControl 自定义控件类
现在,让我们打开 \BgLabelControlApp\BgLabelControlApp\BgLabelControl.h 并 BgLabelControl.cpp 实现运行时类。 在 BgLabelControl.h中,更改构造函数以设置默认样式键,实现 Label 和 LabelProperty,添加一个名为 OnLabelChanged 的静态事件处理程序以处理对依赖属性的值的更改,并添加一个私有成员来存储 LabelProperty 的后备字段。
添加这些内容后,你的 BgLabelControl.h 如下所示。 可以复制并粘贴此代码列表以替换其内容 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>
{
};
}
在 BgLabelControl.cpp中,定义如下所示的静态成员。 可以复制并粘贴此代码列表以替换其内容 BgLabelControl.cpp。
// BgLabelControl.cpp
#include "pch.h"
#include "BgLabelControl.h"
#include "BgLabelControl.g.cpp"
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)。 并演示如何获取指向实现投影类型的类型的指针。 第二个操作自然只能在实现投影类型的项目(即实现运行时类的项目)中实现。
注释
如果尚未安装 Windows SDK 版本 10.0.17763.0(Windows 10 版本 1809 或更高版本),则需要在上述依赖项属性中调用 winrt::from_abi 更改事件处理程序,而不是 winrt::get_self。
设计 BgLabelControl 的默认样式
在其构造函数中,BgLabelControl 为自己设置默认样式键。 但是 什么是 默认样式? 自定义(模板化)控件需要具有默认样式(包含默认控件模板),当控件使用者未设置样式和/或模板时,该模板可用于呈现自身。 在本部分中,我们将向包含默认样式的项目添加标记文件。
确保显示所有文件在解决方案资源管理器中仍处于开启状态。 在项目节点下,创建新文件夹(不是筛选器,而是文件夹),并将其命名为“主题”。 在下方 Themes,添加 Visual C++>XAML XAML>视图类型的新项,并将其命名为“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 Control 类型的所有实例都具有的 Background 属性)和一个文本元素(其文本绑定到 BgLabelControl::Label 依赖属性)组成。
将 BgLabelControl 的实例添加到主 UI 页面
打开 MainPage.xaml,其中包含主 UI 页面的 XAML 标记。 紧接在 Button 元素(StackPanel 内)之后,添加以下标记 。
<local:BgLabelControl Background="Red" Label="Hello, World!"/>
此外,添加以下 include 指令 MainPage.h ,以便 MainPage 类型(编译 XAML 标记和命令性代码的组合)知道 BgLabelControl 自定义控件类型。 如果要从另一个 XAML 页面使用 BgLabelControl,则也会将此相同的 include 指令添加到该页的头文件中。 或者,也可以将单个 include 指令放在预编译头文件中。
// MainPage.h
...
#include "BgLabelControl.h"
...
现在构建并运行项目。 你将看到默认控件模板在标记中绑定到 BgLabelControl 实例的背景画笔和标签上。
本演练演示了 C++/WinRT 中自定义(模板化)控件的简单示例。 你可以创建自己的自定义控件,并使其内容丰富、功能齐全。 例如,自定义控件可以采用可编辑数据网格、视频播放器或三维几何图形可视化工具等复杂内容的形式。
实现 可重写 方法,例如 MeasureOverride 和 OnApplyTemplate
请参阅 使用 C++/WinRT 调用和重写基类型中的相关章节。