第 3 部分:使用 XAML Islands 添加 UWP CalendarView 控件
这是教程的第 3 部分,此教程演示如何使名为 Contoso Expenses 的示例 WPF 桌面应用实现现代化。 有关下载示例应用的教程、先决条件和说明的概述,请参阅教程:实现 WPF 应用现代化。 本文假设你已完成第 2 部分。
在本教程的虚构应用场景中,Contoso 开发团队想要更轻松地在支持触摸的设备上选择支出报表的日期。 在此教程的本部分,你将向应用添加 UWP CalendarView 控件。 此控件与任务栏上 Windows 日期和时间功能中使用的控件相同。
与在第 2 部分中添加的 InkCanvas 控件不同,Windows 社区工具包不提供可在 WPF 应用中使用的 UWP CalendarView 的包装版本 。 作为替代方法,你将在通用 WindowsXamlHost 控件中托管 InkCanvas 。 你可以使用此控件来托管由 Windows SDK 或 WinUI 库提供的任何第一方 UWP 控件,或由第三方创建的任何自定义 UWP 控件。 WindowsXamlHost 控件由 Microsoft.Toolkit.Wpf.UI.XamlHost
包(包含于 NuGet 包中)提供 。 此包包含于在第 2 部分安装的 Microsoft.Toolkit.Wpf.UI.Controls
NuGet 包中。
注意
本教程仅演示如何使用 WindowsXamlHost 来托管由 Windows SDK 提供的第一方 CalendarView 控件 。 若要详细演示如何托管自定义控件,请参阅使用 XAML 岛在 WPF 应用中托管自定义 UWP 控件。
为使用 WindowsXamlHost 控件,需要从 WPF 应用中的代码直接调用 WinRT API 。 Microsoft.Windows.SDK.Contracts
NuGet 包包含了必需的引用,该引用使你能够从应用调用 WinRT API。 此包也包含于在第 2 部分安装的 Microsoft.Toolkit.Wpf.UI.Controls
NuGet 包中。
添加 WindowsXamlHost 控件
在解决方案资源管理器中,展开 ContosoExpenses.Core 项目中的 Views 文件夹,然后双击 AddNewExpense.xaml 文件 。 这是用于向列表中添加新支出的窗体。 下面是它在当前版本应用中的显示方式。
WPF 中包含的日期选取器控件适用于带有鼠标和键盘的传统计算机。 由于控件尺寸较小且日历中每一天之间的空间有限,因此使用触摸屏选择日期实际上是不可行的。
在 AddNewExpense.xaml 文件的顶部,将以下属性添加到 Window 元素 。
xmlns:xamlhost="clr-namespace:Microsoft.Toolkit.Wpf.UI.XamlHost;assembly=Microsoft.Toolkit.Wpf.UI.XamlHost"
添加此属性后,Window 元素现在应如下所示 。
<Window x:Class="ContosoExpenses.Views.AddNewExpense" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:xamlhost="clr-namespace:Microsoft.Toolkit.Wpf.UI.XamlHost;assembly=Microsoft.Toolkit.Wpf.UI.XamlHost" DataContext="{Binding Source={StaticResource ViewModelLocator},Path=AddNewExpenseViewModel}" xmlns:local="clr-namespace:ContosoExpenses" mc:Ignorable="d" Title="Add new expense" Height="450" Width="800" Background="{StaticResource AddNewExpenseBackground}">
将 Window 元素的 Height 属性从 450 更改为 800 。 这是必需的,因为 UWP CalendarView 控件占用的空间比 WPF 日期选取器要多 。
<Window x:Class="ContosoExpenses.Views.AddNewExpense" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:xamlhost="clr-namespace:Microsoft.Toolkit.Wpf.UI.XamlHost;assembly=Microsoft.Toolkit.Wpf.UI.XamlHost" DataContext="{Binding Source={StaticResource ViewModelLocator},Path=AddNewExpenseViewModel}" xmlns:local="clr-namespace:ContosoExpenses" mc:Ignorable="d" Title="Add new expense" Height="800" Width="800" Background="{StaticResource AddNewExpenseBackground}">
在文件底部附近找到
DatePicker
元素,并将其替换为以下 XAML。<xamlhost:WindowsXamlHost InitialTypeName="Windows.UI.Xaml.Controls.CalendarView" Grid.Column="1" Grid.Row="6" Margin="5, 0, 0, 0" x:Name="CalendarUwp" />
此 XAML 将添加 WindowsXamlHost 控件 。 InitialTypeName 属性指示要托管的 UWP 控件的完整名称(在本例中为 Windows.UI.Xaml.Controls.CalendarView) 。
按 F5 在调试程序中生成并运行应用。 从列表中选择员工,然后按“添加新支出”按钮 。 确认以下页面托管新的 UWP CalendarView 控件 。
关闭应用。
与 WindowsXamlHost 控件进行交互
接下来,你将更新应用以处理选定日期,将其显示在屏幕上,然后填充要保存到数据库中的 Expense 对象 。
UWP CalendarView 包含与此应用场景相关的两个成员:
- SelectedDates 属性包含用户选定的日期 。
- 用户选择日期时,将引发 SelectedDatesChanged 事件 。
但是,WindowsXamlHost 控件是任何类型的 UWP 控件均通用的主机控件 。 因此,它不会公开名为 SelectedDates 的属性或名为 SelectedDatesChanged 的事件,因为它们特定于 CalendarView 控件 。 若要访问这些成员,必须编写将 WindowsXamlHost 强制转换为 CalendarView 类型的代码 。 实现此目的的最佳位置是响应 WindowsXamlHost 控件的 ChildChanged 事件,该事件在呈现托管控件时引发 。
在 AddNewExpense.xaml 文件中,为之前添加的 WindowsXamlHost 控件的 ChildChanged 事件添加事件处理程序 。 完成后,WindowsXamlHost 元素应如下所示 。
<xamlhost:WindowsXamlHost InitialTypeName="Windows.UI.Xaml.Controls.CalendarView" Grid.Column="1" Grid.Row="6" Margin="5, 0, 0, 0" x:Name="CalendarUwp" ChildChanged="CalendarUwp_ChildChanged" />
在同一文件中,找到主 Grid 的 Grid.RowDefinitions 元素 。 在子元素列表的末尾再添加一个 Height 等于 Auto 的 RowDefinition 元素 。 完成后,Grid.RowDefinitions 元素应如下所示(现在应有 9 个 RowDefinition 元素) 。
<Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions>
在文件末尾附近的 WindowsXamlHost 元素之后和 Button 元素之前添加以下 XAML 。
<TextBlock Text="Selected date:" FontSize="16" FontWeight="Bold" Grid.Row="7" Grid.Column="0" /> <TextBlock Text="{Binding Path=Date}" FontSize="16" Grid.Row="7" Grid.Column="1" />
找到文件末尾附近的 Button 元素,并将 Grid.Row 属性从 7 更改为 8 。 这会将按钮在网格中向下移动一行,因为添加了新行。
<Button Content="Save" Grid.Row="8" Grid.Column="0" Command="{Binding Path=SaveExpenseCommand}" Margin="5, 12, 0, 0" HorizontalAlignment="Left" Width="180" />
打开 AddNewExpense.xaml.cs 代码文件 。
将以下语句添加到文件顶部。
using Microsoft.Toolkit.Wpf.UI.XamlHost;
将以下事件处理程序添加到
AddNewExpense
类。 此代码可实现先前在 XAML 文件中声明的 WindowsXamlHost 控件的 ChildChanged 事件 。private void CalendarUwp_ChildChanged(object sender, System.EventArgs e) { WindowsXamlHost windowsXamlHost = (WindowsXamlHost)sender; Windows.UI.Xaml.Controls.CalendarView calendarView = (Windows.UI.Xaml.Controls.CalendarView)windowsXamlHost.Child; if (calendarView != null) { calendarView.SelectedDatesChanged += (obj, args) => { if (args.AddedDates.Count > 0) { Messenger.Default.Send<SelectedDateMessage>(new SelectedDateMessage(args.AddedDates[0].DateTime)); } }; } }
此代码使用 WindowsXamlHost 控件的 Child 属性访问 UWP CalendarView 控件 。 然后,代码将预订 SelectedDatesChanged 事件,该事件在用户从日历中选择日期时触发 。 该事件处理程序将选定日期传递到 ViewModel,在此 ViewModel 中创建新的 Expense 对象并将其保存到数据库 。 为执行此操作,该代码使用 MVVM 轻量 NuGet 包提供的消息传递基础结构。 该代码将名为 SelectedDateMessage 的消息发送到 ViewModel,后者将接收该消息,并使用选定值设置 Date 属性 。 在这种情况下,将为单一选择模式配置 CalendarView 控件,因此集合将仅包含一个元素 。
此时,该项目仍无法编译,因为它缺少 SelectedDateMessage 类的实现 。 以下步骤可实现此类。
在解决方案资源管理器中,右键单击 Messages 文件夹,然后选择“添加”->“类”。 将新类命名为 SelectedDateMessage,然后单击“添加” 。
将 SelectedDateMessage.cs 代码文件的内容替换为以下代码 。 此代码为
GalaSoft.MvvmLight.Messaging
命名空间(来自 MVVM 轻量 NuGet 包)添加 using 语句,从 MessageBase 类继承,并添加通过公共构造函数初始化的 DateTime 属性 。 完成后,该文件应如下所示。using GalaSoft.MvvmLight.Messaging; using System; namespace ContosoExpenses.Messages { public class SelectedDateMessage: MessageBase { public DateTime SelectedDate { get; set; } public SelectedDateMessage(DateTime selectedDate) { this.SelectedDate = selectedDate; } } }
接下来,你将更新 ViewModel 以接收此消息,并填充 ViewModel 的 Date 属性 。 在解决方案资源管理器中,展开 ViewModels 文件夹,然后打开 AddNewExpenseViewModel.cs 文件 。
找到
AddNewExpenseViewModel
类的公共构造函数,然后将以下代码添加到该构造函数的末尾。 此代码将登记接收 SelectedDateMessage,通过 SelectedDate 属性从其中提取所选日期,然后使用它来设置由 ViewModel 公开的 Date 属性 。 由于此属性与先前添加的 TextBlock 控件绑定,因此可以查看用户选择的日期 。Messenger.Default.Register<SelectedDateMessage>(this, message => { Date = message.SelectedDate; });
完成后,
AddNewExpenseViewModel
构造函数应如下所示。public AddNewExpenseViewModel(IDatabaseService databaseService, IStorageService storageService) { this.databaseService = databaseService; this.storageService = storageService; Date = DateTime.Today; Messenger.Default.Register<SelectedDateMessage>(this, message => { Date = message.SelectedDate; }); }
注意
同一代码文件中的
SaveExpenseCommand
方法执行将支出保存到数据库的工作。 此方法使用 Date 属性创建新的 Expense 对象 。 此属性现在由 CalendarView 控件通过刚创建的SelectedDateMessage
消息进行填充 。按 F5 在调试程序中生成并运行应用。 选择一个可用员工,然后单击“添加新支出”按钮 。 填写窗体中的所有字段,然后从新的 CalendarView 控件中选择一个日期 。 确认选定日期显示为控件下方的字符串。
按“保存”按钮 。 将关闭窗体,并将新的支出添加到支出列表的末尾。 带有支出日期的第一列应是你在 CalendarView 控件中选择的日期 。
后续步骤
本教程进行到这里,你已成功将 WPF 日期时间控件替换为 UWP CalendarView 控件,该控件除了支持鼠标和键盘输入以外,还支持触摸和数字笔 。 尽管 Windows 社区工具包不提供可直接在 WPF 应用中使用的 UWP CalendarView 控件的包装版本,但你可以使用通用 WindowsXamlHost 控件来托管该控件 。
现在你已准备好学习第 4 部分:添加 Windows 用户活动和通知。