MVVM 设计模式

本篇文章学习于: 刘铁猛老师《深入浅出WPF》

什么是MVVM模式?

MVVM的全称是——Model、View、ViewModel,翻译过来就是:模型、视图、视图模型。
ViewModel是比较抽象的,它起到承上启下的作用,用于处理业务逻辑。
每一个View都需要有对应的Model和ViewModel。
ViewModel与View的沟通:A.传递数据——数据属性 B.传递操作——命令属性

为什么要使用MVVM模式?

该模式最大的优点就是将UI和业务逻辑进行剥离,使项目高内聚低耦合。美工和后端开发人员可以同时开工,页面修改不会影响到后台的业务逻辑,方便了项目后期的维护。

  • 团队层面:统一思维方式和实现方法
  • 架构层面:稳定,解耦,富有禅意
  • 代码层面:可读,可测,可替换

什么时候用MVVM模式?

如果你只需要显示一句“Hello World”,使用该模式会令你抓狂。如果你是开发一个正儿八经的WPF应用,并且该应用后期会进行功能扩展,维护等操作。那么建议使用MVVM模式开发WPF应用。

MVVM 示例

使用了MVVM模式的程序工作流程图

1. 不使用MVVM实现程序

<Window x:Class="Demo9.WpfMVVM设计模式.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:Demo9.WpfMVVM设计模式"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Button Content="Save" Click="saveButton_Click"/>
        <Grid Grid.Row="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <TextBox x:Name="tb1" Grid.Row="0" Background="LightBlue" FontSize="24" Margin="4" />
            <TextBox x:Name="tb2" Grid.Row="1" Background="LightBlue" FontSize="24" Margin="4" />
            <TextBox x:Name="tb3" Grid.Row="2" Background="LightBlue" FontSize="24" Margin="4" />
            <Button x:Name="addButton" Grid.Row="3" Content="Add" Width="120" Height="80" Click="addButton_Click"/>
        </Grid>
    </Grid>
</Window>

image.png

namespace Demo9.WpfMVVM设计模式 {
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
        }

        private void addButton_Click(object sender, RoutedEventArgs e) {
            double num1 = double.Parse(tb1.Text);
            double num2 = double.Parse(tb2.Text);
            double re = num1 + num2;
            tb3.Text = re.ToString();
        }

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

2. 客户要改需求

如果这时候客户不想要文本输入的方式来实现功能,想要以滑动的方式使用加法器。
如下:

<Window x:Class="Demo9.WpfMVVM设计模式.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:Demo9.WpfMVVM设计模式"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Menu>
            <MenuItem Header="_File">
                <MenuItem Header="_Save" Click="saveButton_Click" />
            </MenuItem>
        </Menu>
        <Grid Grid.Row="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <Slider x:Name="slider1" Grid.Row="0" Background="LightBlue" Minimum="-100" Maximum="100" Margin="4" />
            <Slider x:Name="slider2" Grid.Row="1" Background="LightBlue" Minimum="-100" Maximum="100" Margin="4" />
            <Slider x:Name="slider3" Grid.Row="2" Background="LightBlue" Minimum="-100" Maximum="100" Margin="4" />
            <Button x:Name="addButton" Grid.Row="3" Content="Add" Width="120" Height="80" Click="addButton_Click" />
        </Grid>
    </Grid>
</Window>

image.png

namespace Demo9.WpfMVVM设计模式 {
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
        }

        private void addButton_Click(object sender, RoutedEventArgs e) {
            double num1 = slider1.Value;
            double num2 = slider1.Value;
            double re = num1 + num2;
            slider3.Value = re;
        }

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

虽然这样我们就实现了,但是我们不仅改了界面还改了后台逻辑,很麻烦。万一客户又变一种样式,改的很烦这样就。

3. MVVM设计模式出场

那么,可不可以在用户需求变更的时候,在界面频繁变更的时候,让我们改代码不这么痛苦。
尽可能:更改界面是开放的,逻辑代码变更是闭合的,那么我们就使用MVVM设计模式。

首先把界面恢复到最开始的状态

<Window x:Class="Demo9.WpfMVVM设计模式.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:Demo9.WpfMVVM设计模式"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Button Content="Save" />
        <Grid Grid.Row="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <TextBox x:Name="tb1" Grid.Row="0" Background="LightBlue" FontSize="24" Margin="4" />
            <TextBox x:Name="tb2" Grid.Row="1" Background="LightBlue" FontSize="24" Margin="4" />
            <TextBox x:Name="tb3" Grid.Row="2" Background="LightBlue" FontSize="24" Margin="4" />
            <Button x:Name="addButton" Grid.Row="3" Content="Add" Width="120" Height="80" />
        </Grid>
    </Grid>
</Window>

image.png

namespace Demo9.WpfMVVM设计模式 {
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
        }
    }
}

4. 使用MVVM模式

  • NotificationObject与数据属性
  • DelegateCommand与命令属性
  • View与ViewMode的交互(技术难点)

A. 建立几个文件夹

image.png

B. 创建 NotificationObject

在ViewModels文件夹下创建 具有通知能力对象 的这么一个类,所有ViewModel类的基类
Binding通过监听这个事件属性来更新绑定UI的控件的值。

namespace Demo9.WpfMVVM设计模式.ViewModels {
    internal class NotificationObject : INotifyPropertyChanged {
        public event PropertyChangedEventHandler PropertyChanged;

        public void RaisePropertyChanged(string propertyName) {
            if (PropertyChanged != null) {
                PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

C. 创建DelegateCommand

在项目里,新建一个文件夹Commands,再在该文件夹下创建 DelegateCommand类

namespace Demo9.WpfMVVM设计模式.Commands {
    internal class DelegateCommand : ICommand {
        public event EventHandler CanExecuteChanged;

        public bool CanExecute(object parameter) {
            if (this.CanExecuteFunc == null)
                return true;
            return this.CanExecuteFunc(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; }
    }
}

D. 给View建模

ViewModel的本质就是对应的View的建模
这里示例的本质就是:
2个值让用户可以输入,1个值给用户显示输出,1个命令让后台进行加法计算,还有另外1个命令可以做文件保存
——3个数据属性,2个命令属性

这里创建MainWindow对应的ViewModel的MainWindowsViewModel类

namespace Demo9.WpfMVVM设计模式.ViewModels {
    internal class MainWindowViewModel : NotificationObject {
        private double input1;
        public double Input1 {
            get { return input1; }
            set {
                input1 = value;
                this.RaisePropertyChanged("Input1");
            }
        }

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

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

        public DelegateCommand AddCommand { get; set; }
    
        private void Add(object parameter) {
            this.Result = this.Input1 + this.Input2;
        }

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

再在MainWindow.xaml文件下添加Binding
image.png
在MainWindow.xaml.cs添加DataContext
image.png
运行:成功实现!!!
image.png

E. 再添加 保存文件 这个命令

namespace Demo9.WpfMVVM设计模式.ViewModels {
    internal class MainWindowViewModel : NotificationObject {

        private double input1;
        public double Input1 {
            get { return input1; }
            set {
                input1 = value;
                this.RaisePropertyChanged("Input1");
            }
        }

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

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

        public DelegateCommand AddCommand { get; set; }
        private void Add(object parameter) {
            this.Result = this.Input1 + this.Input2;
        }

        public DelegateCommand SaveFileCommand { get; set; }
        private void Save(object parameter) {
            SaveFileDialog sfd = new SaveFileDialog();
            sfd.ShowDialog();
        }

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

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

再在MainWindow.xaml添加保存文件的Binding
image.png
运行,成功实现!
image.png

F. 修改成滑块的界面实现该功能

这时候客户不想要文本输入的方式来实现功能,想要以滑动的方式使用加法器。
可以看到只需要让UI控件重新绑定,后台代码没有修改。效果依然实现。

<Window x:Class="Demo9.WpfMVVM设计模式.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:Demo9.WpfMVVM设计模式"
  mc:Ignorable="d"
  Title="MainWindow" Height="450" Width="800">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Menu>
      <MenuItem Header="_File">
        <MenuItem Header="_Save" Command="{Binding SaveFileCommand}" />
      </MenuItem>
    </Menu>
    <Grid Grid.Row="1">
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
      </Grid.RowDefinitions>
      <Slider x:Name="slider1" Value="{Binding Input1}" Grid.Row="0" Background="LightBlue" Minimum="-100" Maximum="100" Margin="4" />
      <Slider x:Name="slider2" Value="{Binding Input2}" Grid.Row="1" Background="LightBlue" Minimum="-100" Maximum="100" Margin="4" />
      <Slider x:Name="slider3" Value="{Binding Result}" Grid.Row="2" Background="LightBlue" Minimum="-100" Maximum="100" Margin="4" />
      <Button x:Name="addButton" Command="{Binding AddCommand}" Grid.Row="3" Content="Add" Width="120" Height="80" />
    </Grid>
  </Grid>
</Window>

image.png
可以看到只需要重新绑定,后台代码没有修改。效果依然实现。

posted @ 2023-05-27 17:10  不爱菠萝的菠萝君  阅读(526)  评论(0)    收藏  举报