WPF_MVVM简述

MVVM即Model-View-ViewModel

  • Model:Class(实体类)
  • View:UI(User Interface)
  • ViewModel:Model for View(逻辑代码)

MVVM模式实例

Command

internal class DelegateCommand : ICommand
    {
        //命令可执行性发生变化时,发送通知
        public event EventHandler? CanExecuteChanged;
        //判断委托执行函数
        public bool CanExecute(object? parameter)
        {
            //未设置判断委托则默认可执行
            if (this.CanExecuteFunc == null)
            {
                return true;
            }
            return this.CanExecute(parameter);
        }
        //执行委托执行函数
        public void Execute(object? parameter)
        {
            //未设置执行委托则默认不执行
            if (this.ExecuteAction == null)
            {
                return;
            }
            this.ExecuteAction(parameter);
        }
        //创建委托属性(执行委托 与 判断委托)
        public Action<object> ExecuteAction { get; set; }
        public Func<object,bool> CanExecuteFunc { get; set; }
    }

Model层

根据需要添加实体类(此处仅演示MVVM模式,不涉及数据库)

View层

View层的前端代码中,Button通过 Command="{Binding 命令属性}" 形式与后端的命令属性绑定,TextBox、TextBlock、Slider等通过 Text="{Binding 数据属性}" 形式与数据属性绑定

<Window x:Class="WPF_MVVM.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"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Button Command="{Binding AddCommand}" Content="Add" HorizontalAlignment="Center" Margin="0,300,0,0" VerticalAlignment="Top" Height="71" Width="204"/>
        <TextBox Text="{Binding Input1}" x:Name="tb1" HorizontalAlignment="Center" Margin="0,89,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="740" Height="65"/>
        <TextBox Text="{Binding Input2}" x:Name="tb2" HorizontalAlignment="Center" TextWrapping="Wrap" VerticalAlignment="Top" Width="740" Height="65" Margin="0,159,0,0"/>
        <TextBox Text="{Binding Result}" x:Name="tb3"  HorizontalAlignment="Center" Margin="0,229,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="740" Height="65"/>
        <Button Command="{Binding SaveCommand}" Content="Save" HorizontalAlignment="Center" Margin="0,10,0,0" VerticalAlignment="Top" Height="71" Width="204"/>

    </Grid>
</Window>

View层的后端代码中,需要指定前端的DataContext,即ViewModel

public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new MainWindowViewModel();
        }
    }

ViewModel层

基类:实现INotifyPropertyChanged接口,并定义函数触发PropertyChanged事件
RaisePropertyChanged函数被调用时触发PropertyChanged事件,事件会将新的属性数据与原先的属性数据进行比对,若数据确实发生变化,则将新的数据传递至界面

//ViewModel基类
    internal class NotificationObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler? PropertyChanged;

        //函数被调用时触发PropertyChanged事件
        //事件会将新的属性数据与原先的属性数据进行比对,若数据确实发生变化,则将新的数据传递至界面
        public void RaisePropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

Model for View:实现基类,定义属性调用RaisePropertyChanged函数

internal class MainWindowViewModel:NotificationObject
    {
		//数据属性
		private int input1;

		public int Input1
		{
			get { return input1; }
			set
			{
				input1 = value;
				//通知关联在Input1上的Binding,其关注的属性的值已发生改变
				this.RaisePropertyChanged("Input1");
			}
		}

		private int input2;

		public int Input2
		{
			get { return input2; }
			set
			{
				input2 = value;
				this.RaisePropertyChanged("Input2");
			}
		}

		private int result;

		public int Result
		{
			get { return result; }
			set
			{
				result = value;
				this.RaisePropertyChanged("Result");
			}
		}

        //命令属性
        public DelegateCommand AddCommand { get; set; }
        public DelegateCommand SaveCommand { get; set; }

		private void Add(object parameter)
		{
			this.Result = this.Input1 + this.Input2;
		}
		private void Save(object parameter)
		{
			SaveFileDialog dialog = new SaveFileDialog();
			dialog.ShowDialog();
		}

        public MainWindowViewModel()
        {
			this.AddCommand = new DelegateCommand();
			this.AddCommand.ExecuteAction = new Action<object>(this.Add);

			this.SaveCommand = new DelegateCommand();
			this.SaveCommand.ExecuteAction = new Action<object>(this.Save);
        }
    }

综上所述,可知:前端需求发生改变时,仅需修改前端代码,并重新绑定后端属性即可,后端代码则无需改动,因为从始至终后端对前端都只字未提,只是前端单方面地在对后端进行绑定。

此处举反例,即不使用MVVM模式下的后端代码:

        private void Button_Click_Save(object sender, RoutedEventArgs e)
        {
            SaveFileDialog dialog = new SaveFileDialog();
            dialog.ShowDialog();
        }

        private void Button_Click_Add(object sender, RoutedEventArgs e)
        {
            //TextBox中的数据为String类型,进行计算前需要格式转换
            try
            {
                double num1 = double.Parse(this.tb1.Text);
                double num2 = double.Parse(this.tb2.Text);
                double result = num1 + num2;
                this.tb3.Text = result.ToString();
            }
            catch (Exception)
            {
                MessageBox.Show("数据有误,重新输入");
                throw;
            }
        }

此时,后端直接通过前端的Name属性操作数据,倘若前端需求发生改变,则不仅前端代码需要修改,后端代码亦然