从 C# 移动到 C++/WinRT

提示

如果你之前已阅读过本主题,并带着特定任务回来复习,可以跳转到本主题的基于要执行的任务查找内容部分。

本主题对 C# 项目中的源代码移植到 C++/WinRT 项目中的等效项时所涉及的技术细节进行了全面分类。

有关移植某一通用 Windows 平台 (UWP) 应用示例的案例研究,请参阅对应的主题将 Clipboard 示例从 C# 移植到 C++/WinRT。 可以通过按照该演练操作并在操作时为自己移植示例,来获取移植实践和体验。

如何准备以及预期的结果

案例研究将 Clipboard 示例从 C# 移植到 C++/WinRT 阐释了在将项目迁移到 C++/WinRT 时要做出的软件设计决策类型的示例。 因此,最好是通过深入了解现有代码的工作原理来准备迁移。 这样,可以大致了解应用的功能和代码的结构,然后,你做出的决策就会始终引领你向正确的方向前进。

在所需的移植更改的类型方面,可以将其分组为四个类别。

  • 移植语言投影。 Windows 运行时 (WinRT) 已投影到各种编程语言。 其中的每个语言投影均已设计为符合所涉及的编程语言的语言习惯。 对于 C#,某些 Windows 运行时类型被投影为 .NET 类型。 因此,例如,你会将 System.Collections.Generic.IReadOnlyList<T> 转换回 Windows.Foundation.Collections.IVectorView<T>。 此外,在 C# 中,某些 Windows 运行时操作也被投影为方便的 C# 语言功能。 例如,在 C# 中,可以使用 += 运算符语法来注册事件处理委托。 因此,你将转换语言功能,例如,转换回要执行的基本操作(在本示例中为事件注册)。
  • 移植语言语法。 这些更改中的许多更改都是简单的机械转换(将一个符号替换为另一个符号)。 例如,将点 (.) 更改为双冒号 (::)。
  • 移植语言过程。 其中的一些可能是简单、重复的更改(例如 myObject.MyPropertymyObject.MyProperty())。 其他移植则需要更深层次的更改(例如,将涉及使用 System.Text.StringBuilder 的过程迁移到涉及使用 std::wostringstream 的过程)。
  • 与移植相关且特定于 C++/WinRT 的任务。 Windows 运行时的某些详细信息会在后台由 C# 隐式处理。 这些详细信息在 C++/WinRT 中是显式处理的。 例如,使用 .idl 文件来定义运行时类。

在后续基于任务的索引之后,本主题中其余部分的结构遵循上面的分类。

根据要执行的任务查找内容

任务 内容
创作 Windows 运行时组件 (WRC) 只能通过 C++ 实现特定的功能(或调用特定的 API)。 可以将该功能编写为 C++/WINRT WRC,然后从 C#(或其他语言的)应用中使用该 WRC。 请参阅使用 C++/WinRT 的 Windows 运行时组件如果要在 Windows 运行时组件中创作运行时类
移植异步方法 最好将 C++/WinRT 运行时类中异步方法的第一行设为 auto lifetime = get_strong();(请参阅在类成员协同程序中安全访问 this 指针)。

Task 移植,请参阅异步行为
Task<T> 移植,请参阅异步操作
async void 移植,请参阅发后不理方法
移植类 首先,确定该类是必须为运行时类,还是可以是普通类。 若要帮助你确定这一信息,请参阅使用 C++/WinRT 创作 API的开始部分。 然后,请参阅下面三行。
移植运行时类 用于在 C++ 应用外部共享功能的类,或在 XAML 数据绑定中使用的类。 请参阅如果要在 Windows 运行时组件中创作运行时类如果要创作在 XAML UI 中引用的运行时类

这些链接对此进行了更详细的介绍,但必须在 IDL 中声明运行时类。 如果你的项目已包含 IDL 文件(例如,Project.idl),建议在该文件中声明任何新的运行时类。 在 IDL 中,声明将在应用外使用或将在 XAML 中使用的任何方法和数据成员。 在更新 IDL 文件后重新生成,在解决方案资源管理器中,选中项目节点,并确保打开“显示所有文件”,查看项目 Generated Files 文件夹中生成的存根文件(.h.cpp)。 将存根文件与项目中已有的文件进行比较,并根据需要添加文件或添加/更新函数签名。 存根文件语法始终是正确的,因此建议使用它来最大程度地减少生成错误。 只要项目中的存根与存根文件中的相匹配,就可以通过移植 C# 代码来实现它们。
移植普通类 请参阅如果你没有创作运行时类
创作 IDL Microsoft 接口定义语言 3.0 简介
如果你正在创作要在 XAML UI 中引用的运行时类
使用 XAML 标记中的对象
在 IDL 中定义运行时类
移植集合 使用 C++/WinRT 的集合
使数据源可供 XAML 标记使用
关联容器
矢量成员访问
移植事件 作为类成员的事件处理程序委托
撤销事件处理程序委托
移植方法 从 C# :private async void SampleButton_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) { ... }
到 C++/WinRT .h 文件:fire_and_forget SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&);
到 C++/WinRT .cpp 文件:fire_and_forget OcrFileImage::SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&) {...}
移植字符串 C++/WinRT 中的字符串处理
ToString
字符串生成
将字符串装箱和取消装箱
类型转换(类型强制转换) C#:o.ToString()
C++/WinRT:to_hstring(static_cast<int>(o))
另请参阅 ToString

C#:(Value)o
C++/WinRT:unbox_value<Value>(o)
在取消装箱失败时引发。 另请参阅装箱和取消装箱

C#:o as Value? ?? fallback
C++/WinRT:unbox_value_or<Value>(o, fallback)
如果取消装箱失败,则返回回退。 另请参阅装箱和取消装箱

C#:(Class)o
C++/WinRT:o.as<Class>()
在转换失败时引发。

C#:o as Class
C++/WinRT:o.try_as<Class>()
如果转换失败,则返回 null。

涉及语言投影的更改

类别 C# C++/WinRT 另请参阅
非类型化对象 objectSystem.Object Windows::Foundation::IInspectable 移植 EnableClipboardContentChangedNotifications 方法
投影命名空间 using System; using namespace Windows::Foundation;
using System.Collections.Generic; using namespace Windows::Foundation::Collections;
集合大小 collection.Count collection.Size() 移植 BuildClipboardFormatsOutputString 方法
典型集合类型 IList<T>,以及用用于添加元素的“Add” IVector<T>,以及用用于添加元素的“Append”。 如果在任意位置使用 std::vector,则通过 push_back 添加元素 。
只读集合类型 IReadOnlyList<T> IVectorView<T> 移植 BuildClipboardFormatsOutputString 方法
作为类成员的事件处理程序委托 myObject.EventName += Handler; token = myObject.EventName({ get_weak(), &Class::Handler }); 移植 EnableClipboardContentChangedNotifications 方法
撤销事件处理程序委托 myObject.EventName -= Handler; myObject.EventName(token); 移植 EnableClipboardContentChangedNotifications 方法
关联容器 IDictionary<K, V> IMap<K, V>
矢量成员访问 x = v[i];
v[i] = x;
x = v.GetAt(i);
v.SetAt(i, x);

注册/撤销事件处理程序

在 C++/WinRT 中,可以使用多个语法选项注册/撤销事件处理程序委托,如在 C++/WinRT 中使用委托处理事件中所述。 另请参阅移植 EnableClipboardContentChangedNotifications 方法

有时,例如,当事件接收方(处理事件的对象)即将销毁时,你需要撤销事件处理程序,以使事件源(引发事件的对象)不会调用已销毁的对象。 请参阅撤销已注册的委托。 在此类情况下,请为事件处理程序创建一个 event_token 成员变量。 有关示例,请参阅移植 EnableClipboardContentChangedNotifications 方法

还可以使用 XAML 标记注册事件处理程序。

<Button x:Name="OpenButton" Click="OpenButton_Click" />

在 C# 中,OpenButton_Click 方法可以是专用的,但 XAML 仍然能够将它连接到 OpenButton 引发的 ButtonBase.Click 事件。

在 C++/WinRT 中,OpenButton_Click 方法在你的实现类型中必须是公共的(如果你想要使用 XAML 标记注册该方法)。 如果只在命令性代码中注册事件处理程序,则该事件处理程序不需要是公共的。

namespace winrt::MyProject::implementation
{
    struct MyPage : MyPageT<MyPage>
    {
        void OpenButton_Click(
            winrt::Windows:Foundation::IInspectable const& sender,
            winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
    }
};

也可将注册 XAML 页设置为你的实现类型的友元,并将 OpenButton_Click 设置为专用。

namespace winrt::MyProject::implementation
{
    struct MyPage : MyPageT<MyPage>
    {
    private:
        friend MyPageT;
        void OpenButton_Click(
            winrt::Windows:Foundation::IInspectable const& sender,
            winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
    }
};

最后一种方案是你要移植的 C# 项目绑定到标记中的事件处理程序(有关该方案的更多背景信息,请参阅 x:Bind 中的函数)。

<Button x:Name="OpenButton" Click="{x:Bind OpenButton_Click}" />

只需将该标记更改为更简单的 Click="OpenButton_Click" 即可。 如果希望,也可将该标记保留原样。 要支持此操作,你只需在 IDL 中声明事件处理程序即可。

void OpenButton_Click(Object sender, Windows.UI.Xaml.RoutedEventArgs e);

注意

将函数声明为 void,即使你将它作为发后不理 (Fire and forget) 来实现也是如此。

涉及语言语法的更改

类别 C# C++/WinRT 另请参阅
访问修饰符 public \<member\> public:
    \<member\>
移植 Button_Click 方法
访问数据成员 this.variable this->variable  
异步行为 async Task ... IAsyncAction ... IAsyncAction 接口使用 C++/WinRT 执行并发和异步操作
异步操作 async Task<T> ... IAsyncOperation<T> ... IAsyncOperation 接口使用 C++/WinRT 执行并发和异步操作
“发后不理”方法(意味着异步) async void ... winrt::fire_and_forget ... 移植 CopyButton_Click 方法发后不理
访问枚举常量 E.Value E::Value 移植 DisplayChangedFormats 方法
配合地等待 await ... co_await ... 移植 CopyButton_Click 方法
作为私有字段的投影类型集合 private List<MyRuntimeClass> myRuntimeClasses = new List<MyRuntimeClass>(); std::vector
<MyNamespace::MyRuntimeClass>
m_myRuntimeClasses;
GUID 构造 private static readonly Guid myGuid = new Guid("C380465D-2271-428C-9B83-ECEA3B4A85C1"); winrt::guid myGuid{ 0xC380465D, 0x2271, 0x428C, { 0x9B, 0x83, 0xEC, 0xEA, 0x3B, 0x4A, 0x85, 0xC1} };
命名空间分隔符 A.B.T A::B::T
Null null nullptr 移植 UpdateStatus 方法
获取类型对象 typeof(MyType) winrt::xaml_typename<MyType>() 移植 Scenarios 属性
方法的参数声明 MyType MyType const& 参数传递
异步方法的参数声明 MyType MyType 参数传递
调用静态方法 T.Method() T::Method()
字符串 stringSystem.String winrt::hstring C++/WinRT 中的字符串处理
字符串文本 "a string literal" L"a string literal" 移植构造函数 CurrentFEATURE_NAME
推断(或推导)的类型 var auto 移植 BuildClipboardFormatsOutputString 方法
Using 指令 using A.B.C; using namespace A::B::C; 移植构造函数 CurrentFEATURE_NAME
原义/原始字符串文本 @"verbatim string literal" LR"(raw string literal)" 移植 DisplayToast 方法

注意

如果头文件没有包含用于给定命名空间的 using namespace 指令,则必须完全限定该命名空间的所有类型名称;或者至少对它们进行充分限定,以使编译器可以找到它们。 有关示例,请参阅移植 DisplayToast 方法

移植类和成员

对于每种 C# 类型,都需要确定是将它移植到 Windows 运行时类型,还是移植到常规 C++ 类/结构/枚举。 有关详细信息以及演示如何做出这些决策的详细示例,请参阅将 Clipboard 示例从 C# 移植到 C++/WinRT

一个 C# 属性通常会成为一个访问器函数、一个赋值函数和一个支持数据成员。 有关详细信息和示例,请参阅移植 IsClipboardContentChangedEnabled 属性

对于非静态字段,请使其成为你的实现类型的数据成员。

C# 静态字段会成为 C++/WinRT 静态访问器和/或赋值函数。 有关详细信息和示例,请参阅移植构造函数 CurrentFEATURE_NAME

对于成员函数,你也需要为每个成员函数决定其是否属于 IDL,或者它是实现类型的公共成员函数还是私有成员函数。 有关详细信息以及如何决定的示例,请参阅 MainPage 类型的 IDL

移植 XAML 标记和资产文件

将 Clipboard 示例从 C# 移植到 C++/WinRT的案例中,我们能够在 C# 和 C++/WINRT 项目中使用相同的 XAML 标记(包括资源)和资产文件。 在某些情况下,需要编辑标记才能实现此目的。 请参阅复制完成移植 MainPage 所需的 XAML 和样式

涉及语言内过程的更改

类别 C# C++/WinRT 另请参阅
异步方法中的生存期管理 N/A auto lifetime{ get_strong() };
auto lifetime = get_strong();
移植 CopyButton_Click 方法
处置 using (var t = v) auto t{ v };
t.Close(); // or let wrapper destructor do the work
移植 CopyImage 方法
构造对象 new MyType(args) MyType{ args }
MyType(args)
移植 Scenarios 属性
创建未初始化的引用 MyType myObject; MyType myObject{ nullptr };
MyType myObject = nullptr;
移植构造函数 CurrentFEATURE_NAME
使用参数将对象构造到变量中 var myObject = new MyType(args); auto myObject{ MyType{ args } };
auto myObject{ MyType(args) };
auto myObject = MyType{ args };
auto myObject = MyType(args);
MyType myObject{ args };
MyType myObject(args);
移植 Footer_Click 方法
在不使用参数的情况下将对象构造到变量中 var myObject = new T(); MyType myObject; 移植 BuildClipboardFormatsOutputString 方法
对象初始化速记 var p = new FileOpenPicker{
    ViewMode = PickerViewMode.List
};
FileOpenPicker p;
p.ViewMode(PickerViewMode::List);
批量矢量操作 var p = new FileOpenPicker{
    FileTypeFilter = { ".png", ".jpg", ".gif" }
};
FileOpenPicker p;
p.FileTypeFilter().ReplaceAll({ L".png", L".jpg", L".gif" });
移植 CopyButton_Click 方法
循环访问集合 foreach (var v in c) for (auto&& v : c) 移植 BuildClipboardFormatsOutputString 方法
捕获异常 catch (Exception ex) catch (winrt::hresult_error const& ex) 移植 PasteButton_Click 方法
异常详细信息 ex.Message ex.message() 移植 PasteButton_Click 方法
获取属性值 myObject.MyProperty myObject.MyProperty() 移植 NotifyUser 方法
设置属性值 myObject.MyProperty = value; myObject.MyProperty(value);
递增属性值 myObject.MyProperty += v; myObject.MyProperty(thing.Property() + v);
对于字符串,请切换到生成器
ToString() myObject.ToString() winrt::to_hstring(myObject) ToString()
语言字符串到 Windows 运行时字符串 N/A winrt::hstring{ s }
字符串生成 StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
字符串生成
字符串内插 $"{i++}) {s.Title}" winrt::to_hstring 和/或 winrt::hstring::operator+ 移植 OnNavigatedTo 方法
用于比较的空字符串 System.String.Empty winrt::hstring::empty 移植 UpdateStatus 方法
创建空字符串 var myEmptyString = String.Empty; winrt::hstring myEmptyString{ L"" };
字典操作 map[k] = v; // replaces any existing
v = map[k]; // throws if not present
map.ContainsKey(k)
map.Insert(k, v); // replaces any existing
v = map.Lookup(k); // throws if not present
map.HasKey(k)
类型转换(失败时引发) (MyType)v v.as<MyType>() 移植 Footer_Click 方法
类型转换(失败时为 null) v as MyType v.try_as<MyType>() 移植 PasteButton_Click 方法
具有 x:Name 的 XAML 元素是属性 MyNamedElement MyNamedElement() 移植构造函数 CurrentFEATURE_NAME
切换到 UI 线程 CoreDispatcher.RunAsync CoreDispatcher.RunAsyncwinrt::resume_foreground 移植 NotifyUser 方法以及移植 HistoryAndRoaming 方法
XAML 页的强制性代码中的 UI 元素构造 请参阅 UI 元素构造 请参阅 UI 元素构造

以下部分更详细地介绍了该表中的某些项。

UI 元素构造

这些代码示例演示了 XAML 页面的强制性代码中的 UI 元素构造。

var myTextBlock = new TextBlock()
{
    Text = "Text",
    Style = (Windows.UI.Xaml.Style)this.Resources["MyTextBlockStyle"]
};
TextBlock myTextBlock;
myTextBlock.Text(L"Text");
myTextBlock.Style(
    winrt::unbox_value<Windows::UI::Xaml::Style>(
        Resources().Lookup(
            winrt::box_value(L"MyTextBlockStyle")
        )
    )
);

ToString()

C# 类型提供 Object.ToString 方法。

int i = 2;
var s = i.ToString(); // s is a System.String with value "2".

C++/ WinRT 不直接提供此工具,不过可以转为使用替代方法。

int i{ 2 };
auto s{ std::to_wstring(i) }; // s is a std::wstring with value L"2".

C++/WinRT 也支持 winrt::to_hstring,但仅限数目有限的一些类型。 对于任何其他需要字符串化的类型,你需要添加重载。

Language 将整数字符串化 将枚举字符串化
C# string result = "hello, " + intValue.ToString();
string result = $"hello, {intValue}";
string result = "status: " + status.ToString();
string result = $"status: {status}";
C++/WinRT hstring result = L"hello, " + to_hstring(intValue); // must define overload (see below)
hstring result = L"status: " + to_hstring(status);

如果将枚举字符串化,则需提供 winrt::to_hstring 的实现。

namespace winrt
{
    hstring to_hstring(StatusEnum status)
    {
        switch (status)
        {
        case StatusEnum::Success: return L"Success";
        case StatusEnum::AccessDenied: return L"AccessDenied";
        case StatusEnum::DisabledByPolicy: return L"DisabledByPolicy";
        default: return to_hstring(static_cast<int>(status));
        }
    }
}

这些字符串化通常通过数据绑定来隐式使用。

<TextBlock>
You have <Run Text="{Binding FlowerCount}"/> flowers.
</TextBlock>
<TextBlock>
Most recent status is <Run Text="{x:Bind LatestOperation.Status}"/>.
</TextBlock>

这些绑定会对被绑定属性执行 winrt::to_hstring。 至于第二个示例 (StatusEnum),则必须提供你自己的 winrt::to_hstring 重载,否则会出现编译器错误。

另请参阅移植 Footer_Click 方法

字符串生成

C# 有一个内置的 StringBuilder 类型,用于字符串生成。

类别 C# C++/WinRT
字符串生成 StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
追加 Windows 运行时字符串(保留 null 值) builder.Append(s); builder << std::wstring_view{ s };
添加新行 builder.Append(Environment.NewLine); builder << std::endl;
访问结果 s = builder.ToString(); ws = builder.str();

另请参阅移植 BuildClipboardFormatsOutputString 方法移植 DisplayChangedFormats 方法

在主 UI 线程上运行代码

此示例取自条形码扫描仪示例

要在 C# 项目的主 UI 线程上工作时,通常使用 CoreDispatcher.RunAsync 方法,如下所示。

private async void Watcher_Added(DeviceWatcher sender, DeviceInformation args)
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        // Do work on the main UI thread here.
    });
}

更简单的是用 C++/WinRT 来表示。 请注意,假设我们希望在第一个暂停点(在本例中是 co_await)之后访问参数时,我们将按值接受参数。 有关详细信息,请参阅参数传递

winrt::fire_and_forget Watcher_Added(DeviceWatcher sender, winrt::DeviceInformation args)
{
    co_await Dispatcher();
    // Do work on the main UI thread here.
}

如果需要以默认优先级以外的优先级执行工作,请参阅 winrt::resume_foreground 函数,该函数包含需要优先处理的重载。 有关演示如何等待调用 winrt::resume_foreground 的代码示例,请参阅编程时仔细考虑线程相关性

在 IDL 中定义运行时类

请参阅 MainPage 类型的 IDL合并 .idl 文件

包含所需的 C++/WinRT Windows 命名空间头文件

在 C++/WinRT 中,只要你希望使用来自 Windows 命名空间的类型,都需要包含对应的 C++/WinRT Windows 命名空间头文件。 有关示例,请参阅移植 NotifyUser 方法

装箱和取消装箱

C# 自动将标量装箱到对象中。 C++/WinRT 要求你显式调用 winrt::box_value 函数。 两种语言都要求你以显式方式取消装箱。 请参阅使用 C++/WinRT 装箱和取消装箱

在下面的表中,我们将使用这些定义。

C# C++/WinRT
int i; int i;
string s; winrt::hstring s;
object o; IInspectable o;
操作 C# C++/WinRT
装箱 o = 1;
o = "string";
o = box_value(1);
o = box_value(L"string");
取消装箱 i = (int)o;
s = (string)o;
i = unbox_value<int>(o);
s = unbox_value<winrt::hstring>(o);

如果尝试取消值类型的 null 指针的装箱,C++/CX 和 C# 会引发异常。 C++/WinRT 将其视为编程错误,因此会崩溃。 在 C++/WinRT 中,请使用 winrt::unbox_value_or 函数来处理对象类型不符合预期的情况。

方案 C# C++/WinRT
取消已知整数的装箱 i = (int)o; i = unbox_value<int>(o);
如果 o 为 null System.NullReferenceException 崩溃
如果 o 不是装箱的整数 System.InvalidCastException 崩溃
取消整数的装箱,在为 null 的情况下使用回退;任何其他情况则崩溃 i = o != null ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
尽可能取消整数的装箱;在任何其他情况下使用回退 i = as int? ?? fallback; i = unbox_value_or<int>(o, fallback);

有关示例,请参阅移植 OnNavigatedTo 方法移植 Footer_Click 方法

将字符串装箱和取消装箱

字符串在某些情况下是值类型,在另一些情况下是引用类型。 C# 和 C++/WinRT 对待字符串的方式有所不同。

ABI 类型 HSTRING 是一个指向引用计数字符串的指针。 但是,它并非派生自 IInspectable,因此从技术上来说它不是一个对象。 另外,null HSTRING 表示空字符串。 将并非派生自 IInspectable 的项装箱时,需将其包装到 IReference<T> 中,而 Windows 运行时会以 PropertyValue 对象的形式提供标准实现(自定义类型以 PropertyType::OtherType 形式报告)。

C# 将 Windows 运行时字符串表示为引用类型,而 C++/WinRT 则将字符串投影为值类型。 这意味着装箱的 null 字符串可能有不同的表示形式,具体取决于你所采用的方法。

行为 C# C++/WinRT
声明 object o;
string s;
IInspectable o;
hstring s;
字符串类型类别 引用类型 值类型
null HSTRING 投影方式 "" hstring{}
null 和 "" 是否相同?
null 的有效性 s = null;
s.Length 引发 NullReferenceException
s = hstring{};
s.size() == 0(有效)
如果将 null 字符串分配给对象 o = (string)null;
o == null
o = box_value(hstring{});
o != nullptr
如果将 "" 分配给对象 o = "";
o != null
o = box_value(hstring{L""});
o != nullptr

基本装箱和取消装箱。

操作 C# C++/WinRT
将字符串装箱 o = s;
空字符串变为非 null 对象。
o = box_value(s);
空字符串变为非 null 对象。
取消已知字符串的装箱 s = (string)o;
Null 对象变为 null 字符串。
如果不是字符串,则引发 InvalidCastException。
s = unbox_value<hstring>(o);
Null 对象崩溃。
如果不是字符串,则崩溃。
将可能的字符串取消装箱 s = o as string;
Null 对象或非字符串变为 null 字符串。

或者

s = o as string ?? fallback;
Null 或非字符串变为 fallback。
空字符串被保留。
s = unbox_value_or<hstring>(o, fallback);
Null 或非字符串变为 fallback。
空字符串被保留。

使类可供 {Binding} 标记扩展使用

若要使用 {Binding} 标记扩展将数据绑定到数据类型,则请参阅使用 {Binding} 绑定声明的对象

使用 XAML 标记中的对象

在 C# 项目中,可以使用 XAML 标记中的专用成员和命名元素。 但在 C++/WinRT 中,以 XAML {x:Bind} 标记扩展形式使用的所有实体必须在 IDL 中以公开方式公开。

另外,绑定到布尔值时,在 C# 中会显示 truefalse,但在 C++/WinRT 中会显示 Windows.Foundation.IReference`1<布尔值>

有关详细信息和代码示例,请参阅使用标记中的对象

使数据源可供 XAML 标记使用

在 C++/WinRT 2.0.190530.8 或更高版本中,winrt::single_threaded_observable_vector 创建一个可观测的支持 IObservableVector<T>IObservableVector<IInspectable> 的矢量。 有关示例,请参阅 移植 Scenarios 属性

你可以创作 Midl 文件 (.idl) ,如下所示(另请参阅将运行时类重构到 Midl 文件 (.idl) 中)。

namespace Bookstore
{
    runtimeclass BookSku { ... }

    runtimeclass BookstoreViewModel
    {
        Windows.Foundation.Collections.IObservableVector<BookSku> BookSkus{ get; };
    }

    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        MainPage();
        BookstoreViewModel MainViewModel{ get; };
    }
}

实现方式如下。

// BookstoreViewModel.h
...
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
{
    BookstoreViewModel()
    {
        m_bookSkus = winrt::single_threaded_observable_vector<Bookstore::BookSku>();
        m_bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"To Kill A Mockingbird"));
    }
    
	Windows::Foundation::Collections::IObservableVector<Bookstore::BookSku> BookSkus();
    {
        return m_bookSkus;
    }

private:
    Windows::Foundation::Collections::IObservableVector<Bookstore::BookSku> m_bookSkus;
};
...

有关详细信息,请参阅 XAML 项目控件;绑定到 C++/WinRT 集合使用 C++/WinRT 的集合

使数据源可供 XAML 标记使用(在 C++/WinRT 2.0.190530.8 之前)

XAML 数据绑定要求项源实现 IIterable<IInspectable> 以及下述接口组合之一。

  • IObservableVector<IInspectable>
  • IBindableVectorINotifyCollectionChanged
  • IBindableVectorIBindableObservableVector
  • IBindableVector 本身(不会响应更改)
  • IVector<IInspectable>
  • IBindableIterable(会通过迭代将元素保存到专用集合中)

无法在运行时检测 IVector<T> 之类的泛型接口。 每个 IVector<T> 都有不同的接口标识符 (IID),该标识符是 T 的函数。任何开发人员都可以随意扩展 T 集,因此,很明显 XAML 绑定代码不可能知道要查询的完整集。 该限制对 C# 来说不是问题,因为每个实现 IEnumerable<T> 的 CLR 对象都会自动实现 IEnumerable。 在 ABI 级别,这意味着每个实现 IObservableVector<T> 的对象都会自动实现 IObservableVector<IInspectable>

C++/WinRT 不提供该保证。 如果 C++/WinRT 运行时类实现 IObservableVector<T>,则我们不能假定也会通过某种方式提供 IObservableVector<IInspectable> 的实现。

因此,上一示例应如下所示。

...
runtimeclass BookstoreViewModel
{
    // This is really an observable vector of BookSku.
    Windows.Foundation.Collections.IObservableVector<Object> BookSkus{ get; };
}

实现如下。

// BookstoreViewModel.h
...
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
{
    BookstoreViewModel()
    {
        m_bookSkus = winrt::single_threaded_observable_vector<Windows::Foundation::IInspectable>();
        m_bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"To Kill A Mockingbird"));
    }
    
    // This is really an observable vector of BookSku.
	Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> BookSkus();
    {
        return m_bookSkus;
    }

private:
    Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> m_bookSkus;
};
...

如需访问 m_bookSkus 中的对象,则需将其 QI 回 Bookstore::BookSku

Widget MyPage::BookstoreViewModel(winrt::hstring title)
{
    for (auto&& obj : m_bookSkus)
    {
        auto bookSku = obj.as<Bookstore::BookSku>();
        if (bookSku.Title() == title) return bookSku;
    }
    return nullptr;
}

派生类

若要从运行时类派生,基类必须是可组合类。 C# 不需要你执行任何特殊步骤即可将类变为可组合类,但 C++/WinRT 需要。 请使用 unsealed 关键字来指示你希望将类用作基类。

unsealed runtimeclass BasePage : Windows.UI.Xaml.Controls.Page
{
    ...
}
runtimeclass DerivedPage : BasePage
{
    ...
}

实现类型的头文件中,必须先包含基类头文件,然后再包含为派生类自动生成的头文件。 否则会出现“将此类型用作表达式非法”之类的错误。

// DerivedPage.h
#include "BasePage.h"       // This comes first.
#include "DerivedPage.g.h"  // Otherwise this header file will produce an error.

namespace winrt::MyNamespace::implementation
{
    struct DerivedPage : DerivedPageT<DerivedPage>
    {
        ...
    }
}

重要的 API