本教程的这一部分介绍了数据视图和模型的概念。
在本教程的前面步骤中,你向项目添加了一个新页面,允许用户保存、编辑或删除单个笔记。 但是,由于应用需要处理多个笔记,因此需要添加另一个页面来显示所有备注(调用它 AllNotesPage)。 此页面让用户选择要在编辑器页中打开的备注,以便用户可以查看、编辑或删除它。 它还应该允许用户创建新笔记。
若要实现此目的, AllNotesPage 需要具有笔记集合和显示集合的方法。 这是应用遇到问题的地方,因为笔记数据紧密绑定到 NotePage 文件。 在 AllNotesPage中,只需显示列表或其他集合视图中的所有笔记,其中包含有关每个笔记的信息,如创建日期和文本预览。 由于笔记文本紧密绑定到 TextBox 控件,因此无法执行此作。
在添加页面以显示所有笔记之前,让我们进行一些更改,以便将笔记数据与备注演示文稿分开。
视图和模型
通常,WinUI 应用至少具有 视图层 和数据 层。
视图层使用 XAML 标记定义 UI。 标记包括定义特定 UI 组件和数据成员之间的连接的数据绑定表达式(如 x:Bind)。 代码隐藏文件有时用作视图层的一部分,以包含自定义或作 UI 所需的其他代码,或者在调用对数据执行工作的方法之前从事件处理程序参数中提取数据。
数据层或 模型定义表示应用数据和相关逻辑的类型。 此层独立于视图层,你可以创建多个与数据交互的不同视图。
目前,表示 NotePage 数据视图(注释文本)。 但是,从系统文件中将数据读入应用后,它仅存在于 in 的属性Text中TextBoxNotePage。 它不以允许以不同方式或不同位置呈现数据的方式在应用中表示;也就是说,应用没有数据层。 现在,你将重新构建项目以创建数据层。
分离视图和模型
小窍门
可以从 GitHub 存储库下载或查看本教程的代码。 若要查看此步骤中的代码,请参阅此提交: 备注页 - 视图模型。
重构现有代码以将模型与视图分开。 接下来的几个步骤将组织代码,以便分别定义视图和模型。
在 解决方案资源管理器中,右键单击 WinUINotes 项目并选择“ 添加新>文件夹”。 将该文件夹命名为 Models。
再次右键单击 WinUINotes 项目,然后选择“ 添加新>文件夹”。 将该文件夹命名为 Views。
找到该项目 NotePage.xaml 并将其拖动到 Views 文件夹。 该文件 NotePage.xaml.cs 应随该文件一起移动。
注释
移动文件时,Visual Studio 通常会发出有关移动操作可能需要很长时间的警告进行提示。 如果看到此警告,则表示没有任何问题,请按“确定”。
Visual Studio 还可能会询问是否要调整已移动文件的命名空间。 请选择“否”。 后续步骤中将更改命名空间。
更新视图命名空间
现在视图已移动到 Views 文件夹,需要更新命名空间才能匹配。 页面的 XAML 和代码隐藏文件的命名空间设置为 WinUINotes。 需要将其更新为 WinUINotes.Views。
在 “解决方案资源管理器” 窗格中,展开 NotePage.xaml 以显示代码隐藏文件。
双击 NotePage.xaml.cs 该项以打开代码编辑器(如果尚未打开)。 将命名空间更改为
WinUINotes.Views:namespace WinUINotes.Views双击 NotePage.xaml 该项以打开 XAML 编辑器(如果尚未打开)。 旧命名空间通过
x:Class特性引用,该特性定义哪个类类型是 XAML 的代码隐藏。 此条目不仅仅是命名空间,而是具有该类型的命名空间。 将x:Class值更改为WinUINotes.Views.NotePage:x:Class="WinUINotes.Views.NotePage"
修复 MainWindow 中的命名空间引用
在上一步中,你创建了笔记页并更新 MainWindow.xaml 了以导航到它。 请记住,它已使用 local: 命名空间映射进行映射。 常见的做法是将名称 local 映射到项目的根命名空间,Visual Studio 项目模板已为你执行此作(xmlns:local="using:WinUINotes")。 现在,页面已移动到新命名空间,XAML 中的类型映射现在无效。
幸运的是,可以根据需要添加自己的命名空间映射。 需要执行此作才能访问在项目中创建的不同文件夹中的项目。 这个新的 XAML 命名空间将映射到其命名空间 WinUINotes.Views,因此将其 views命名。 声明应类似于以下特性:xmlns:views="using:WinUINotes.Views"。
在 “解决方案资源管理器” 窗格中,双击 MainWindow.xaml 条目以在 XAML 编辑器中打开它。
在映射下方的行中添加此新命名空间映射
local:xmlns:views="using:WinUINotes.Views"localXAML 命名空间用于设置Frame.SourcePageType属性,因此请将其更改为views该属性。 XAML 现在应如下所示:<Window x:Class="WinUINotes.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:WinUINotes" xmlns:views="using:WinUINotes.Views" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Title="WinUI Notes"> <!-- ... Unchanged XAML not shown. --> <Frame x:Name="rootFrame" Grid.Row="1" SourcePageType="views:NotePage"/> <!-- ... Unchanged XAML not shown. --> </Window>生成并运行应用。 应用应运行而不出现任何编译器错误,并且所有内容都应像以前一样工作。
定义模型
目前,模型(数据)嵌入在便笺视图中。 你将创建一个新类来表示笔记页的数据:
在“解决方案资源管理器”窗格中,右键单击 Models 文件夹,然后选择“添加”>“类...”。
为类 Note.cs 命名,然后按 Add。 该文件 Note.cs 将在代码编辑器中打开。
将文件中的代码 Note.cs 替换为此代码,这会使类
public添加用于处理注释的属性和方法:using System; using System.Threading.Tasks; using Windows.Storage; namespace WinUINotes.Models { public class Note { private StorageFolder storageFolder = ApplicationData.Current.LocalFolder; public string Filename { get; set; } = string.Empty; public string Text { get; set; } = string.Empty; public DateTime Date { get; set; } = DateTime.Now; public Note() { Filename = "notes" + DateTime.Now.ToBinary().ToString() + ".txt"; } public async Task SaveAsync() { // Save the note to a file. StorageFile noteFile = (StorageFile)await storageFolder.TryGetItemAsync(Filename); if (noteFile is null) { noteFile = await storageFolder.CreateFileAsync(Filename, CreationCollisionOption.ReplaceExisting); } await FileIO.WriteTextAsync(noteFile, Text); } public async Task DeleteAsync() { // Delete the note from the file system. StorageFile noteFile = (StorageFile)await storageFolder.TryGetItemAsync(Filename); if (noteFile is not null) { await noteFile.DeleteAsync(); } } } }保存文件。
你会注意到,此代码与代码 NotePage.xaml.cs非常相似,并进行了一些更改和添加。
Filename 并 Text 已更改为 public 属性,并添加了一个新 Date 属性。
保存和删除文件的代码已放置在方法中 public 。 它与按钮事件处理程序Click中使用的NotePage代码大致相同,但在删除文件后更新视图的额外代码。 此处不需要它,因为你将使用数据绑定来保持模型和视图同步。
这些异步方法签名返回 Task 而不是 void。 该 Task 类表示不返回值的单个异步作。 除非方法签名要求 void,否则 Click 对于事件处理程序, async 方法应返回一个 Task。
你也不会再保留对保存笔记的 StorageFile 引用。 只需在需要该文件保存或删除时,即可获取该文件。
在文件中NotePage,你使用了文件名的占位符: note.txt 现在,应用支持多个笔记,保存笔记的文件名需要不同且唯一。 为此,请 Filename 设置构造函数中的属性。 可以使用 DateTime.ToBinary 方法基于当前时间创建文件名的一部分,并使文件名唯一。 生成的文件名如下所示: notes-8584626598945870392.txt
更新备注页
现在,可以更新 NotePage 视图以使用 Note 数据模型并删除已移动到 Note 模型的代码。
如果尚未在编辑器中打开,请打开 Views\NotePage.xaml.cs 文件。
在页面顶部的最后
using一条语句之后,添加一个新using语句,以授予代码对文件夹和命名空间中的Models类的访问权限。using WinUINotes.Models;从类中删除以下行:
private StorageFolder storageFolder = ApplicationData.Current.LocalFolder; private StorageFile? noteFile = null; private string fileName = "note.txt";而是在其位置添加一
Note个名为noteModel的对象。 这表示提供视图的NotePage注释数据。private Note? noteModel;也不再需要
NotePage_Loaded事件处理程序。 不会将文本直接从文本文件读取到 TextBox 中。 相反,注释文本将读入Note对象中。 在后面的步骤中添加代码时,将添加AllNotesPage此代码。 删除这些行。Loaded += NotePage_Loaded; ... private async void NotePage_Loaded(object sender, RoutedEventArgs e) { noteFile = (StorageFile)await storageFolder.TryGetItemAsync(fileName); if (noteFile is not null) { NoteEditor.Text = await FileIO.ReadTextAsync(noteFile); } }将
SaveButton_Click方法中的代码替换为以下代码:if (noteModel is not null) { await noteModel.SaveAsync(); }将
DeleteButton_Click方法中的代码替换为以下代码:if (noteModel is not null) { await noteModel.DeleteAsync(); }
现在可以更新 XAML 文件以使用 Note 模型。 以前,将文本直接从文本文件读取到 TextBox.Text 代码隐藏文件中的属性中。 现在,对属性使用数据绑定 Text 。
如果尚未在编辑器中打开,请打开 Views\NotePage.xaml 文件。
向
Text控件添加属性TextBox。 将其绑定到Text:noteModel的属性Text="{x:Bind noteModel.Text, Mode=TwoWay}"。Header更新要绑定到Date:noteModel的属性Header="{x:Bind noteModel.Date.ToString()}"。<TextBox x:Name="NoteEditor" <!-- ↓ Add this line. ↓ --> Text="{x:Bind noteModel.Text, Mode=TwoWay}" AcceptsReturn="True" TextWrapping="Wrap" PlaceholderText="Enter your note" <!-- ↓ Update this line. ↓ --> Header="{x:Bind noteModel.Date.ToString()}" ScrollViewer.VerticalScrollBarVisibility="Auto" Width="400" Grid.Column="1"/>
数据绑定是应用 UI 显示数据的一种方法,还可以选择与该数据保持同步。 绑定 Mode=TwoWay 上的设置意味着 TextBox.Text 自动同步和 noteModel.Text 属性。 当文本在更新时 TextBox,更改将 Text 反映在属性 noteModel中,如果 noteModel.Text 已更改,则更新将反映在其中 TextBox。
该Header属性使用默认值ModeOneTime,noteModel.Date因为创建文件后该属性不会更改。 此代码还演示了一项称为x:Bind的强大功能,使你可以使用类似于ToString绑定路径中的步骤的函数。
重要
选择正确的 BindingMode 非常重要;否则,数据绑定可能无法按预期工作。 (一个常见的错误{x:Bind}是忘记在何时BindingMode或OneWay需要更改默认值TwoWay。
| Name | Description |
|---|---|
OneTime |
仅在创建绑定时更新目标属性。 默认值为 {x:Bind}. |
OneWay |
在创建绑定时更新目标属性。 对源对象的更改也可以传播到目标。 默认值为 {Binding}. |
TwoWay |
在任一更改时更新目标对象或源对象。 创建绑定后,将从源更新目标属性。 |
数据绑定支持分离数据和 UI,这会导致更简单的概念模型,以及更好的可读性、可测试性和可维护性。
在 WinUI 中,有两种类型的绑定可供选择:
- 标记
{x:Bind}扩展在编译时处理。 其一些优点是改进绑定表达式的性能和编译时验证。 建议在 WinUI 应用中绑定。 - 标记
{Binding}扩展在运行时处理,并使用常规用途运行时对象检查。
在文档中了解详细信息:
数据绑定和 MVVM
Model-View-ViewModel (MVVM)是一种 UI 体系结构设计模式,用于分离 UI 和非 UI 代码,该代码深受 .NET 开发人员欢迎。 在了解有关创建 WinUI 应用的详细信息时,你可能会看到并听到它提到的内容。 按照此处所述,分离视图和模型是完成应用的完整 MVVM 实现的第一步,但就本教程而言,这是第一步。
注释
我们使用了术语“模型”来引用本教程中的数据模型,但请务必注意,此模型在完整的 MVVM 实现中与 ViewModel 更紧密地对齐,同时合并 模型的各个方面。
若要了解有关 MVVM 的详细信息,请参阅以下资源: