WPF中MVVM模式的理解與實(shí)現(xiàn)
MVVM模式的介紹
MVVM(Model-View-ViewModel)是一種設(shè)計(jì)模式,特別適用于WPF(Windows Presentation Foundation)等XAML-based的應(yīng)用程序開(kāi)發(fā)。MVVM模式主要包含三個(gè)部分:Model(模型)、View(視圖)和ViewModel(視圖模型)。
- Model(模型):模型代表的是業(yè)務(wù)邏輯和數(shù)據(jù)。它包含了應(yīng)用程序中用于處理的核心數(shù)據(jù)對(duì)象。模型通常包含業(yè)務(wù)規(guī)則、數(shù)據(jù)訪問(wèn)和存儲(chǔ)邏輯。
- View(視圖):視圖是用戶看到和與之交互的界面。在WPF中,視圖通常由XAML定義,并且包含各種用戶界面元素,如按鈕、文本框、列表等。
- ViewModel(視圖模型):視圖模型是視圖的抽象,它包含視圖所需的所有數(shù)據(jù)和命令。視圖模型通過(guò)實(shí)現(xiàn)
INotifyPropertyChanged
接口和使用ICommand
對(duì)象,將視圖的狀態(tài)和行為抽象化,從而實(shí)現(xiàn)了視圖和模型的解耦。
MVVM模式的主要優(yōu)點(diǎn)是分離了視圖和模型,使得視圖和業(yè)務(wù)邏輯之間的依賴性降低,提高了代碼的可維護(hù)性和可測(cè)試性。此外,通過(guò)數(shù)據(jù)綁定和命令綁定,MVVM模式可以減少大量的樣板代碼,使得代碼更加簡(jiǎn)潔和易于理解。
不使用MVVM模式的例子
要真正理解為什么要使用MVVM,使用MVVM有什么好處,肯定要與不使用MVVM的情況進(jìn)行對(duì)比。在Winform中我們使用了事件驅(qū)動(dòng)編程,同樣在WPF中我們也可以使用事件驅(qū)動(dòng)編程。
Windows Forms(WinForms)是一種基于事件驅(qū)動(dòng)的圖形用戶界面(GUI)框架。在WinForms中,用戶與應(yīng)用程序的交互主要通過(guò)事件來(lái)驅(qū)動(dòng)。
事件驅(qū)動(dòng)編程是一種編程范式,其中程序的執(zhí)行流程由外部事件(如用戶操作或系統(tǒng)事件)決定。在WinForms中,事件可以是用戶的各種操作,如點(diǎn)擊按鈕、選擇菜單項(xiàng)、輸入文本等,也可以是系統(tǒng)的事件,如窗口加載、大小改變等。
當(dāng)一個(gè)事件發(fā)生時(shí),會(huì)觸發(fā)與之關(guān)聯(lián)的事件處理器(Event Handler)。事件處理器是一個(gè)函數(shù)或方法,用于響應(yīng)特定的事件。例如,當(dāng)用戶點(diǎn)擊一個(gè)按鈕時(shí),可以觸發(fā)一個(gè)事件處理器,執(zhí)行一些特定的操作。
在WinForms中,你可以為各種控件添加事件處理器,以響應(yīng)用戶的操作。這種事件驅(qū)動(dòng)的方式使得你可以很容易地創(chuàng)建交互式的GUI應(yīng)用程序,而無(wú)需關(guān)心程序的執(zhí)行流程。
事件驅(qū)動(dòng)的簡(jiǎn)圖如下圖所示:
- 事件源(Event Source):事件源是產(chǎn)生事件的對(duì)象。在WinForms中,事件源通常是用戶界面元素,如按鈕、文本框、菜單項(xiàng)等。當(dāng)用戶與這些元素進(jìn)行交互(如點(diǎn)擊按鈕、輸入文本等)時(shí),這些元素就會(huì)產(chǎn)生相應(yīng)的事件。
- 事件(Event):事件是由事件源產(chǎn)生的一個(gè)信號(hào),表示某種特定的事情已經(jīng)發(fā)生。例如,當(dāng)用戶點(diǎn)擊一個(gè)按鈕時(shí),按鈕就會(huì)產(chǎn)生一個(gè)Click事件。事件通常包含一些關(guān)于該事件的信息,例如事件發(fā)生的時(shí)間、事件的源對(duì)象等。
- 事件處理器(Event Handler):事件處理器是一個(gè)函數(shù)或方法,用于響應(yīng)特定的事件。當(dāng)一個(gè)事件發(fā)生時(shí),與該事件關(guān)聯(lián)的事件處理器就會(huì)被調(diào)用。在事件處理器中,你可以編寫代碼來(lái)定義當(dāng)事件發(fā)生時(shí)應(yīng)該執(zhí)行的操作。例如,你可以在按鈕的Click事件處理器中編寫代碼,定義當(dāng)用戶點(diǎn)擊按鈕時(shí)應(yīng)該執(zhí)行的操作。
現(xiàn)在我們通過(guò)一個(gè)例子在WPF中使用事件驅(qū)動(dòng)編程。
首先看一下我們的示例xaml頁(yè)面:
<Window x:Class="WPF_MVVM_Pattern.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WPF_MVVM_Pattern" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded"> <StackPanel> <ToolBar> <Label Content="姓名:"></Label> <TextBox x:Name="nameTextBox" Width="50"></TextBox> <Label Content="郵箱:"></Label> <TextBox x:Name="emailTextBox" Width="100"></TextBox> <Button Content="添加" Click="AddUser"></Button> </ToolBar> <StackPanel> <DataGrid x:Name="dataGrid1"></DataGrid> </StackPanel> </StackPanel> </Window>
使用了兩個(gè)事件,分別是窗體加載事件:
Loaded="Window_Loaded"
與button點(diǎn)擊事件:
<Button Content="添加" Click="AddUser"></Button>
實(shí)現(xiàn)該操作與兩個(gè)類有關(guān):
public class User { public string? Name { get; set; } public string? Email { get; set; } }
public static class UserManager { public static ObservableCollection<User> DataBaseUsers = new ObservableCollection<User>() { new User() { Name = "小王", Email = "123@qq.com" }, new User() { Name = "小紅", Email = "456@qq.com" }, new User() { Name = "小五", Email = "789@qq.com" } }; public static ObservableCollection<User> GetUsers() { return DataBaseUsers; } public static void AddUser(User user) { DataBaseUsers.Add(user); } }
窗體加載事件處理程序:
private void Window_Loaded(object sender, RoutedEventArgs e) { dataGrid1.ItemsSource = UserManager.GetUsers(); }
"添加"按鈕點(diǎn)擊事件處理程序:
private void AddUser(object sender, RoutedEventArgs e) { User user = new User(); user.Name = nameTextBox.Text; user.Email = emailTextBox.Text; UserManager.AddUser(user); MessageBox.Show("成功添加用戶!"); }
實(shí)現(xiàn)的效果如下所示:
使用MVVM的例子
剛剛我們使用的是事件驅(qū)動(dòng)編程,我們?cè)趙inform開(kāi)發(fā)中經(jīng)常這樣干。對(duì)于一些小項(xiàng)目或者demo程序這樣做很方便,但是如果業(yè)務(wù)邏輯很多,這樣做就不好維護(hù),因?yàn)閁I與業(yè)務(wù)邏輯嚴(yán)重耦合了。
我們經(jīng)常在cs文件中使用xaml中的元素,也就是經(jīng)常在cs中引用xaml中的元素,如下所示:
在C#代碼文件中直接引用XAML元素,會(huì)導(dǎo)致代碼與界面元素之間的耦合度增加,這是一種不良的編程實(shí)踐。以下是這種做法的一些潛在問(wèn)題:
- 耦合度高:代碼與界面元素緊密耦合,這使得代碼更難以維護(hù)和重用。如果你更改了界面元素(例如更改了元素的名稱或類型),你可能需要修改引用這個(gè)元素的所有代碼。
- 測(cè)試?yán)щy:由于代碼直接依賴于界面元素,這使得單元測(cè)試變得困難。你可能需要?jiǎng)?chuàng)建一個(gè)界面元素的實(shí)例,或者使用復(fù)雜的模擬技術(shù),才能測(cè)試這些代碼。
- 違反MVVM模式:在WPF中,推薦使用MVVM(Model-View-ViewModel)模式來(lái)組織代碼。在MVVM模式中,視圖(View)和模型(Model)之間的交互是通過(guò)視圖模型(ViewModel)來(lái)進(jìn)行的,而不是直接在代碼中引用界面元素。
開(kāi)始使用MVVM模式
RelayCommand
首先新建一個(gè)Commands文件夾,新建一個(gè)RelayComand類:
RelayCommand如下:
public class RelayCommand : ICommand { public event EventHandler? CanExecuteChanged; private Action<object> _Excute { get; set; } private Predicate<object> _CanExcute { get;set; } public RelayCommand(Action<object> ExcuteMethod, Predicate<object> CanExcuteMethod) { _Excute = ExcuteMethod; _CanExcute = CanExcuteMethod; } public bool CanExecute(object? parameter) { return _CanExcute(parameter); } public void Execute(object? parameter) { _Excute(parameter); } }
RelayCommand實(shí)現(xiàn)了ICommand接口。
先來(lái)介紹一下ICommand
接口。
ICommand
在WPF(Windows Presentation Foundation)中,ICommand
是一個(gè)接口,它定義了一種機(jī)制,用于在用戶界面(UI)中處理事件,這種機(jī)制與用戶界面的具體行為進(jìn)行了解耦。這是實(shí)現(xiàn)MVVM(Model-View-ViewModel)設(shè)計(jì)模式的關(guān)鍵部分。
ICommand
接口包含兩個(gè)方法和一個(gè)事件:
Execute(object parameter)
:當(dāng)調(diào)用此命令時(shí),應(yīng)執(zhí)行的操作。CanExecute(object parameter)
:如果可以執(zhí)行Execute
方法,則返回true
;否則返回false
。這可以用于啟用或禁用控件,例如按鈕。CanExecuteChanged
事件:當(dāng)CanExecute
的返回值可能發(fā)生更改時(shí),應(yīng)引發(fā)此事件。
ICommand的結(jié)構(gòu)圖如下所示:
代碼如下所示:
public interface ICommand { event EventHandler? CanExecuteChanged; bool CanExecute(object? parameter); void Execute(object? parameter); }
現(xiàn)在再來(lái)看看RelayCommand
。
RelayCommand
RelayCommand
是一種常用于WPF和MVVM模式的設(shè)計(jì)模式,它是一種特殊的命令類型。在MVVM模式中,RelayCommand
允許將命令的處理邏輯從視圖模型中分離出來(lái),使得視圖模型不需要知道命令的具體執(zhí)行邏輯,從而實(shí)現(xiàn)了視圖模型和命令處理邏輯的解耦。
RelayCommand
通常包含兩個(gè)主要部分:CanExecute
和Execute
。CanExecute
是一個(gè)返回布爾值的函數(shù),用于確定命令是否可以執(zhí)行。Execute
是一個(gè)執(zhí)行命令的函數(shù),當(dāng)CanExecute
返回true
時(shí),Execute
將被調(diào)用。
這種設(shè)計(jì)模式使得你可以在不改變視圖模型的情況下,更改命令的處理邏輯,提高了代碼的可維護(hù)性和可重用性。
簡(jiǎn)單理解就是RelayCommand
是ICommand
接口的一個(gè)常見(jiàn)實(shí)現(xiàn),它允許你將Execute
和CanExecute
的邏輯定義為委托,從而實(shí)現(xiàn)對(duì)命令的靈活處理。
在RelayCommand中我們定義了兩個(gè)委托:
private Action<object> _Excute { get; set; } private Predicate<object> _CanExcute { get;set; }
Action<object>
是一個(gè)委托,它封裝了一個(gè)接受單個(gè)參數(shù)并且沒(méi)有返回值的方法。這個(gè)參數(shù)的類型是object
。
對(duì)應(yīng)于這一部分:
Predicate<object>
是一個(gè)委托,它封裝了一個(gè)接受單個(gè)參數(shù)并返回一個(gè)bool
值的方法。這個(gè)參數(shù)的類型是object
。
對(duì)應(yīng)于這一部分:
RelayCommand的構(gòu)造函數(shù)為:
public RelayCommand(Action<object> ExcuteMethod, Predicate<object> CanExcuteMethod) { _Excute = ExcuteMethod; _CanExcute = CanExcuteMethod; }
現(xiàn)在去看看View—ViewModel
。
View—ViewModel
ViewModel是一個(gè)抽象,它代表了View的狀態(tài)和行為。ViewModel包含了View所需的數(shù)據(jù),并提供了命令以響應(yīng)View上的用戶操作。ViewModel不知道View的具體實(shí)現(xiàn),它只知道如何提供View所需的狀態(tài)和行為。
ViewModel的主要職責(zé)包括:
- 數(shù)據(jù)綁定:ViewModel提供了View所需的數(shù)據(jù)。這些數(shù)據(jù)通常是以屬性的形式提供的,當(dāng)這些屬性的值改變時(shí),ViewModel會(huì)通過(guò)實(shí)現(xiàn)
INotifyPropertyChanged
接口來(lái)通知View。 - 命令綁定:ViewModel提供了命令以響應(yīng)View上的用戶操作。這些命令通常是以
ICommand
接口的實(shí)現(xiàn)的形式提供的。 - 視圖邏輯:ViewModel包含了View的邏輯,例如,決定何時(shí)顯示或隱藏某個(gè)元素,何時(shí)啟用或禁用某個(gè)按鈕等。
新建一個(gè)ViewModel文件夾,在該文件夾中新建一個(gè)MainViewModel類:
目前寫的MainViewModel如下:
public class MainViewModel { public ObservableCollection<User> Users { get; set; } public ICommand AddUserCommand { get; set; } public string? Name { get; set; } public string? Email { get; set; } public MainViewModel() { Users = UserManager.GetUsers(); AddUserCommand = new RelayCommand(AddUser, CanAddUser); } private bool CanAddUser(object obj) { return true; } private void AddUser(object obj) { User user = new User(); user.Name = Name; user.Email = Email; UserManager.AddUser(user); } }
現(xiàn)在我們結(jié)合這張圖,理解View與ViewModel之間的關(guān)系:
一個(gè)一個(gè)來(lái)理解,首先最重要的就是數(shù)據(jù)綁定。
現(xiàn)在View的xaml如下:
<Window x:Class="WPF_MVVM_Pattern.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WPF_MVVM_Pattern" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <StackPanel> <ToolBar> <Label Content="姓名:"></Label> <TextBox Text="{Binding Name}" Width="50"></TextBox> <Label Content="郵箱:"></Label> <TextBox Text="{Binding Email}" Width="100"></TextBox> <Button Content="添加" Command="{Binding AddUserCommand }"></Button> </ToolBar> <StackPanel> <DataGrid ItemsSource="{Binding Users}"></DataGrid> </StackPanel> </StackPanel> </Window>
cs如下:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); MainViewModel mainViewModel = new MainViewModel(); this.DataContext = mainViewModel; } }
將MainWindow的DataContext賦值給了mainViewModel。
在
<TextBox Text="{Binding Name}" Width="50"></TextBox> <TextBox Text="{Binding Email}" Width="100"></TextBox> <DataGrid ItemsSource="{Binding Users}"></DataGrid>
中進(jìn)行了數(shù)據(jù)綁定,對(duì)應(yīng)于圖中的這一部分:
現(xiàn)在來(lái)看看命令綁定。
<Button Content="添加" Command="{Binding AddUserCommand }"></Button>
進(jìn)行了命令綁定,對(duì)應(yīng)于圖中這一部分:
現(xiàn)在先來(lái)看看效果:
現(xiàn)在已經(jīng)實(shí)現(xiàn)了與前面基于事件驅(qū)動(dòng)同樣的效果,但是上面那張圖中的Send Notifications還沒(méi)有體現(xiàn)。
Send Notifications表示ViewModel中的更改會(huì)通知View。
現(xiàn)在我們來(lái)以一個(gè)例子說(shuō)明一下Send Notifications是如何實(shí)現(xiàn)的。
首先添加一個(gè)測(cè)試命令:
public ICommand TestCommand { get; set; }
在構(gòu)造函數(shù)中添加:
TestCommand = new RelayCommand(Test, CanTest);
實(shí)現(xiàn)Test與CanTest方法:
private bool CanTest(object obj) { return true; } private void Test(object obj) { Name = "小1"; Email = "111@qq.com"; }
View中修改如下:
<Button Content="測(cè)試" Command="{Binding TestCommand }"></Button>
現(xiàn)在去嘗試,我們會(huì)發(fā)現(xiàn)沒(méi)有效果,原因是我們的ViewModel沒(méi)有實(shí)現(xiàn)INotifyPropertyChanged
接口。
INotifyPropertyChanged接口介紹
在WPF(Windows Presentation Foundation)中,INotifyPropertyChanged
接口用于實(shí)現(xiàn)數(shù)據(jù)綁定中的屬性更改通知。當(dāng)綁定到UI元素的數(shù)據(jù)源中的屬性值發(fā)生更改時(shí),INotifyPropertyChanged
接口可以通知UI元素更新。
INotifyPropertyChanged
接口只定義了一個(gè)事件:PropertyChanged
。當(dāng)屬性值發(fā)生更改時(shí),應(yīng)觸發(fā)此事件。事件參數(shù)PropertyChangedEventArgs
包含更改的屬性的名稱。
現(xiàn)在我們的MainViewModel實(shí)現(xiàn)一下INotifyPropertyChanged接口,如下所示:
public class MainViewModel : INotifyPropertyChanged { public ObservableCollection<User> Users { get; set; } public ICommand AddUserCommand { get; set; } public ICommand TestCommand { get; set; } private string? _name; public string? Name { get { return _name; } set { if (_name != value) { _name = value; OnPropertyChanged(nameof(Name)); } } } private string? _email; public string? Email { get { return _email; } set { if (_email != value) { _email = value; OnPropertyChanged(nameof(Email)); } } } public MainViewModel() { Users = UserManager.GetUsers(); AddUserCommand = new RelayCommand(AddUser, CanAddUser); TestCommand = new RelayCommand(Test, CanTest); } private bool CanTest(object obj) { return true; } private void Test(object obj) { Name = "小1"; Email = "111@qq.com"; } private bool CanAddUser(object obj) { return true; } private void AddUser(object obj) { User user = new User(); user.Name = Name; user.Email = Email; UserManager.AddUser(user); } public event PropertyChangedEventHandler? PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
現(xiàn)在再嘗試一下,會(huì)發(fā)現(xiàn)ViewModel中的更改會(huì)成功通知View了,如下所示:
對(duì)應(yīng)于圖中的這一部分:
現(xiàn)在我們來(lái)看看ViewModel—Model。
ViewModel—Model
現(xiàn)在我們來(lái)看看ViewModel與Model之間的關(guān)系,可以根據(jù)下面這張圖進(jìn)行理解:
Model(模型):Model代表了業(yè)務(wù)邏輯和數(shù)據(jù)。它包含了應(yīng)用程序中的數(shù)據(jù)和對(duì)數(shù)據(jù)的操作,例如,從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù),或者向數(shù)據(jù)庫(kù)中添加數(shù)據(jù)。Model是獨(dú)立于UI的,它不知道UI的存在。
ViewModel(視圖模型):ViewModel是Model和View之間的橋梁。它包含了View所需的數(shù)據(jù)(這些數(shù)據(jù)來(lái)自于Model),并提供了命令以響應(yīng)View上的用戶操作。ViewModel將Model的數(shù)據(jù)轉(zhuǎn)換為View可以顯示的數(shù)據(jù),同時(shí),它也將View上的用戶操作轉(zhuǎn)換為對(duì)Model的操作。
在我們這個(gè)例子中我們的數(shù)據(jù)來(lái)源于Model文件夾下的User類與UserManager類:
這里的Send Notifications又該如何理解呢?
我們也是以一個(gè)小例子進(jìn)行說(shuō)明。
首先將ViewModel中的Test方法修改為:
private void Test(object obj) { Users[0].Name = "小1"; Users[0].Email = "111@qq.com"; }
會(huì)發(fā)現(xiàn)現(xiàn)在并不會(huì)發(fā)送通知,實(shí)現(xiàn)View上的修改,這是因?yàn)閁ser類并沒(méi)有實(shí)現(xiàn)INotifyPropertyChanged接口,現(xiàn)在修改User類實(shí)現(xiàn)INotifyPropertyChanged接口:
public class User : INotifyPropertyChanged { private string? _name; public string? Name { get { return _name; } set { if (_name != value) { _name = value; OnPropertyChanged(nameof(Name)); } } } private string? _email; public string? Email { get { return _email; } set { if (_email != value) { _email = value; OnPropertyChanged(nameof(Email)); } } } public event PropertyChangedEventHandler? PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
現(xiàn)在可以實(shí)現(xiàn)通知了,效果如下所示:
使用MVVM庫(kù)
我們會(huì)發(fā)現(xiàn)如果全部都手動(dòng)實(shí)現(xiàn)MVVM模式的話,代碼有點(diǎn)多,有點(diǎn)麻煩。這時(shí)候就可以使用一些MVVM庫(kù)來(lái)簡(jiǎn)化我們的操作。
這里以CommunityToolkit.Mvvm為例,進(jìn)行說(shuō)明。
CommunityToolkit.Mvvm介紹
CommunityToolkit.Mvvm
是Microsoft Community Toolkit的一部分,它是一個(gè)輕量級(jí)但功能強(qiáng)大的MVVM(Model-View-ViewModel)庫(kù),旨在幫助開(kāi)發(fā)者更容易地實(shí)現(xiàn)MVVM設(shè)計(jì)模式。
該庫(kù)提供了一些基礎(chǔ)類,如ObservableObject
和ObservableRecipient
,這些類實(shí)現(xiàn)了INotifyPropertyChanged
接口,并提供了SetProperty
方法,可以在屬性值改變時(shí)觸發(fā)PropertyChanged
事件。這使得數(shù)據(jù)綁定變得更加簡(jiǎn)單和高效。
此外,該庫(kù)還提供了ICommand
接口的實(shí)現(xiàn),如RelayCommand
和AsyncRelayCommand
,這些類可以幫助你創(chuàng)建命令,命令是MVVM模式中的一個(gè)重要組成部分。
CommunityToolkit.Mvvm
還提供了一些其他有用的特性,如消息傳遞、設(shè)計(jì)時(shí)數(shù)據(jù)支持等,這些特性可以幫助你更好地組織和管理你的代碼。
CommunityToolkit.Mvvm
是一個(gè)強(qiáng)大的工具,它可以幫助你更容易地實(shí)現(xiàn)MVVM模式,從而提高你的代碼質(zhì)量和開(kāi)發(fā)效率。
修改之后的ViewModel如下所示:
public partial class MainViewModel : ObservableObject { public ObservableCollection<User> Users { get; set; } [ObservableProperty] private string? name; [ObservableProperty] private string? email; public MainViewModel() { Users = UserManager.GetUsers(); } [RelayCommand] private void Test(object obj) { Users[0].Name = "小1"; Users[0].Email = "111@qq.com"; } [RelayCommand] private void AddUser(object obj) { User user = new User(); user.Name = Name; user.Email = Email; UserManager.AddUser(user); } }
修改之后的User類如下所示:
public partial class User : ObservableObject { [ObservableProperty] private string? _name; [ObservableProperty] private string? _email; }
用到了CommunityToolkit.Mvvm
庫(kù)中的三個(gè)東西,分別是ObservableObject、[ObservableProperty]與[RelayCommand]。
先來(lái)看一下ObservableObject。
ObservableObject
是CommunityToolkit.Mvvm
庫(kù)中的一個(gè)基礎(chǔ)類,它實(shí)現(xiàn)了INotifyPropertyChanged
接口。這個(gè)接口是.NET數(shù)據(jù)綁定基礎(chǔ)架構(gòu)的一部分,當(dāng)對(duì)象的一個(gè)屬性改變時(shí),它會(huì)通知綁定到該屬性的任何元素。
具體見(jiàn):ObservableObject - Community Toolkits for .NET | Microsoft Learn
在這里我們使用
[ObservableProperty] private string? name;
它將生成一個(gè)像這樣的可觀察屬性:
public string? Name { get => name; set => SetProperty(ref name, value); }
具體見(jiàn):ObservableProperty attribute - Community Toolkits for .NET | Microsoft Learn
我們使用
[RelayCommand] private void AddUser(object obj) { User user = new User(); user.Name = Name; user.Email = Email; UserManager.AddUser(user); }
代碼生成器會(huì)生成一個(gè)命令如下所示:
private RelayCommand? addUserCommand; public IRelayCommand AddUserCommand => addUserCommand ??= new RelayCommand(AddUser);
具體見(jiàn):RelayCommand attribute - Community Toolkits for .NET | Microsoft Learn
現(xiàn)在我們的ViewModel與Model就可以簡(jiǎn)化了,現(xiàn)在再來(lái)看看效果:
總結(jié)
本文先總體介紹了一下MVVM模式,關(guān)于MVVM模式可以根據(jù)這張圖幫助理解:
由于很多同學(xué)可能與我一樣,是從winform到wpf的,因此在wpf中使用winform中的事件驅(qū)動(dòng)編程范式完成了一個(gè)小例子,關(guān)于事件驅(qū)動(dòng)編程,可以根據(jù)這張圖幫助理解:
由于這種模式耦合比較多,我們想要松耦合,因此開(kāi)始學(xué)習(xí)MVVM模式。我們創(chuàng)建了實(shí)現(xiàn)ICommand
接口的RelayCommand
類,實(shí)現(xiàn)INotifyPropertyChanged
接口的MainViewModel
類與User
類。使用數(shù)據(jù)綁定與命令綁定改寫xaml頁(yè)面。
最后由于手動(dòng)實(shí)現(xiàn)MVVM模式,需要寫很多代碼,看過(guò)去比較復(fù)雜與麻煩,我們可以使用MVVM庫(kù)來(lái)簡(jiǎn)化MVVM模式的實(shí)現(xiàn)。
以上就是WPF中MVVM模式的理解與實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于WPF MVVM模式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
unity shader實(shí)現(xiàn)較完整光照效果
這篇文章主要為大家詳細(xì)介紹了unity shader實(shí)現(xiàn)較完整光照效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11C#實(shí)現(xiàn)自定義動(dòng)畫鼠標(biāo)的示例詳解
這篇文章主要為大家詳細(xì)介紹了如何利用C#實(shí)現(xiàn)自定義動(dòng)畫鼠標(biāo)效果,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)C#有一定的幫助,感興趣的小伙伴可以跟隨小編一起了解一下2022-12-12c++ STL之list對(duì)結(jié)構(gòu)體的增加,刪除,排序等操作詳解
這篇文章主要介紹了c++ STL之list對(duì)結(jié)構(gòu)體的增加,刪除,排序等操作詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12關(guān)于C# TabPage如何隱藏的問(wèn)題
TabPage沒(méi)有Visible屬性,所以只能通過(guò)設(shè)置將其與父控件(tabcontrol)的關(guān)聯(lián)性去除就好了,如下面代碼:2013-04-04C#實(shí)現(xiàn)將商品金額小寫轉(zhuǎn)換成大寫的方法
這篇文章主要介紹了C#實(shí)現(xiàn)將商品金額小寫轉(zhuǎn)換成大寫的方法,涉及C#數(shù)組與字符串的相關(guān)操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-08-08