本教學課程的這個部分介紹數據檢視和模型的概念。
在教學的前幾步,你為 project 新增了一個頁面,讓使用者可以儲存、編輯或刪除單一筆記。 不過,因為應用程式需要處理一個以上的筆記,所以您需要新增另一個頁面來顯示所有附註(呼叫它 AllNotesPage)。 此頁面允許使用者選擇在編輯器頁面開啟的筆記,以便查看、編輯或刪除。 它也應該讓使用者建立新的附註。
若要達成此目的, AllNotesPage 必須有附注的集合,以及顯示集合的方法。 這是應用程式發生問題的地方,因為筆記資料緊密系結至 NotePage 檔案。 在 AllNotesPage中,您只想在清單或其他集合檢視中顯示所有筆記,並包含每個筆記的相關資訊,例如創建日期和文字預覽。 由於筆記文字被緊密地綁在TextBox控制項上,因此根本無法做到這一點。
在您新增頁面以顯示所有筆記之前,讓我們進行一些變更,以將記事數據與筆記呈現分開。
視圖和模型
通常 WinUI 3 應用程式至少有一個 檢視層 和 一個資料層。
檢視層會使用 XAML 標記來定義 UI。 標記包含數據系結運算式(例如 x:Bind),可定義特定 UI 元件與數據成員之間的連線。 程序代碼後置檔案有時會當做檢視層的一部分使用,以包含自定義或作 UI 所需的額外程式碼,或在呼叫對數據執行工作的方法之前,從事件處理程式自變數擷取數據。
數據層或 模型會定義代表應用程式數據和相關邏輯的類型。 此圖層與檢視層無關,您可以建立多個與數據互動的不同檢視。
目前, NotePage 代表數據檢視(附註文字)。 不過,數據從系統檔案讀入應用程式後,它只存在於 Text 的 TextBox 屬性中 NotePage。 它不會以可讓您以不同方式或不同位置呈現數據的方式在應用程式中表示;也就是說,應用程式沒有數據層。 你現在要重組專案,建立資料層。
分隔檢視和模型
小提示
你可以從
重構現有的程序代碼,以將模型與檢視區隔開。 接下來的幾個步驟會組織程序代碼,讓檢視和模型彼此分開定義。
在 Solution Explorer,右鍵點擊 WinUINotes project,選擇 Add>New Folder。 將資料夾命名為 Models。
再次右鍵點擊 WinUINotes project,選擇 Add>New Folder。 將資料夾命名為 Views。
找到項目 NotePage.xaml 並將其拖曳到資料夾中 Views 。 NotePage.xaml.cs檔案應該隨之移動。
備註
當你移動檔案時,Visual Studio 通常會提示你移動操作可能需要很長時間。 如果您看到此警告,這裡就不會有問題,請按 確定。
Visual Studio 也可能問你是否要調整移動檔案的命名空間。 選取 否。 您將在後續步驟中變更命名空間。
更新檢視命名空間
現在,檢視已移至 Views 資料夾,您必須更新命名空間以符合。 頁面 XAML 和程式碼後置檔案的命名空間會設定為 WinUINotes。 這必須更新為 WinUINotes.Views。
在 Solution Explorer 面板中,展開 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 映射到你project的根命名空間,而 Visual Studio project 範本已經幫你做到這點(xmlns:local="using:WinUINotes")。 現在頁面已移至新的命名空間,XAML 中的類型對應現在無效。
幸運的是,您可以視需要新增自己的命名空間對應。 你需要執行此操作,才能存取你在專案中建立的不同資料夾中的項目。 這個新的 XAML 命名空間會對應至 的 WinUINotes.Views命名空間,因此請將它命名為 views。 宣告看起來應該像下列屬性: xmlns:views="using:WinUINotes.Views"。
在 Solution Explorer 面板中,雙擊 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>建置並執行應用程式。 應用程式應該在沒有任何編譯程式錯誤的情況下執行,而且所有專案都應該如先前一樣運作。
定義模型
目前,模型(數據)內嵌在附注檢視中。 您將建立新的類別來代表記事頁面的資料:
在 Solution Explorer 面板中,按右鍵 Models 資料夾,選擇 新增>Class...。
為類別Note.cs命名,然後按 [新增]。 該 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陳述句,讓你的程式碼access到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會使用Mode的預設OneTime,因為noteModel.Date屬性在檔案建立後不會發生改變。 此程式代碼也展示了一個稱為x:Bind的強大功能,函式綁定,它允許您使用ToString函式作為綁定路徑中的一個步驟。
這很重要
請務必選擇正確的 BindingMode;否則,您的數據系結可能無法如預期般運作。 (常見的錯誤{x:Bind}是當需要BindingMode或OneWay時,忘記變更預設值TwoWay。)
| 名稱 | 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,請參閱下列資源: