翼度科技»论坛 编程开发 .net 查看内容

深入理解WPF中MVVM的设计思想

7

主题

7

帖子

21

积分

新手上路

Rank: 1

积分
21
近些年来,随着WPF在生产,制造,工业控制等领域应用越来越广发,很多企业对WPF开发的需求也逐渐增多,使得很多人看到潜在机会,不断从Web,WinForm开发转向了WPF开发,但是WPF开发也有很多新的概念及设计思想,如:数据驱动,数据绑定,依赖属性,命令,控件模板,数据模板,MVVM等,与传统WinForm,ASP.NET WebForm开发,有很大的差异,今天就以一个简单的小例子,简述WPF开发中MVVM设计思想及应用。

 
为什么要用MVVM?

 
传统的WinForm开发,一般采用事件驱动,即用户点击事件,触发对应的事件,并在事件中通过唯一标识符获取页面上用户输入的数据,然后进行业务逻辑处理。这样做会有一个弊端,就是用户输入(User Interface)和业务逻辑(Business)是紧密耦合在一起的,无法做到分离,随着项目的业务不断复杂化,这种高度耦合的弊端将会越来越明显。并且会出现分工不明确(如:后端工程师,前端UI),工作无法拆分的现象。所以分层(如:MVC,MVVM),可测试(Unit Test),前后端分离,就成为必须要面对的问题。而今天要讲解的MVVM设计模式,就非常好的解决了我们所面临的问题。
 
什么是MVVM?

 
MVVM即模型(Model)-视图(View)-视图模型(ViewModel) ,是用于解耦 UI 代码和非 UI 代码的 设计模式。 借助 MVVM,可以在 XAML 中以声明方式定义 UI,将 UI使用数据绑定标到包含数据和命令的其他层。 数据绑定提供数据和结构的松散耦合,使 UI 和链接的数据保持同步,同时可以将用户输入路由到相应的命令。具体如下图所示:

如上图所示:

  • View(用户页面),主要用于向使用者展示信息,并接收用户输入的信息(数据绑定),及响应用户的操作(Command)。
  • ViewModel(用户视图业务逻辑),主要处理客户请求,以及数据呈现。
  • Model数据模型,作为存储数据的载体,是一个个的具体的模型类,通过ViewModel进行调用。但是在小型项目中,Model并不是必须的
  • IService(数据接口),数据访问服务,用于获取各种类型数据的服务。数据的形式有很多种,如网络数据,本地数据,数据库数据,但是在ViewModel调用时,都统一封装成了Service。在小型项目中,IService数据接口也并不是必须的,不属于MVVM的范畴
  • 在上图中,DataBase,Network,Local等表示不同的数据源形式,并不属于MVVM的范畴。
 
前提条件

 
要实现MVVM,首先需要满足两个条件:

  • 属性变更通知,在MVVM思想中,由WinForm的事件驱动,转变成了数据驱动。在C#中,普通的属性,并不具备变更通知功能,要实现变更通知功能,必须要实现INotifyPropertyChanged接口。
  • 绑定命令,在WPF中,为了解决事件响应功能之间的耦合,提出了绑定命令思想,即命令可以绑定的方式与控件建立联系。绑定命令必须实现ICommand接口。
在上述两个条件都满足后,如何将ViewModel中的具备变更通知的属性和命令,与View中的控件关联起来呢?答案就是绑定(Binding)

当View层的数据控件和具备通知功能的属性进行Binding后,Binging就会自动侦听来自接口的PropertyChanged事件。进而达到数据驱动UI的效果,可谓【一桥飞架南北,天堑变通途】。
 
MVVM实例

 
为了进一步感受MVVM的设计思想,验证上述的理论知识,以实例进行说明。本实例的项目架构如下所示:

 
MVVM核心代码

 
1. 具备通知功能的属性

 
首先定义一个抽象类ObservableObject,此接口实现INotifyPropertyChanged接口,如下所示:
  1. using System.ComponentModel;
  2. using System.Runtime.CompilerServices;
  3. namespace DemoMVVM.Core
  4. {
  5.     /// <summary>
  6.     /// 可被观测的类
  7.     /// </summary>
  8.     public abstract class ObservableObject : INotifyPropertyChanged
  9.     {
  10.         /// <summary>
  11.         /// 属性改变事件
  12.         /// </summary>
  13.         public event PropertyChangedEventHandler? PropertyChanged;
  14.         /// <summary>
  15.         /// 属性改变触发方法
  16.         /// </summary>
  17.         /// <param name="propertyName">属性名称</param>
  18.         protected void RaisePropertyChanged([CallerMemberName]string propertyName=null)
  19.         {
  20.             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  21.         }
  22.         /// <summary>
  23.         /// 设置属性值,如果发生改变,则调用通知方法
  24.         /// </summary>
  25.         /// <typeparam name="T"></typeparam>
  26.         /// <param name="target"></param>
  27.         /// <param name="value"></param>
  28.         /// <param name="propertyName"></param>
  29.         /// <returns></returns>
  30.         protected bool SetProperty<T>(ref T target,T value, [CallerMemberName] string propertyName = null)
  31.         {
  32.             if (EqualityComparer<T>.Default.Equals(target, value))
  33.             {
  34.                 return false;
  35.             }
  36.             else
  37.             {
  38.                 target=value;
  39.                 RaisePropertyChanged(propertyName);
  40.                 return true;
  41.             }
  42.         }
  43.     }
  44. }
复制代码
注意:上述SetProperty主要用于将普通属性,变为具备通知功能的属性。
 
然后定义一个ViewMode基类,继承自ObservableObject,以备后续扩展,如下所示:
  1. namespace DemoMVVM.Core
  2. {
  3.     /// <summary>
  4.     /// ViewModel基类,继承自ObservableObject
  5.     /// </summary>
  6.     public abstract class ViewModelBase:ObservableObject
  7.     {
  8.     }
  9. }
复制代码
 
2. 具备绑定功能的命令

 
首先定义一个DelegateCommand,实现ICommand接口,如下所示:
  1. namespace DemoMVVM.Core
  2. {
  3.     public class DelegateCommand : ICommand
  4.     {
  5.         private Action<object> execute;
  6.         private Predicate<object> canExecute;
  7.         public event EventHandler? CanExecuteChanged;
  8.         public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
  9.         {
  10.             if (execute == null)
  11.             {
  12.                 throw new ArgumentNullException("execute 不能为空");
  13.             }
  14.             this.execute = execute;
  15.             this.canExecute = canExecute;
  16.         }
  17.         public DelegateCommand(Action<object> execute):this(execute,null)
  18.         {
  19.         }
  20.         public bool CanExecute(object? parameter)
  21.         {
  22.             return  canExecute?.Invoke(parameter)!=false;
  23.         }
  24.         public void Execute(object? parameter)
  25.         {
  26.             execute?.Invoke(parameter);
  27.         }
  28.     }
  29. }
复制代码
注意,DelegateCommand的构造函数,接收两个参数,一个是Execute(干活的),一个是CanExecute(判断是否可以干活的)
 
MVVM应用代码

 
本实例主要实现两个数的运算。如加,减,乘,除等功能。
首先定义ViewModel,继承自ViewModelBase,主要实现具备通知功能的属性和命令,如下所示:
  1. using DemoMVVM.Core;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Runtime;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. namespace DemoMVVM
  9. {
  10.     public class MainWindowViewModel:ViewModelBase
  11.     {
  12.         #region 属性及构造函数
  13.         private double leftNumber;
  14.                 public double LeftNumber
  15.                 {
  16.                         get { return leftNumber; }
  17.                         set { SetProperty(ref leftNumber , value); }
  18.                 }
  19.                 private double rightNumber;
  20.                 public double RightNumber
  21.                 {
  22.                         get { return rightNumber; }
  23.                         set { SetProperty(ref rightNumber , value); }
  24.                 }
  25.                 private double resultNumber;
  26.                 public double ResultNumber
  27.                 {
  28.                         get { return resultNumber; }
  29.                         set { SetProperty(ref resultNumber , value); }
  30.                 }
  31.                 public MainWindowViewModel()
  32.                 {
  33.                 }
  34.                 #endregion
  35.                 #region 命令
  36.                 private DelegateCommand operationCommand;
  37.                 public DelegateCommand OperationCommand
  38.                 {
  39.                         get {
  40.                                 if (operationCommand == null)
  41.                                 {
  42.                                         operationCommand = new DelegateCommand(Operate);
  43.                                 }
  44.                                 return operationCommand; }
  45.                 }
  46.                 private void Operate(object obj)
  47.                 {
  48.                         if(obj == null)
  49.                         {
  50.                                 return;
  51.                         }
  52.                         var type=obj.ToString();
  53.                         switch (type)
  54.                         {
  55.                                 case "+":
  56.                                         this.ResultNumber = this.LeftNumber + this.RightNumber;
  57.                                         break;
  58.                                 case "-":
  59.                     this.ResultNumber = this.LeftNumber - this.RightNumber;
  60.                     break;
  61.                                 case "*":
  62.                     this.ResultNumber = this.LeftNumber * this.RightNumber;
  63.                     break;
  64.                                 case "/":
  65.                                         if (this.RightNumber == 0)
  66.                                         {
  67.                                                 this.ResultNumber = 0;
  68.                                         }
  69.                                         else
  70.                                         {
  71.                                                 this.ResultNumber = this.LeftNumber / this.RightNumber;
  72.                                         }
  73.                     break;
  74.                         }
  75.                 }
  76.         #endregion
  77.     }
  78. }
复制代码
 创建视图,并在视图中进行数据绑定,将ViewModel和UI关联起来,如下所示:
  1. <Window x:
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5.         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6.         xmlns:local="clr-namespace:DemoMVVM"
  7.         mc:Ignorable="d"
  8.         Title="MVVM示例" Height="350" Width="600">
  9.     <Grid>
  10.         <Grid.ColumnDefinitions>
  11.             <ColumnDefinition></ColumnDefinition>
  12.             <ColumnDefinition></ColumnDefinition>
  13.             <ColumnDefinition Width="0.3*"></ColumnDefinition>
  14.             <ColumnDefinition></ColumnDefinition>
  15.         </Grid.ColumnDefinitions>
  16.         <Grid.RowDefinitions>
  17.             <RowDefinition></RowDefinition>
  18.             <RowDefinition></RowDefinition>
  19.             <RowDefinition></RowDefinition>
  20.             <RowDefinition></RowDefinition>
  21.         </Grid.RowDefinitions>
  22.         <StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal">
  23.             <TextBlock Text="A1:" VerticalAlignment="Center" ></TextBlock>
  24.             <TextBox  Margin="10" Width="120" Height="35" Text="{Binding LeftNumber, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"></TextBox>
  25.         </StackPanel>
  26.         <StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal">
  27.             <TextBlock Text="A2:" VerticalAlignment="Center" ></TextBlock>
  28.             <TextBox  Margin="10" Width="120" Height="35" Text="{Binding RightNumber, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"></TextBox>
  29.         </StackPanel>
  30.         <TextBlock Grid.Row="1" Grid.Column="2" Text="=" VerticalAlignment="Center" HorizontalAlignment="Center"></TextBlock>
  31.         <StackPanel Grid.Row="1" Grid.Column="3" Orientation="Horizontal">
  32.             <TextBlock Text="A3:" VerticalAlignment="Center" ></TextBlock>
  33.             <TextBox  Margin="10" Width="120" Height="35" Text="{Binding ResultNumber, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"></TextBox>
  34.         </StackPanel>
  35.         <StackPanel Grid.Row="2" Grid.ColumnSpan="4" Orientation="Horizontal" HorizontalAlignment="Center">
  36.             <Button Content="+" Width="100" Height="35" Margin="10" Command="{Binding OperationCommand}" CommandParameter="+"></Button>
  37.             <Button Content="-" Width="100" Height="35" Margin="10" Command="{Binding OperationCommand}" CommandParameter="-"></Button>
  38.             <Button Content="*" Width="100" Height="35" Margin="10" Command="{Binding OperationCommand}" CommandParameter="*"></Button>
  39.             <Button Content="/" Width="100" Height="35" Margin="10" Command="{Binding OperationCommand}" CommandParameter="/"></Button>
  40.         </StackPanel>                                               
  41.     </Grid>
  42. </Window>
复制代码
注意,在xaml前端UI代码中,分别对TextBox的Text和Button的Command进行了绑定,已达到数据驱动UI,以及UI响应客户的功能
在UI的构造函数中,将DataContext数据上下文和ViewModel进行关联,如下所示:
  1. namespace DemoMVVM
  2. {
  3.     /// <summary>
  4.     /// Interaction logic for MainWindow.xaml
  5.     /// </summary>
  6.     public partial class MainWindow : Window
  7.     {
  8.         private MainWindowViewModel viewModel;
  9.         public MainWindow()
  10.         {
  11.             InitializeComponent();
  12.             viewModel = new MainWindowViewModel();
  13.             this.DataContext = viewModel;
  14.         }
  15.     }
  16. }
复制代码
 
MVVM实例演示

 
通过以上步骤,已经完成了MVVM的简单应用。实例演示如下:

以上就是深入理解WPF中MVVM的设计思想的全部内容。希望可以抛砖引玉,一起学习,共同进步。


来源:https://www.cnblogs.com/hsiang/archive/2023/09/18/17709739.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具