WPF/C#:理解与实现WPF中的MVVM模式
<h2 id="mvvm模式的介绍">MVVM模式的介绍</h2><p>MVVM(Model-View-ViewModel)是一种设计模式,特别适用于WPF(Windows Presentation Foundation)等XAML-based的应用程序开发。MVVM模式主要包含三个部分:Model(模型)、View(视图)和ViewModel(视图模型)。</p>
<ol>
<li><strong>Model(模型)</strong>:模型代表的是业务逻辑和数据。它包含了应用程序中用于处理的核心数据对象。模型通常包含业务规则、数据访问和存储逻辑。</li>
<li><strong>View(视图)</strong>:视图是用户看到和与之交互的界面。在WPF中,视图通常由XAML定义,并且包含各种用户界面元素,如按钮、文本框、列表等。</li>
<li><strong>ViewModel(视图模型)</strong>:视图模型是视图的抽象,它包含视图所需的所有数据和命令。视图模型通过实现<code>INotifyPropertyChanged</code>接口和使用<code>ICommand</code>对象,将视图的状态和行为抽象化,从而实现了视图和模型的解耦。</li>
</ol>
<p>MVVM模式的主要优点是分离了视图和模型,使得视图和业务逻辑之间的依赖性降低,提高了代码的可维护性和可测试性。此外,通过数据绑定和命令绑定,MVVM模式可以减少大量的样板代码,使得代码更加简洁和易于理解。</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240527095704838.png"></p>
<h2 id="不使用mvvm模式的例子">不使用MVVM模式的例子</h2>
<p>要真正理解为什么要使用MVVM,使用MVVM有什么好处,肯定要与不使用MVVM的情况进行对比。在Winform中我们使用了事件驱动编程,同样在WPF中我们也可以使用事件驱动编程。</p>
<p>Windows Forms(WinForms)是一种基于事件驱动的图形用户界面(GUI)框架。在WinForms中,用户与应用程序的交互主要通过事件来驱动。</p>
<p><strong>事件驱动编程</strong>是一种编程范式,其中程序的执行流程由外部事件(如用户操作或系统事件)决定。在WinForms中,事件可以是用户的各种操作,如点击按钮、选择菜单项、输入文本等,也可以是系统的事件,如窗口加载、大小改变等。</p>
<p>当一个事件发生时,会触发与之关联的<strong>事件处理器(Event Handler)</strong>。事件处理器是一个函数或方法,用于响应特定的事件。例如,当用户点击一个按钮时,可以触发一个事件处理器,执行一些特定的操作。</p>
<p>在WinForms中,你可以为各种控件添加事件处理器,以响应用户的操作。这种事件驱动的方式使得你可以很容易地创建交互式的GUI应用程序,而无需关心程序的执行流程。</p>
<p>事件驱动的简图如下图所示:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240527100814935.png"></p>
<ol>
<li><strong>事件源(Event Source)</strong>:事件源是产生事件的对象。在WinForms中,事件源通常是用户界面元素,如按钮、文本框、菜单项等。当用户与这些元素进行交互(如点击按钮、输入文本等)时,这些元素就会产生相应的事件。</li>
<li><strong>事件(Event)</strong>:事件是由事件源产生的一个信号,表示某种特定的事情已经发生。例如,当用户点击一个按钮时,按钮就会产生一个Click事件。事件通常包含一些关于该事件的信息,例如事件发生的时间、事件的源对象等。</li>
<li><strong>事件处理器(Event Handler)</strong>:事件处理器是一个函数或方法,用于响应特定的事件。当一个事件发生时,与该事件关联的事件处理器就会被调用。在事件处理器中,你可以编写代码来定义当事件发生时应该执行的操作。例如,你可以在按钮的Click事件处理器中编写代码,定义当用户点击按钮时应该执行的操作。</li>
</ol>
<p>现在我们通过一个例子在WPF中使用事件驱动编程。</p>
<p>首先看一下我们的示例xaml页面:</p>
<pre><code class="language-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"
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>
</code></pre>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240527141918081.png"></p>
<p>使用了两个事件,分别是窗体加载事件:</p>
<pre><code class="language-xaml"> Loaded="Window_Loaded"
</code></pre>
<p>与button点击事件:</p>
<pre><code class="language-xaml"><Button Content="添加"
Click="AddUser"></Button>
</code></pre>
<p>实现该操作与两个类有关:</p>
<pre><code class="language-csharp"> public class User
{
public string? Name { get; set; }
public string? Email { get; set; }
}
</code></pre>
<pre><code class="language-csharp"> 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);
}
}
</code></pre>
<p>窗体加载事件处理程序:</p>
<pre><code class="language-csharp"> private void Window_Loaded(object sender, RoutedEventArgs e)
{
dataGrid1.ItemsSource =UserManager.GetUsers();
}
</code></pre>
<p>"添加"按钮点击事件处理程序:</p>
<pre><code class="language-csharp"> private void AddUser(object sender, RoutedEventArgs e)
{
User user = new User();
user.Name = nameTextBox.Text;
user.Email = emailTextBox.Text;
UserManager.AddUser(user);
MessageBox.Show("成功添加用户!");
}
</code></pre>
<p>实现的效果如下所示:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/MVVM%E5%AE%9E%E7%8E%B0%E6%95%88%E6%9E%9C.gif"></p>
<h2 id="使用mvvm的例子">使用MVVM的例子</h2>
<p>刚刚我们使用的是事件驱动编程,我们在winform开发中经常这样干。对于一些小项目或者demo程序这样做很方便,但是如果业务逻辑很多,这样做就不好维护,因为UI与业务逻辑严重耦合了。</p>
<p>我们经常在cs文件中使用xaml中的元素,也就是经常在cs中引用xaml中的元素,如下所示:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240527155929453.png"></p>
<p>在C#代码文件中直接引用XAML元素,会导致代码与界面元素之间的耦合度增加,这是一种不良的编程实践。以下是这种做法的一些潜在问题:</p>
<ol>
<li><strong>耦合度高</strong>:代码与界面元素紧密耦合,这使得代码更难以维护和重用。如果你更改了界面元素(例如更改了元素的名称或类型),你可能需要修改引用这个元素的所有代码。</li>
<li><strong>测试困难</strong>:由于代码直接依赖于界面元素,这使得单元测试变得困难。你可能需要创建一个界面元素的实例,或者使用复杂的模拟技术,才能测试这些代码。</li>
<li><strong>违反MVVM模式</strong>:在WPF中,推荐使用MVVM(Model-View-ViewModel)模式来组织代码。在MVVM模式中,视图(View)和模型(Model)之间的交互是通过视图模型(ViewModel)来进行的,而不是直接在代码中引用界面元素。</li>
</ol>
<h3 id="开始使用mvvm模式">开始使用MVVM模式</h3>
<h3 id="relaycommand">RelayCommand</h3>
<p>首先新建一个Commands文件夹,新建一个RelayComand类:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240528084137467.png"></p>
<p>RelayCommand如下:</p>
<pre><code class="language-csharp">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);
}
}
</code></pre>
<p>RelayCommand实现了ICommand接口。</p>
<p>先来介绍一下<code>ICommand</code>接口。</p>
<h4 id="icommand">ICommand</h4>
<p>在WPF(Windows Presentation Foundation)中,<code>ICommand</code>是一个接口,它定义了一种机制,用于在用户界面(UI)中处理事件,这种机制与用户界面的具体行为进行了解耦。这是实现MVVM(Model-View-ViewModel)设计模式的关键部分。</p>
<p><code>ICommand</code>接口包含两个方法和一个事件:</p>
<ul>
<li><code>Execute(object parameter)</code>:当调用此命令时,应执行的操作。</li>
<li><code>CanExecute(object parameter)</code>:如果可以执行<code>Execute</code>方法,则返回<code>true</code>;否则返回<code>false</code>。这可以用于启用或禁用控件,例如按钮。</li>
<li><code>CanExecuteChanged</code>事件:当<code>CanExecute</code>的返回值可能发生更改时,应引发此事件。</li>
</ul>
<p>ICommand的结构图如下所示:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240528084534354.png"></p>
<p>代码如下所示:</p>
<pre><code class="language-csharp">public interface ICommand
{
event EventHandler? CanExecuteChanged;
bool CanExecute(object? parameter);
void Execute(object? parameter);
}
</code></pre>
<p>现在再来看看<code>RelayCommand</code>。</p>
<h4 id="relaycommand-1">RelayCommand</h4>
<p><code>RelayCommand</code>是一种常用于WPF和MVVM模式的设计模式,它是一种特殊的命令类型。在MVVM模式中,<code>RelayCommand</code>允许将命令的处理逻辑从视图模型中分离出来,使得视图模型不需要知道命令的具体执行逻辑,从而实现了视图模型和命令处理逻辑的解耦。</p>
<p><code>RelayCommand</code>通常包含两个主要部分:<code>CanExecute</code>和<code>Execute</code>。<code>CanExecute</code>是一个返回布尔值的函数,用于确定命令是否可以执行。<code>Execute</code>是一个执行命令的函数,当<code>CanExecute</code>返回<code>true</code>时,<code>Execute</code>将被调用。</p>
<p>这种设计模式使得你可以在不改变视图模型的情况下,更改命令的处理逻辑,提高了代码的可维护性和可重用性。</p>
<p>简单理解就是<code>RelayCommand</code>是<code>ICommand</code>接口的一个常见实现,它允许你将<code>Execute</code>和<code>CanExecute</code>的逻辑定义为委托,从而实现对命令的灵活处理。</p>
<p>在RelayCommand中我们定义了两个委托:</p>
<pre><code class="language-csharp">private Action<object> _Excute { get; set; }
private Predicate<object> _CanExcute { get;set; }
</code></pre>
<p><code>Action<object></code>是一个委托,它封装了一个接受单个参数并且没有返回值的方法。这个参数的类型是<code>object</code>。</p>
<p>对应于这一部分:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240528085200268.png"></p>
<p><code>Predicate<object></code>是一个委托,它封装了一个接受单个参数并返回一个<code>bool</code>值的方法。这个参数的类型是<code>object</code>。</p>
<p>对应于这一部分:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240528085237548.png"></p>
<p>RelayCommand的构造函数为:</p>
<pre><code class="language-csharp"> public RelayCommand(Action<object> ExcuteMethod, Predicate<object> CanExcuteMethod)
{
_Excute = ExcuteMethod;
_CanExcute = CanExcuteMethod;
}
</code></pre>
<p>现在去看看<code>View—ViewModel</code>。</p>
<h3 id="viewviewmodel">View—ViewModel</h3>
<p><strong>ViewModel</strong>是一个抽象,它代表了View的状态和行为。ViewModel包含了View所需的数据,并提供了命令以响应View上的用户操作。ViewModel不知道View的具体实现,它只知道如何提供View所需的状态和行为。</p>
<p>ViewModel的主要职责包括:</p>
<ul>
<li><strong>数据绑定</strong>:ViewModel提供了View所需的数据。这些数据通常是以属性的形式提供的,当这些属性的值改变时,ViewModel会通过实现<code>INotifyPropertyChanged</code>接口来通知View。</li>
<li><strong>命令绑定</strong>:ViewModel提供了命令以响应View上的用户操作。这些命令通常是以<code>ICommand</code>接口的实现的形式提供的。</li>
<li><strong>视图逻辑</strong>:ViewModel包含了View的逻辑,例如,决定何时显示或隐藏某个元素,何时启用或禁用某个按钮等。</li>
</ul>
<p>新建一个ViewModel文件夹,在该文件夹中新建一个MainViewModel类:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240528093058019.png"></p>
<p>目前写的MainViewModel如下:</p>
<pre><code class="language-csharp">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);
}
}
</code></pre>
<p>现在我们结合这张图,理解View与ViewModel之间的关系:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240528093349406.png"></p>
<p>一个一个来理解,首先最重要的就是数据绑定。</p>
<p>现在View的xaml如下:</p>
<pre><code class="language-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>
</code></pre>
<p>cs如下:</p>
<pre><code class="language-csharp">public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MainViewModel mainViewModel = new MainViewModel();
this.DataContext = mainViewModel;
}
}
</code></pre>
<p>将MainWindow的DataContext赋值给了mainViewModel。</p>
<p>在</p>
<pre><code class="language-xaml"> <TextBox Text="{Binding Name}"Width="50"></TextBox>
<TextBox Text="{Binding Email}" Width="100"></TextBox>
<DataGrid ItemsSource="{Binding Users}"></DataGrid>
</code></pre>
<p>中进行了数据绑定,对应于图中的这一部分:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240528094205054.png"></p>
<p>现在来看看命令绑定。</p>
<pre><code class="language-xaml"> <Button Content="添加"
Command="{Binding AddUserCommand }"></Button>
</code></pre>
<p>进行了命令绑定,对应于图中这一部分:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240528094348003.png"></p>
<p>现在先来看看效果:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/MVVM%E5%AE%9E%E7%8E%B0%E6%95%88%E6%9E%9C2.gif"></p>
<p>现在已经实现了与前面基于事件驱动同样的效果,但是上面那张图中的Send Notifications还没有体现。</p>
<p>Send Notifications表示ViewModel中的更改会通知View。</p>
<p>现在我们来以一个例子说明一下Send Notifications是如何实现的。</p>
<p>首先添加一个测试命令:</p>
<pre><code class="language-csharp"> public ICommand TestCommand { get; set; }
</code></pre>
<p>在构造函数中添加:</p>
<pre><code class="language-csharp"> TestCommand = new RelayCommand(Test, CanTest);
</code></pre>
<p>实现Test与CanTest方法:</p>
<pre><code class="language-csharp">private bool CanTest(object obj)
{
return true;
}
private void Test(object obj)
{
Name = "小1";
Email = "111@qq.com";
}
</code></pre>
<p>View中修改如下:</p>
<pre><code class="language-xaml"> <Button Content="测试"
Command="{Binding TestCommand }"></Button>
</code></pre>
<p>现在去尝试,我们会发现没有效果,原因是我们的ViewModel没有实现<code>INotifyPropertyChanged</code>接口。</p>
<p><strong>INotifyPropertyChanged接口介绍</strong></p>
<p>在WPF(Windows Presentation Foundation)中,<code>INotifyPropertyChanged</code>接口用于实现数据绑定中的属性更改通知。当绑定到UI元素的数据源中的属性值发生更改时,<code>INotifyPropertyChanged</code>接口可以通知UI元素更新。</p>
<p><code>INotifyPropertyChanged</code>接口只定义了一个事件:<code>PropertyChanged</code>。当属性值发生更改时,应触发此事件。事件参数<code>PropertyChangedEventArgs</code>包含更改的属性的名称。</p>
<p>现在我们的MainViewModel实现一下INotifyPropertyChanged接口,如下所示:</p>
<pre><code class="language-csharp"> 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));
}
}
</code></pre>
<p>现在再尝试一下,会发现ViewModel中的更改会成功通知View了,如下所示:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/MVVM%E5%AE%9E%E7%8E%B0%E6%95%88%E6%9E%9C3.gif"></p>
<p>对应于图中的这一部分:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240528101649982.png"></p>
<p>现在我们来看看ViewModel—Model。</p>
<h3 id="viewmodelmodel">ViewModel—Model</h3>
<p>现在我们来看看ViewModel与Model之间的关系,可以根据下面这张图进行理解:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240528101926207.png"></p>
<p><strong>Model(模型)</strong>:Model代表了业务逻辑和数据。它包含了应用程序中的数据和对数据的操作,例如,从数据库中获取数据,或者向数据库中添加数据。Model是独立于UI的,它不知道UI的存在。</p>
<p><strong>ViewModel(视图模型)</strong>:ViewModel是Model和View之间的桥梁。它包含了View所需的数据(这些数据来自于Model),并提供了命令以响应View上的用户操作。ViewModel将Model的数据转换为View可以显示的数据,同时,它也将View上的用户操作转换为对Model的操作。</p>
<p>在我们这个例子中我们的数据来源于Model文件夹下的User类与UserManager类:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240528103959230.png"></p>
<p>这里的Send Notifications又该如何理解呢?</p>
<p>我们也是以一个小例子进行说明。</p>
<p>首先将ViewModel中的Test方法修改为:</p>
<pre><code class="language-csharp"> private void Test(object obj)
{
Users.Name = "小1";
Users.Email = "111@qq.com";
}
</code></pre>
<p>会发现现在并不会发送通知,实现View上的修改,这是因为User类并没有实现INotifyPropertyChanged接口,现在修改User类实现INotifyPropertyChanged接口:</p>
<pre><code class="language-csharp">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));
}
}
</code></pre>
<p>现在可以实现通知了,效果如下所示:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/MVVM%E5%AE%9E%E7%8E%B0%E6%95%88%E6%9E%9C4.gif"></p>
<h2 id="使用mvvm库">使用MVVM库</h2>
<p>我们会发现如果全部都手动实现MVVM模式的话,代码有点多,有点麻烦。这时候就可以使用一些MVVM库来简化我们的操作。</p>
<p>这里以CommunityToolkit.Mvvm为例,进行说明。</p>
<p><strong>CommunityToolkit.Mvvm介绍</strong></p>
<p><code>CommunityToolkit.Mvvm</code>是Microsoft Community Toolkit的一部分,它是一个轻量级但功能强大的MVVM(Model-View-ViewModel)库,旨在帮助开发者更容易地实现MVVM设计模式。</p>
<p>该库提供了一些基础类,如<code>ObservableObject</code>和<code>ObservableRecipient</code>,这些类实现了<code>INotifyPropertyChanged</code>接口,并提供了<code>SetProperty</code>方法,可以在属性值改变时触发<code>PropertyChanged</code>事件。这使得数据绑定变得更加简单和高效。</p>
<p>此外,该库还提供了<code>ICommand</code>接口的实现,如<code>RelayCommand</code>和<code>AsyncRelayCommand</code>,这些类可以帮助你创建命令,命令是MVVM模式中的一个重要组成部分。</p>
<p><code>CommunityToolkit.Mvvm</code>还提供了一些其他有用的特性,如消息传递、设计时数据支持等,这些特性可以帮助你更好地组织和管理你的代码。</p>
<p><code>CommunityToolkit.Mvvm</code>是一个强大的工具,它可以帮助你更容易地实现MVVM模式,从而提高你的代码质量和开发效率。</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240528112612211.png"></p>
<p>修改之后的ViewModel如下所示:</p>
<pre><code class="language-csharp"> public partial class MainViewModel : ObservableObject
{
public ObservableCollection<User> Users { get; set; }
private string? name;
private string? email;
public MainViewModel()
{
Users = UserManager.GetUsers();
}
private void Test(object obj)
{
Users.Name = "小1";
Users.Email = "111@qq.com";
}
private void AddUser(object obj)
{
User user = new User();
user.Name = Name;
user.Email = Email;
UserManager.AddUser(user);
}
}
</code></pre>
<p>修改之后的User类如下所示:</p>
<pre><code class="language-csharp"> public partial class User : ObservableObject
{
private string? _name;
private string? _email;
}
</code></pre>
<p>用到了<code>CommunityToolkit.Mvvm</code>库中的三个东西,分别是ObservableObject、与。</p>
<p>先来看一下ObservableObject。</p>
<p><code>ObservableObject</code>是<code>CommunityToolkit.Mvvm</code>库中的一个基础类,它实现了<code>INotifyPropertyChanged</code>接口。这个接口是.NET数据绑定基础架构的一部分,当对象的一个属性改变时,它会通知绑定到该属性的任何元素。</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240528120932293.png"></p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240528121004023.png"></p>
<p>具体见:ObservableObject - Community Toolkits for .NET | Microsoft Learn</p>
<p>在这里我们使用</p>
<pre><code class="language-csharp">
private string? name;
</code></pre>
<p>它将生成一个像这样的可观察属性:</p>
<pre><code class="language-csharp">public string? Name
{
get => name;
set => SetProperty(ref name, value);
}
</code></pre>
<p>具体见:ObservableProperty attribute - Community Toolkits for .NET | Microsoft Learn</p>
<p>我们使用</p>
<pre><code class="language-csharp">
private void AddUser(object obj)
{
User user = new User();
user.Name = Name;
user.Email = Email;
UserManager.AddUser(user);
}
</code></pre>
<p>代码生成器会生成一个命令如下所示:</p>
<pre><code class="language-csharp">private RelayCommand? addUserCommand;
public IRelayCommand AddUserCommand => addUserCommand ??= new RelayCommand(AddUser);
</code></pre>
<p>具体见:RelayCommand attribute - Community Toolkits for .NET | Microsoft Learn</p>
<p>现在我们的ViewModel与Model就可以简化了,现在再来看看效果:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/MVVM%E5%AE%9E%E7%8E%B0%E6%95%88%E6%9E%9C5.gif"></p>
<h2 id="总结">总结</h2>
<p>本文先总体介绍了一下MVVM模式,关于MVVM模式可以根据这张图帮助理解:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240527095704838.png"></p>
<p>由于很多同学可能与我一样,是从winform到wpf的,因此在wpf中使用winform中的事件驱动编程范式完成了一个小例子,关于事件驱动编程,可以根据这张图帮助理解:</p>
<p><img src="https://mingupupup.oss-cn-wuhan-lr.aliyuncs.com/imgs/image-20240527100814935.png"></p>
<p>由于这种模式耦合比较多,我们想要松耦合,因此开始学习MVVM模式。我们创建了实现<code>ICommand</code>接口的<code>RelayCommand</code>类,实现<code>INotifyPropertyChanged</code>接口的<code>MainViewModel</code>类与<code>User</code>类。使用数据绑定与命令绑定改写xaml页面。</p>
<p>最后由于手动实现MVVM模式,需要写很多代码,看过去比较复杂与麻烦,我们可以使用MVVM库来简化MVVM模式的实现。</p>
<p>以上,就是本次分享的全部内容,希望对正在学习wpf中mvvm模式的同学有所帮助,如果有什么不对的地方,恳请批评指正,共同进步!</p>
<h2 id="参考">参考</h2>
<p>1、What is the MVVM pattern, What benefits does MVVM have? (youtube.com)</p>
<p>2、WPF MVVM Tutorial: Build An App with Data Binding and Commands (youtube.com)</p>
<p>3、Model-View-ViewModel - .NET | Microsoft Learn</p>
<p>4、Introduction to the MVVM Toolkit - Community Toolkits for .NET | Microsoft Learn</p><br><br>
来源:https://www.cnblogs.com/mingupupu/p/18218027
頁:
[1]