本篇文章学习于: 刘铁猛老师《深入浅出WPF》
什么是MVVM模式?
MVVM的全称是——Model、View、ViewModel,翻译过来就是:模型、视图、视图模型。
ViewModel是比较抽象的,它起到承上启下的作用,用于处理业务逻辑。
每一个View都需要有对应的Model和ViewModel。
ViewModel与View的沟通:A.传递数据——数据属性 B.传递操作——命令属性
为什么要使用MVVM模式?
该模式最大的优点就是将UI和业务逻辑进行剥离,使项目高内聚低耦合。美工和后端开发人员可以同时开工,页面修改不会影响到后台的业务逻辑,方便了项目后期的维护。
- 团队层面:统一思维方式和实现方法
- 架构层面:稳定,解耦,富有禅意
- 代码层面:可读,可测,可替换
什么时候用MVVM模式?
如果你只需要显示一句“Hello World”,使用该模式会令你抓狂。如果你是开发一个正儿八经的WPF应用,并且该应用后期会进行功能扩展,维护等操作。那么建议使用MVVM模式开发WPF应用。
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>
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>
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>
namespace Demo9.WpfMVVM设计模式 {
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
}
}
4. 使用MVVM模式
- NotificationObject与数据属性
- DelegateCommand与命令属性
- View与ViewMode的交互(技术难点)
A. 建立几个文件夹
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
在MainWindow.xaml.cs添加DataContext
运行:成功实现!!!
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
运行,成功实现!
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>
可以看到只需要重新绑定,后台代码没有修改。效果依然实现。