MVVM 设计模式
本篇文章学习于: 刘铁猛老师《深入浅出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>
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
· 面试官:你是如何进行SQL调优的?