2022年8月2日 星期二

MAUI : 資料綁定 Data Binding - 5 在 Maui 專案內,如何得知 ViewModel 內的屬性產生異動,而 View 可以收到通知呢?

資料綁定 Data Binding - 5 在 Maui 專案內,如何得知 ViewModel 內的屬性產生異動,而 View 可以收到通知呢?

在最後對於資料綁定 Data Binding 的 INotifyPropertyChanged 的用法,將會回到 MAUI 專案內來實際觀察,在這篇文章中,將會使用 CommunityToolkit.Mvvm 這個套件來進行檢測

系列文章清單

1 自行建置 INotifyPropertyChanged 介面

2 設計基底類別,透過繼承可以使用屬性變更通知

3 PropertyChanged.Fody 套件,大幅簡化屬性變更通知程式設計碼

4 CommunityToolkit.Mvvm 套件,透過原始碼產生來簡化屬性變更通知程式設計碼

5 在 Maui 專案內,如何得知 ViewModel 內的屬性產生異動,而 View 可以收到通知呢?

建立新專案

  • 開啟 Visual Studio 2022 Preview 開發工具

    在這個時間點,若想要使用 MAUI 專案來進行開發,需要安裝 Visual Studio 2022 17.3 Preview 版本,才能夠順利建立 MAUI 專案

  • 當 [Visual Studio 2022 Preview] 對話窗出現的時候

  • 點選右下角的 [建立新的專案] 按鈕選項

  • 現在將看到 [建立新專案] 對話窗

  • 切換右上角的 [所有專案類型] 下拉選單控制項

  • 從清單中找到並選擇 [.NET MAUI 應用程式] 這個專案範本

    此專案可用於建立適用於 iOS、Android、Mac Catalyst、Tizen 和 WinUI 的 NET MAUI 應用程式

  • 點選右下角的 [下一步] 按鈕

  • 此時將會看到 [設定新的專案] 對話窗

  • 在 [專案名稱] 欄位,輸入 mauiMonitorPropertyChanged

  • 點選右下角的 [下一步] 按鈕

  • 最後會看到 [其他資訊] 對話窗

  • 使用預設設定值,也就是 [架構] 為 [.NET 6.0 (長期支援)]

  • 點選右下角的 [建立] 按鈕

加入 CommunityToolkit.Mvvm 的 NuGet 套件

  • 滑鼠右擊該專案的 [相依性] 節點
  • 從彈出功能表中選擇 [管理 NuGet 套件] 功能選項
  • 此時,[NuGet: csCommunityToolkitMVVM] 視窗將會出現
  • 點選 [瀏覽] 標籤頁次
  • 在左上方的搜尋文字輸入盒內輸入 CommunityToolkit.Mvvm 關鍵字
  • 若你沒有看到 8.0 以上的版本,請勾選 [包括搶鮮版] 檢查盒控制項
  • 現在,將會看到 CommunityToolkit.Mvvm 套件出現在清單內
  • 點選這個 CommunityToolkit.Mvvm 套件,並且點選右上方的 [安裝] 按鈕,安裝這個套件到這個專案內。

進行 ViewModel 的設計

  • 滑鼠右擊專案節點
  • 從彈出功能表中,點選 [加入] > [類別]
  • 當出現 [新增項目 - mauiMonitorPropertyChanged] 對話窗後
  • 在下方的 [名稱] 欄位內,輸入 MainPageViewModel
  • 點選對話窗右下角的 [新增] 按鈕
  • 使用底下 C# 程式碼將這個檔案內容替換掉
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace mauiMonitorPropertyChanged
{
    using CommunityToolkit.Mvvm.ComponentModel;
    using CommunityToolkit.Mvvm.Input;

    public partial class MainPageViewModel : ObservableObject
    {
        public MainPageViewModel()
        {
            person = new Person();
        }
        [ObservableProperty]
        private Person person;

        [RelayCommand]
        void ChangeData()
        {
            Person.Name = "Vulcan Lee";
            Person.Age = 25;
        }
    }
    public partial class Person : ObservableObject
    {
        [ObservableProperty]
        private string name;

        [ObservableProperty]
        private int age;

        private string customDesign;
        public string CustomDesign
        {
            get => name;
            set => SetProperty(ref customDesign, value);
        }
    }
}

在這裡設計一個 Person 類別,並且透過 CommunityToolkit.Mvvm 套件的原始碼自動產生機制,幫助產生出相關的 PropertyChanged 屬性變更事件需要用到的相關程式碼。

在最上面則是建立一個新的類別,將會是 MainPage 這個 XAML 頁面需要用到的 ViewModel,因此,在這裡將會把這個 ViewModel 類別命名為 MainPageViewModel。

在 MainPageViewModel 建構式內,首先將 Person 這個屬性建立初始物件值,避免之後產生 null reference 空值存取的問題。

這裡設計一個 void ChangeData() 方法,一旦執行這個方法之後,將會變更 Person 物件內的 Name 與 Age 屬性值,其實,這是一個用於命令要綁定的委派方法,在這裡,透過在此方法上方加入了 [RelayCommand] 屬性宣告,讓這個方法可以用於 XAML 頁面上來進行命令的綁定,因此,在 MainPage.xaml 檔案中,將會看到有個 ChangeDataCommand 這樣的物件可以選擇。

進行 View 與 ViewModel 的服務註冊

接下來要來進行使用 DI Container 相依性服務容器的註冊工作,要使用這樣的功能,將會在取得頁面物件的時候,可以自動注入相對應的 ViewModel 物件來使用。

  • 在專案根目錄找到並且打開 [MauiProgram.cs] 檔案
  • 在最後面找到 return builder.Build(); 敘述
  • 在此敘述前,加入底下程式碼
#region 在這裡進行 ViewModel 的註冊
builder.Services.AddSingleton<MainPage>();
builder.Services.AddSingleton<MainPageViewModel>();
#endregion

修正 View XAML 內容,加入 Entry 控制項與宣告與 ViewModel 的資料綁定設定

  • 在專案根目錄下,找到並且打開 [MainPage.xaml] 檔案
  • 使用底下 XAML 標記,替換這個檔案內的內容
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:mauiMonitorPropertyChanged"
             x:DataType="local:MainPageViewModel"
             x:Class="mauiMonitorPropertyChanged.MainPage">

    <ScrollView>
        <VerticalStackLayout
            Spacing="25"
            Padding="30,0"
            VerticalOptions="Center">

            <Entry Placeholder="請輸入姓名"
                   x:Name="entryName"
                   Text="{Binding Person.Name}"/>
            <Entry Placeholder="請輸入年紀"
                   x:Name="entryAge"
                   Text="{Binding Person.Age}"/>
            <Button Text="變更屬性"
                    Command="{Binding ChangeDataCommand}"/>
            
            <Image
                Source="dotnet_bot.png"
                SemanticProperties.Description="Cute dot net bot waving hi to you!"
                HeightRequest="200"
                HorizontalOptions="Center" />

            <Label
                Text="Hello, World!"
                SemanticProperties.HeadingLevel="Level1"
                FontSize="32"
                HorizontalOptions="Center" />

            <Label
                Text="Welcome to .NET Multi-platform App UI"
                SemanticProperties.HeadingLevel="Level2"
                SemanticProperties.Description="Welcome to dot net Multi platform App U I"
                FontSize="18"
                HorizontalOptions="Center" />

            <Button
                x:Name="CounterBtn"
                Text="Click me"
                SemanticProperties.Hint="Counts the number of times you click"
                Clicked="OnCounterClicked"
                HorizontalOptions="Center" />

        </VerticalStackLayout>
    </ScrollView>

</ContentPage>

在根目錄節點, ContentPage 內,宣告一個 local 命名空間,這次要用來指定該頁面 ViewModel 所在的命名空間位置,接下來的 x:DataType , 這是用於 編譯的系結 ,在微軟官方文件上,是這樣描述的 : 編譯的系結藉由在編譯時期解析系結運算式,而不是執行時間,以改善 .NET MAUI 應用程式中的資料系結效能。 此外,在編譯時間驗證繫結運算式可改善開發人員的疑難排解體驗,因為無效的繫結會回報為建置錯誤。

我非常熱愛這項功能,因為,可以讓我在設計 XAML 的時候,即時得知綁定的物件名稱是甚麼與是否正確,避免要等到執行時期才知道發生了問題。

接著,宣告了兩個 Entry 控制項與一個按鈕,前面兩個控制項用於顯示與讓使用者輸入 姓名 與 年紀 的內容,而後者的按鈕控制項,透過 Command 這個屬性值,來綁定到 ViewModel 內的這個具有實作 ICommand 的物件 (ChangeDataCommand 這個物件,是由 CommunityToolkit.Mvvm 透過原始碼產生器自動產生的,在 ViewModel 內的綁定方法為 ChangeData)

修正 Code Behind 程式碼,注入 ViewModel 與 綁定屬性變更通知

  • 在專案根目錄找到並且打開 [MainPage.xaml.cs] 檔案
  • 使用底下 C# 程式碼替換掉這個檔案內容
using System.Diagnostics;

namespace mauiMonitorPropertyChanged;

public partial class MainPage : ContentPage
{
    public MainPageViewModel MainPageViewModel { get; }
    int count = 0;

    public MainPage(MainPageViewModel mainPageViewModel)
    {
        InitializeComponent();
        MainPageViewModel = mainPageViewModel;
        this.BindingContext = MainPageViewModel;

        MainPageViewModel.Person.PropertyChanging += (s, e) =>
        {
            Debug.WriteLine($"   ViewModel.Person : {e.PropertyName} 正在進行變更完成");
        };
        MainPageViewModel.Person.PropertyChanged += (s, e) =>
        {
            Debug.WriteLine($"   ViewModel.Person :  {e.PropertyName} 已經變更完成");
        };
        entryName.PropertyChanged += (s, e) =>
        {
            Debug.WriteLine($"   姓名控制項 {e.PropertyName} 已經變更完成");
        };
        entryName.PropertyChanging += (s, e) =>
        {
            Debug.WriteLine($"   姓名控制項 {e.PropertyName} 正在進行變更完成");
        };
    }

    private void OnCounterClicked(object sender, EventArgs e)
    {
        count++;

        if (count == 1)
            CounterBtn.Text = $"Clicked {count} time";
        else
            CounterBtn.Text = $"Clicked {count} times";

        SemanticScreenReader.Announce(CounterBtn.Text);
    }
}

在這裡的 Code Behind 程式碼中,透過了建構式注入方式,取得該頁面要使用的 ViewModel 物件,也就是 MainPageViewModel。接著,將這個 ViewModel 物件設定到 XAML ContentPage 內的 BindingContext 屬性內。

在建構式內,還針對的姓名文字輸入盒 Entry 這個控制項,訂閱綁定了該控制項的 PropertyChanging 與 PropertyChanged 這兩個事件,所以,當在使用這個 Entry 控制項的時候,有宣告某些屬性有進行資料綁定的行為,並且這個屬性值有變更的時候,就會觸發這了 Lambda 事件。

另外,還針對 ViewModel 內的 Person 物件,同樣的也訂閱了 PropertyChanging 與 PropertyChanged 這兩個事件,一旦這些事件被觸發之後,將會顯示訊息文字在 Visual Studio 輸出視窗內。

進行測試與觀察

由於 .NET MAUI 專案具有單一專案,但是可以在不同平台下執行的特性,因此,在這裡將會選擇最簡單的執行方式,那就是選擇使用 Windows 平台下來執行

  • 點選最上方中間工具列區域的 [Windows Machine] 按鈕

  • 這個專案將會產生出一個 WinUI 3 的應用程式

  • 執行結果畫面如下

  • 點選 [變更屬性] 按鈕

    此時將會觸發 ViewModel 內的 ChangeData() 方法,在這個方法內,會變更 Person 物件內的 Name 與 Age 這兩個屬性值

  • 從 Visual Studio 2022 [輸出] 視窗內,將會看到底下輸出內容

ViewModel.Person : Name 正在進行變更完成
姓名控制項 Text 正在進行變更完成
姓名控制項 Text 已經變更完成
ViewModel.Person :  Name 已經變更完成
ViewModel.Person : Age 正在進行變更完成
姓名控制項 Height 正在進行變更完成
姓名控制項 Height 已經變更完成
ViewModel.Person :  Age 已經變更完成
姓名控制項 CursorPosition 正在進行變更完成
姓名控制項 CursorPosition 已經變更完成

從輸出內容可以看出,一旦按鈕被點擊後,就會變更 ViewModel 內的 Person.Name 的屬性值,在變更之前,將會顯示 [ViewModel.Person : Name 正在進行變更完成] ,緊接著因為 Name 這個屬性有綁定到 Entry.Text 這個可綁定屬性上,因此,將會觸發其事件,所以,將會顯示出 [姓名控制項 Text 正在進行變更完成],當都變更完成後,就會顯示出 [姓名控制項 Text 已經變更完成] 與 [ViewModel.Person : Name 已經變更完成] 這些內容

從上述的執行結果,可以看到透過 PropertyChanged 這樣的屬性變更通知機制,使得 XAML 的資料綁定作業可以無縫的運作起來。













2022年8月1日 星期一

MAUI : Data Binding - 4 CommunityToolkit.Mvvm 套件,透過原始碼產生來簡化屬性變更通知程式設計碼

Data Binding - 4 CommunityToolkit.Mvvm 套件,透過原始碼產生來簡化屬性變更通知程式設計碼

延續上一篇 資料綁定 Data Binding - 3 PropertyChanged.Fody 套件,大幅簡化屬性變更通知程式設計碼 文章中,透過 PropertyChanged.Fody 套件的協助,使得在進行 ViewModel 程式碼設計的時候,針對需要進行資料綁定的屬性設計方式,僅需要使用 自動實作的屬性 方式來進行設計屬性程式碼,而不再需要使用 含有支援欄位的屬性 ,大幅簡化所要重複輸入相同程式碼的工作,許多實作細節,例如,當新設定的屬性值與原有的屬性值相同的時候,是不會觸發 PropertyChanged 事件,等等這些細部考量,也間接地提升整體的程式碼品質與效能。

這一切的成果都來自於 PropertyChanged.Fody 這個套件在編譯時期,自動的注入相關 IL 程式碼到這個專案內,自動完成相關程式碼設計工作,然而,最近有一套在 毛伊 MAUI 開發平台上非常紅火的套件,那就是 CommunityToolkit.Mvvm,根據官方文件上的解釋為 : 套件 CommunityToolkit.Mvvm (稱為 MVVM Toolkit,先前名為 Microsoft.Toolkit.Mvvm) 是現代化、快速且模組化的 MVVM 程式庫。 它是 Windows 社群工具組的一部分,更詳細的理解,CommunityToolkit.Mvvm 這個套件源自於 MVVMLight 上的風格,具有輕量級特色,在這篇文章中,將來體驗如何使用這個套件,設計出具有屬性異動事件通知 PropertyChanged 的功能。

系列文章清單

1 自行建置 INotifyPropertyChanged 介面

2 設計基底類別,透過繼承可以使用屬性變更通知

3 PropertyChanged.Fody 套件,大幅簡化屬性變更通知程式設計碼

4 CommunityToolkit.Mvvm 套件,透過原始碼產生來簡化屬性變更通知程式設計碼

5 在 Maui 專案內,如何得知 ViewModel 內的屬性產生異動,而 View 可以收到通知呢?

建立新專案

  • 開啟 Visual Studio 2022 開發工具
  • 當 [Visual Studio 2022] 對話窗出現的時候
  • 點選右下角的 [建立新的專案] 按鈕選項
  • 現在將看到 [建立新專案] 對話窗
  • 請選擇 [主控台應用程式] 這個專案範本
  • 點選右下角的 [下一步] 按鈕
  • 此時將會看到 [設定新的專案] 對話窗
  • 在 [專案名稱] 欄位,輸入 csCommunityToolkitMVVM
  • 點選右下角的 [下一步] 按鈕
  • 最後會看到 [其他資訊] 對話窗
  • 請勾選 [不要使用最上層語句] 這個文字檢查盒控制項
  • 點選右下角的 [建立] 按鈕

加入 CommunityToolkit.Mvvm 的 NuGet 套件

  • 滑鼠右擊該專案的 [相依性] 節點
  • 從彈出功能表中選擇 [管理 NuGet 套件] 功能選項
  • 此時,[NuGet: csCommunityToolkitMVVM] 視窗將會出現
  • 點選 [瀏覽] 標籤頁次
  • 在左上方的搜尋文字輸入盒內輸入 CommunityToolkit.Mvvm 關鍵字
  • 若你沒有看到 8.0 以上的版本,請勾選 [包括搶鮮版] 檢查盒控制項
  • 現在,將會看到 CommunityToolkit.Mvvm 套件出現在清單內
  • 點選這個 CommunityToolkit.Mvvm 套件,並且點選右上方的 [安裝] 按鈕,安裝這個套件到這個專案內。

使用 CommunityToolkit.Mvvm 提供功能來設計具有屬性變更通知的 Person 類別

底下的範例程式碼,都將會建立在 [Program.cs] 這個檔案內

首先要建立一個 Person 類別,其中將會有兩個屬性,分別為 姓名 Name 與 年紀 Age

完成後的程式碼如下:

public partial class Person : ObservableObject
{
    [ObservableProperty]
    private string name;

    [ObservableProperty]
    private int age;

    private string customDesign;
    public string CustomDesign
    {
        get => name;
        set => SetProperty(ref customDesign, value);
    }
}

現在要來看看,當使用了 CommunityToolkit.Mvvm 這個套件時候,要進行甚麼樣的設計工作,才能夠讓這個類別具有屬性異動情況發生的時候,可以發送出 PropertyChanged 事件通知功能。

首先,對於要啟用這個屬性異動事件通知的類別,需要在類別名稱前面加入 Partial 這個關鍵字,因為稍後的介紹內容,將會看到 CommunityToolkit.Mvvm 最強大功能,那就是原始碼產生器 Source Generator ,這個套件會自動產生相關程式碼出來,為了要能夠讓這個類別可以提供更多的服務與功能,因此,需要將這個類別使用 Partial 關鍵字來設計成為一個部分類別。

接著,這個類別需要繼承 ObservableObject 在微軟的文件上解釋為:是實作 INotifyPropertyChanged 和 INotifyPropertyChanging 介面可觀察之物件的基類。 它可以做為支援屬性變更通知之各種物件的起點。也就是說,只要類別繼承了 ObservableObject 這個類別,就已經完成 INotifyPropertyChanged 相關實作與準備工作, 想要了解更多 ObservableObject 這個類別有哪些 方法 與 事件 可以使用,可以參考 ObservableObject 類別

完成了最基本的準備工作,現在,可以來開始進行這個類別具有屬性變更通知機制的屬性設計囉。

當使用 CommunityToolkit.Mvvm 進行屬性變更通知設計的時候,採用另外一種作法,對於具備屬性異動通知的屬性成員,可以使用欄位的方式來設計,例如,對於姓名這個屬性,便可以僅設計 private string name; 這樣的欄位即可。可以,這個欄位的存取限制為私有,也就是說,對於外部的物件是無法存取這個欄位值,所以,需要在這個欄位前面加上 [ObservableProperty] 屬性宣告,宣告這個欄位具有屬性異動通知的能力。

一旦完成這樣的設計,CommunityToolkit.Mvvm 套件便會自動產生出另外一個同名的 Partial 部分類別,幫助程式設計師把相關程式碼都產生出來;現在,來看看究竟產生了甚麼程式碼吧。

  • 請在這個專案上找到 [相依性] 節點

  • 展開這個節點,將會看到 [分析器] 節點

  • 緊接著將會看到 [CommunityToolkit.Mvvm.SourceGenerators] 這個節點

  • 繼續探索這個節點,就會看到 [CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator] 節點

  • 這裡將會看到自動產生的類別檔案

  • 請滑鼠雙擊 [csCommunityToolkitMVVM.Person.g.cs] 這個節點

  • 將會看到底下由 CommunityToolkit.Mvvm 套件所產生的程式碼

// <auto-generated/>
#pragma warning disable
#nullable enable
namespace csCommunityToolkitMVVM
{
    partial class Person
    {
        /// <inheritdoc cref="name"/>
        [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.0.0.0")]
        [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
        public string Name
        {
            get => name;
            set
            {
                if (!global::System.Collections.Generic.EqualityComparer<string>.Default.Equals(name, value))
                {
                    OnNameChanging(value);
                    OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
                    name = value;
                    OnNameChanged(value);
                    OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
                }
            }
        }

        /// <inheritdoc cref="age"/>
        [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.0.0.0")]
        [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
        public int Age
        {
            get => age;
            set
            {
                if (!global::System.Collections.Generic.EqualityComparer<int>.Default.Equals(age, value))
                {
                    OnAgeChanging(value);
                    OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Age);
                    age = value;
                    OnAgeChanged(value);
                    OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Age);
                }
            }
        }

        /// <summary>Executes the logic for when <see cref="Name"/> is changing.</summary>
        [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.0.0.0")]
        partial void OnNameChanging(string value);
        /// <summary>Executes the logic for when <see cref="Name"/> just changed.</summary>
        [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.0.0.0")]
        partial void OnNameChanged(string value);
        /// <summary>Executes the logic for when <see cref="Age"/> is changing.</summary>
        [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.0.0.0")]
        partial void OnAgeChanging(int value);
        /// <summary>Executes the logic for when <see cref="Age"/> just changed.</summary>
        [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.0.0.0")]
        partial void OnAgeChanged(int value);
    }
}

首先,同樣的會看到有 partial class Person 這樣的宣告,代表這裡產生出的 Person 類別是個部分類別,將會與其他 Partial Class 整合成為一個完整的類別。

在這個類別往下看,將會看到 CommunityToolkit.Mvvm 套件已經產生了一個 Name 屬性,並且對於 get 存取子與 set 存取子的程式碼,也都產生好;對於 get 存取子,那就是單純的回傳 name 這個欄位值,不過,對於 set 存取子,則會使用 EqualityComparer 類別 來進行比對新舊屬性值是否相同。

若不相同,則會觸發一個額外方法 OnNameChanging(value); ,將會通知這個方法此時這個 Name 屬性值準備要進行變更了,而對於屬性值已經完成變更了,將會觸發 OnNameChanged(value); 方法,通知這個 Name 屬性值已經變更完成了;對於這兩個方法,可以額外自行設計在這個類別內,函式名稱要遵從 [On_PropertyName_Changing] 與 [On_PropertyName_Changed] 這兩個名稱規範即可。

雖然在這裡看到有產生這兩個呼叫方法,可是,最終的組件內,透過 ILSpy ,並沒有看到這裡有這兩個方法的呼叫,這是因為在這個 Person 類別內,沒有設計這兩個方法在裡面所導致的。底下將會是透過 ILSpy 反組譯工具所查看到關於 姓名 Name 這個欄位最終程式碼。

[GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.0.0.0")]
[ExcludeFromCodeCoverage]
public string Name
{
    get
    {
        return name;
    }
    set
    {
        if (!EqualityComparer<string>.Default.Equals(name, value))
        {
            OnPropertyChanging(__KnownINotifyPropertyChangingArgs.Name);
            name = value;
            OnPropertyChanged(__KnownINotifyPropertyChangedArgs.Name);
        }
    }
}

當實際要使用 name = value; 敘述來進行屬性值變更前,將會執行 OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name); 方法呼叫,將會觸發正在進行屬性變更的通知事件,而在屬性變更完成之後,則會執行 OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name); 這個方法,觸發屬性已經變更完成的事件通知,其實,這部分就是我們之前自行實作 INotifyPropertyChanged 介面該做的手工程式碼。

確認採用 CommunityToolkit.Mvvm 套件的設計是否可正常運作

在這裡將會採用同樣的測試程式碼,如下所示

public class Program
{
    static void Main(string[] args)
    {
        Person person = new Person();
        person.PropertyChanged += (s, e) =>
        {
            WriteLine($"屬性 {e.PropertyName} 已經變更");
        };

        WriteLine("準備要修改 Name 屬性值");
        person.Name = "Vulcan Lee";

        WriteLine("Press any key for continuing...");
        ReadKey();

        WriteLine("準備要修改 Age 屬性值");
        person.Age = 25;

        WriteLine("Press any key for continuing...");
        ReadKey();
    }
}

在這裡先建立一個型別為 [Person] 的物件,指派給 person 變數內

接著,因為每個 [Person] 物件,都有個公開的 [PropertyChanged] 事件,因此,在此將需要訂閱這個事件,在此使用 Lambda 運算式 設計一個匿名委派方法來綁定這個事件,如此,當有屬性變更的事件產生的時候,將會觸發這裡綁定的 Lambda 委派方法,也就是會在螢幕上顯示出哪個屬性值已經變更了。

完成的變更屬性的事件訂閱與綁定設計,接下來就是要開始針對這個 person 物件的兩個屬性值進行變動,看看是否會有屬性變動通知事件產生,底下是執行後的結果內容。

這裡將會是整個完整的測試程式碼

global using static System.Console;
using CommunityToolkit.Mvvm.ComponentModel;

namespace csCommunityToolkitMVVM
{
    public partial class Person : ObservableObject
    {
        #region 需要做資料綁定的屬性,使用欄位的方式來宣告即可,都都採用自動屬性方式來宣告即可
        #region 姓名
        [ObservableProperty]
        private string name;
        #endregion

        #region 年紀
        [ObservableProperty]
        private int age;
        #endregion

        #region 自行手動設計
        private string customDesign;

        public string CustomDesign
        {
            get => name;
            set => SetProperty(ref customDesign, value);
        }
        #endregion
        #endregion
    }

    public class Program
    {
        static void Main(string[] args)
        {
            Person person = new Person();
            person.PropertyChanged += (s, e) =>
            {
                WriteLine($"屬性 {e.PropertyName} 已經變更");
            };

            WriteLine("準備要修改 Name 屬性值");
            person.Name = "Vulcan Lee";

            WriteLine("Press any key for continuing...");
            ReadKey();

            WriteLine("準備要修改 Age 屬性值");
            person.Age = 25;

            WriteLine("Press any key for continuing...");
            ReadKey();
        }
    }
}