現在您已準備好專案結構,您可以使用 MVVM 工具組開始實作 MVVM 模式。 此步驟牽涉到建立利用 MVVM 工具組功能的檢視模型,例如 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 時,請務必決定如何建構與 ViewModel 相關的模型類別。 在本教學課程中,模型類別 (Note 和 AllNotes) 負責資料表示、商業邏輯和更新資料儲存。 ViewModel 會處理可觀察的屬性、變更通知,以及 UI 互動的命令。
在更簡單的實作中,您可以針對模型類別使用純舊的 CLR 物件 (POCO),而不需要任何商務邏輯或資料存取方法。 在此情況下,ViewModel 會透過服務層處理所有資料作業。 不過,在本教學課程中,模型類別包含載入、儲存及刪除附註的方法,以提供更清楚的關注點區隔,並讓 ViewModel 專注於呈現邏輯。
移動註記模型
將類別移至 NoteWinUINotes.Bus 專案。 它仍然是簡單的模型類別,具有一些資料表示法和狀態管理的邏輯,但沒有任何 MVVM 工具組功能。 ViewModel 會處理可觀察的屬性和變更通知,而不是模型本身。
在 WinUINotes.Bus 專案中,建立名為 Models 的新資料夾。
將檔案從
Note.cs專案移至 WinUINotes.Bus/Models 資料夾。更新命名空間以符合新位置:
namespace WinUINotes.Models { public class Note { // Existing code remains unchanged ... } }
該 Note 類別是一個簡單的資料模型。 它不需要變更通知,因為 ViewModel 會管理可觀察的屬性,並通知使用者介面變更。
移動 AllNotes 模型
將類別移至 AllNotesWinUINotes.Bus 專案。
將檔案從
AllNotes.cs專案移至 WinUINotes.Bus/Models 資料夾。更新命名空間以符合新位置:
namespace WinUINotes.Models { public class AllNotes { // Existing code remains unchanged ... } }
與類別一樣 Note , AllNotes 是一個簡單的模型類別。 ViewModel 會處理可觀察的行為,並管理筆記的集合。
建立 AllNotesViewModel
在 WinUINotes.Bus 專案中,建立名為 ViewModel 的新資料夾。
新增一個名為以下內容的新類別檔案
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 會自動更新。 -
allNotesmodel:這個私有欄位保存模型的AllNotes一個實例,用於處理實際的資料操作。 -
[RelayCommand]:此屬性會從LoadAsync()方法產生LoadCommand屬性,允許UI透過資料繫結觸發載入作業。 -
LoadAsync()method:這個方法會從模型載入註解,清除目前可觀察的集合,並填入載入的註釋。 此模式可確保 UI 繫結集合與基礎資料保持同步。
模型 (資料作業) 與allNotes可觀察集合 (UI 繫結) 之間的Notes分離是重要的 MVVM 模式,可將關注點分開,並將檢視與 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()的屬性。 只有在兩者都有TextFilename值時,才會啟用此命令。 -
InitializeForExistingNote():這個方法會將現有筆記的資料載入 ViewModel 屬性,然後透過資料繫結更新 UI。 -
儲存邏輯:此
Save()方法會使用目前的屬性值更新基礎Note模型,並呼叫SaveAsync()模型。 儲存之後,它會通知DeleteCommand重新評估(因為檔案現在存在且可以刪除)。 -
刪除邏輯:該方法在筆記模型上呼叫
Delete()DeleteAsync(),並建立一個新的空筆記。
在本教學課程稍後,您會整合檔案服務來處理實際的檔案作業,並使用 MVVM 工具組的 WeakReferenceMessenger 類別在刪除附註時通知應用程式的其他部分,同時保持鬆散耦合。
在檔中深入瞭解:
更新視圖以使用 ViewModel
現在,您必須更新 XAML 頁面,以系結至新的 ViewModel。
更新 AllNotesPage 視圖
在
AllNotesPage.xaml中,將ItemsView的ItemsSource繫結更新為使用 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中,更新TextBox和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> ...您也可以設定
UpdateSourceTriggerTextBox.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); } } } }已移除
Save和Delete的Click事件,因為按鈕現在會直接繫結至 ViewModel 中的命令。 在OnNavigatedTo()方法中實例化NoteViewModel。 如果傳遞了Note參數,則會使用現有的備註資料初始化 ViewModel。
在檔中深入瞭解: