现已准备好项目结构,可以使用 MVVM 工具包开始实现 MVVM 模式。 此步骤涉及创建利用 MVVM 工具包功能的 ViewModel,例如 ObservableObject 属性更改通知和 RelayCommand 命令实现。
安装 MVVM 工具包 NuGet 包
需要在 WinUINotes 和 WinUINotes.Bus 项目中安装 MVVM 工具包。
使用 Visual Studio
- 右键单击解决方案资源管理器中的 WinUINotes.Bus 项目。
- 选择“管理 NuGet 包”。
- 搜索 CommunityToolkit.Mvvm 并安装最新的稳定版本。
- 对 WinUINotes 项目重复这些步骤。
使用 .NET CLI
或者,你可以使用 .NET CLI 来安装该包:
dotnet add WinUINotes.Bus package CommunityToolkit.Mvvm
dotnet add WinUINotes package CommunityToolkit.Mvvm
模型层的设计决策
实现 MVVM 时,请务必决定如何构建模型类与 ViewModels 相关的结构。 在本教程中,模型类(Note 和 AllNotes)负责数据表示、业务逻辑和更新数据存储。 ViewModels 处理可观察属性、更改通知和用于 UI 交互的命令。
在更简单的实现中,可以将普通旧 CLR 对象(POCO)用于模型类,而无需任何业务逻辑或数据访问方法。 在这种情况下,ViewModels 通过服务层处理所有数据操作。 但是,在本教程中,模型类包括用于加载、保存和删除笔记的方法,以提供更清晰的关注点分离,并使 ViewModel 专注于呈现逻辑。
移动Note模型
将 Note 类移动到 WinUINotes.Bus 项目。 它仍然是一个简单的模型类,其中包含一些用于数据表示和状态管理的逻辑,但没有任何 MVVM 工具包功能。 ViewModel 处理可观察属性和更改通知,而不是模型本身。
在 WinUINotes.Bus 项目中,创建名为 Models 的新文件夹。
将
Note.cs文件从 WinUINotes 项目移动到 WinUINotes.Bus/Models 文件夹。更新命名空间以匹配新位置:
namespace WinUINotes.Models { public class Note { // Existing code remains unchanged ... } }
该 Note 类是一个简单的数据模型。 它不需要更改通知,因为 ViewModels 管理可观察属性并通知 UI 更改。
移动 AllNotes 模型
将 AllNotes 类移动到 WinUINotes.Bus 项目。
将
AllNotes.cs文件从 WinUINotes 项目移动到 WinUINotes.Bus/Models 文件夹。更新命名空间以匹配新位置:
namespace WinUINotes.Models { public class AllNotes { // Existing code remains unchanged ... } }
与类 Note 一样, AllNotes 是一个简单的模型类。 ViewModel 处理可观察的行为并管理笔记集合。
创建 AllNotesViewModel
在 WinUINotes.Bus 项目中,创建名为 ViewModels 的新文件夹。
添加包含以下内容的新
AllNotesViewModel.cs类文件:using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using System.Collections.ObjectModel; using System.Threading.Tasks; using WinUINotes.Models; namespace WinUINotes.ViewModels { public partial class AllNotesViewModel : ObservableObject { private readonly AllNotes allNotes; [ObservableProperty] private ObservableCollection<Note> notes; public AllNotesViewModel() { allNotes = new AllNotes(); notes = new ObservableCollection<Note>(); } [RelayCommand] public async Task LoadAsync() { await allNotes.LoadNotes(); Notes.Clear(); foreach (var note in allNotes.Notes) { Notes.Add(note); } } } }
AllNotesViewModel 管理 UI 中显示的笔记集合。
-
[ObservableProperty]:该notes字段自动生成具有更改通知的公共Notes属性。 集合Notes发生更改时,UI 会自动更新。 -
allNotes模型:此专用字段包含模型实例AllNotes,用于处理实际数据作。 -
[RelayCommand]:此属性从LoadCommand方法生成属性LoadAsync(),允许 UI 通过数据绑定触发加载作。 -
LoadAsync()方法:此方法从模型加载笔记,清除当前可观测集合,并使用加载的笔记填充它。 此模式可确保 UI 绑定集合与基础数据保持同步。
模型(数据操作)与Notes可观测集合(UI 绑定)之间的allNotes分离是一个关键的 MVVM 模式,它保持关注点分离,并使 View 与 ViewModel 的数据保持同步。
在文档中了解详细信息:
创建 NoteViewModel
在 ViewModels 文件夹中,添加一个新的类文件,命名为
NoteViewModel.cs:using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using System; using System.Threading.Tasks; using WinUINotes.Models; namespace WinUINotes.ViewModels { public partial class NoteViewModel : ObservableObject { private Note note; [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SaveCommand))] [NotifyCanExecuteChangedFor(nameof(DeleteCommand))] private string filename = string.Empty; [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SaveCommand))] private string text = string.Empty; [ObservableProperty] private DateTime date = DateTime.Now; public NoteViewModel() { this.note = new Note(); this.Filename = note.Filename; } public void InitializeForExistingNote(Note note) { this.note = note; this.Filename = note.Filename; this.Text = note.Text; this.Date = note.Date; } [RelayCommand(CanExecute = nameof(CanSave))] private async Task Save() { note.Filename = this.Filename; note.Text = this.Text; note.Date = this.Date; await note.SaveAsync(); // Check if the DeleteCommand can now execute // (it can if the file now exists) DeleteCommand.NotifyCanExecuteChanged(); } private bool CanSave() { return note is not null && !string.IsNullOrWhiteSpace(this.Text) && !string.IsNullOrWhiteSpace(this.Filename); } [RelayCommand(CanExecute = nameof(CanDelete))] private async Task Delete() { await note.DeleteAsync(); note = new Note(); } private bool CanDelete() { // Note: This is to illustrate how commands can be // enabled or disabled. // In a real application, you shouldn't perform // file operations in your CanExecute logic. return note is not null && !string.IsNullOrWhiteSpace(this.Filename) && this.note.NoteFileExists(); } } }
NoteViewModel 演示了几个 MVVM 工具包的关键功能:
-
[ObservableProperty]:filename、text和date字段自动生成具有更改通知支持的公共属性(Filename、Text、Date)。 -
[NotifyCanExecuteChangedFor]:此属性可确保当Filename或Text发生变化时,关联的命令会重新评估它们是否可以执行。 例如,键入文本时,“保存”按钮将根据验证逻辑自动启用或禁用。 -
[RelayCommand(CanExecute = nameof(CanSave))]:此属性生成一个SaveCommand属性,该属性绑定到验证方法CanSave()。 仅当两者Text都具有Filename值时,才启用该命令。 -
InitializeForExistingNote():此方法将现有笔记的数据加载到 ViewModel 属性中,然后通过数据绑定更新 UI。 -
保存逻辑:该方法将当前属性值更新到基础
Note模型,并在模型上调用SaveAsync()方法。 保存后,它会通知DeleteCommand它应重新评估(因为文件现在存在,可以删除)。 -
删除逻辑:该方法
Delete()对笔记模型调用DeleteAsync()并创建新的空笔记。
在本教程稍后的部分,您将集成文件服务以处理实际文件操作,并使用 MVVM 工具包的 WeakReferenceMessenger 类在删除笔记时通知应用的其他部分,同时保持低耦合。
在文档中了解详细信息:
更新视图以使用视图模型
现在,需要更新 XAML 页面以绑定到新的 ViewModel。
更新 AllNotesPage 视图
在
AllNotesPage.xamlItemsSource中,更新ItemsView的绑定,以使用 ViewModel 的Notes属性:<ItemsView ItemsSource="{x:Bind viewModel.Notes}" ...更新
AllNotesPage.xaml.cs文件,如下所示:using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Navigation; using WinUINotes.ViewModels; namespace WinUINotes.Views { public sealed partial class AllNotesPage : Page { private AllNotesViewModel? viewModel; public AllNotesPage() { this.InitializeComponent(); viewModel = new AllNotesViewModel(); } private void NewNoteButton_Click(object sender, RoutedEventArgs e) { Frame.Navigate(typeof(NotePage)); } private void ItemsView_ItemInvoked(ItemsView sender, ItemsViewItemInvokedEventArgs args) { Frame.Navigate(typeof(NotePage), args.InvokedItem); } protected override async void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); if (viewModel is not null) { await viewModel.LoadAsync(); } } } }
在此代码隐藏文件中,构造函数直接实例化 AllNotesViewModel 。 该方法 OnNavigatedTo() 在导航到该页面时在 ViewModel 上调用 LoadAsync() 该方法。 此方法从存储加载笔记并更新可观察集合。 此模式可确保用户在导航到所有备注页时始终刷新数据。
本教程稍后将重构此代码以使用依赖项注入,从而允许将 ViewModel 注入到页面构造函数中,而不是直接创建。 此方法可提高可测试性,并更轻松地管理 ViewModel 生命周期。
更新 NotePage 视图
在
NotePage.xaml中,更新Text和Header的绑定为使用 ViewModel 的属性。 请将StackPanel按钮更新为绑定到命令,而不是使用Click事件:... <TextBox x:Name="NoteEditor" Text="{x:Bind noteVm.Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AcceptsReturn="True" TextWrapping="Wrap" PlaceholderText="Enter your note" Header="{x:Bind noteVm.Date.ToString()}" ScrollViewer.VerticalScrollBarVisibility="Auto" MaxWidth="400" Grid.Column="1"/> <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="4" Grid.Row="1" Grid.Column="1"> <Button Content="Save" Command="{x:Bind noteVm.SaveCommand}"/> <Button Content="Delete" Command="{x:Bind noteVm.DeleteCommand}"/> </StackPanel> ...还可以在
UpdateSourceTrigger绑定上设置TextBox.Text,以确保更改随着用户输入被发送到 ViewModel。 此设置允许Save该按钮根据输入实时启用或禁用。在
NotePage.xaml.cs中,更新代码以使用NoteViewModel:using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Navigation; using WinUINotes.Models; using WinUINotes.ViewModels; namespace WinUINotes.Views { public sealed partial class NotePage : Page { private NoteViewModel? noteVm; public NotePage() { this.InitializeComponent(); } protected override void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); noteVm = new NoteViewModel(); if (e.Parameter is Note note && noteVm is not null) { noteVm.InitializeForExistingNote(note); } } } }Click和Save的事件已被移除,因为这些按钮现在直接绑定到 ViewModel 中的命令。NoteViewModel在方法OnNavigatedTo()中被实例化。 如果传递参数Note,它将使用现有笔记数据初始化 ViewModel。
在文档中了解详细信息: