介绍
MVVM是Model-View-ViewModel的简写。它本质上就是MVC的改进版。MVVM模式有助于将应用程序的业务和表示逻辑与用户界面 (UI) 清晰分离。 保持应用程序逻辑和UI之间的清晰分离有助于解决许多开发问题,并使应用程序更易于测试、维护和演变。 它还可以显著提高代码重用机会,并允许开发人员和UI设计人员在开发应用各自的部分时更轻松地进行协作。
组成部分
MVVM模式,应用的UI以及基础表示和业务逻辑被分成三个独立的类:
视图(View),用于封装UI和UI逻辑, 也就是交互界面,接受用户的输入和展示数据;
视图模型(ViewModel),用于封装表示逻辑和状态;
模型(Model),用于封装应用的业务逻辑和数据。
优势
低耦合:View可以独立于Model变化和修改,一个ViewModel可以绑定到不同的”View”上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
提高代码重用:可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑;
独立开发:业务开发人员可以专注于业务逻辑和数据的开发,界面开发人员可以专注于页面的开发。
使用场景
以下是MVVM架构的应用场景:
场景 | 描述 |
复杂的用户界面 | 当应用程序具有复杂的用户界面和大量的交互时,MVVM可以提供更好的分层和组织代码的方式。 |
需要频繁变更的用户界面 | 如果应用程序的用户界面需要频繁变更,MVVM的数据绑定机制可以简化界面更新的过程,提高开发效率。 |
需要同时支持多个平台或设备的应用程序 | MVVM的解耦性和可测试性使其非常适合开发需要在多个平台或设备上运行的应用程序,如移动应用和桌面应用。 |
需要重用代码和逻辑的应用程序 | MVVM的分离关注点和数据绑定机制使得代码和逻辑的重用更加容易,从而减少了代码的重复编写。 |
需要高可维护性和可扩展性的应用程序 | MVVM的分层结构和清晰的职责分离使得应用程序更易于维护和扩展,有利于团队合作和长期项目的发展。 |
搭建MVVM架构
1、首先需要新建一个项目,再创建Views,ViewModels,Models 三个文件夹,就是一个基本架构。
2、将我们写的xaml页面放在Views目录下,一般就是命名为:xxxView,然后与之对应的视图模型命令为xxxViewModel放在ViewModels目录下,另外将需要的数据模型放置在Models目录下。
3、再者需要创建一些辅助类的文件目录,一般会创建Resources用于存放一些资源如样式,字体,图片等等,创建一个Helper或者Common的目录,用于存放一些公用的方法类或者帮助类。
实现INotifyPropertyChanged接口
实现属性变更的通知 实现页面指令
public class UserViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private User user; // 私有的用户对象
public string Name {
get { return user.Name; }
set {
user.Name = value;
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
public int Age {
get { return user.Age; }
set {
user.Age = value;
PropertyChanged(this, new PropertyChangedEventArgs("Age"));
}
}
}
实现ICommand接口命令
自定义指令
public class MyCommand : ICommand
{
//1 事件成员 状态发生变化的时候事件,
public event EventHandler CanExecuteChanged;
//2 命令是否是可执行状态 参数就是xaml文件通过Commandparameter属性传递过来
public bool CanExecute(object parameter)
{
if (NengBuNengZhiXing == null) return true; //如果外部没有控制状态的函数,默认指令可执行
// 如果有指令能不能执行的函数,NengBuNengZhiXing返回值为true或者false
return NengBuNengZhiXing.Invoke(parameter);
}
//3 命令触发之后执行函数 参数就是xaml文件通过Commandparameter属性传递过来
public void Execute(object parameter)
{
this.ChuFaMingLingFun.Invoke(parameter);
}
// 4
//定义委托接受命令触发之后的函数 作为Execute要调用的
Action<object> ChuFaMingLingFun;
//接受判断命令是否能够执行的函数 ,作为CanExecute的判断条件
Func<object, bool> NengBuNengZhiXing;
//5 给上面俩个变量赋值 通过构造函数进行赋值
public MyCommand(Action<object> a1, Func<object, bool> a2)
{
this.ChuFaMingLingFun = a1;
this.NengBuNengZhiXing = a2;
}
//6 如果可执行的状态的发生变化了 触发CanExecuteChanged事件 在外部修改状态的时候调用
public void OnCanExecuteChange()
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
使用ICommand时参数的传递和接收
传递
传递参数时在UI界面中使用 CommandParamete属性进行传递
传递字符串属性
<Button Height="40" Command="{Binding M1}"
CommandParameter="Red"></Button>
传递对象属性(传递控件)
RelativeSource传递方式
RelativeSource方式可以绑定父级控件进行传递 祖先:FindAncestor
<Button Width="100"
Command="{Binding Cancel}"
<!--传递对象参数Windows窗体-->
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
Height="40"
Margin="200,100,0,0">取消</Button>
ElementName传递方式
FindAncestor方式可以绑定指定控件的Name属性进行传递
<Button Name="B">保存</Button>
<Button Width="100"
Command="{Binding Cancel}"
CommandParameter="{Binding ElementName=B}"
Height="40"
Margin="200,100,0,0">取消</Button>
接收参数
接收参数时,是在IComand命令类中的条件函数中和回调函数中接收
接收的类型对应着传递的类型
public bool CanExecute(object parameter)
{
if (CanWork == null) return true;
return CanWork.Invoke(parameter);
}
public void Execute(object parameter)
{
this.Work.Invoke(parameter);
}
ViewModel视图模型中逻辑和状态,
场景:
INotifyPropertyChanged接口和ICommand接口命令搭配使用,绑定指令和接收指令参数,展示模型数据。
UI窗体页面 标签布局
<Grid>
<!--Name-->
<TextBox Text="{Binding Name}" Width="200" Height="40" Margin="-200,0,0,0" ></TextBox>
<!--Age-->
<TextBox Text="{Binding Age}"
Width="200"
Height="40"
Margin="-200,100,0,0"></TextBox>
<TextBox Text="{Binding Info}"
Width="200"
Height="40"
Margin="-200,200,0,0"></TextBox>
<Button Width="100"
Command="{Binding Save}"
Height="40"
Margin="200,0,0,0">保存</Button>
<Button Width="100"
Command="{Binding Cancel}"
<!--传递对象参数Windows窗体-->
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
Height="40"
Margin="200,100,0,0">取消</Button>
</Grid>
在Model模型文件创建存储数据的类
public class User:INotifyPropertyChanged
{
private string _name = "张三";
public string Name
{
get => _name;
set
{
_name = value;
PropertyChanged(this,new PropertyChangedEventArgs("Name"));
}
}
private int _age ;
public int Age
{
get => _age;
set
{
_age = value;
PropertyChanged(this, new PropertyChangedEventArgs("Age"));
}
}
public event PropertyChangedEventHandler? PropertyChanged;
}
在ViewModel文件实现后台视图模型逻辑
public class UserViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private User user; // 私有的用户对象(模型类对象)
public string Name
{
get { return user.Name; }
set
{
user.Name = value;
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
public int Age
{
get { return user.Age; }
set
{
user.Age = value;
PropertyChanged(this, new PropertyChangedEventArgs("Age"));
}
}
public string Info
{
get { return $"Name:{Name} Age:{Age}"; }
set { Info = value; }
}
public void OnpropertyChanged(string n1)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(n1));
}
}
// 指令的使用,在对应viewmodel的构造函数中进行使用
public UserViewModel()
{
user = new User();
Save = new MyCommand(BaoCun); // 保存的指令
Cancel = new MyCommand(QuXiao); // 取消指令
}
public MyCommand Save { get;private set ; }
public MyCommand Cancel { get;private set ; }
public void BaoCun(object o)
{
MessageBox.Show("保存数据");
// 数据变化进行
OnpropertyChanged("Info");
}
public void QuXiao(object o)
{
// 关闭窗体
var window = o as Window;
if (window != null)
{
window.Close();
}
}
}
public class MyCommand : ICommand
{
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter)
{
if (f1 == null) return true;
return f1.Invoke(parameter) == true;
}
public void Execute(object? parameter)
{
a1.Invoke(parameter);
}
Action<object> a1; // 在Execute去调用
Func<object,bool> f1; // 在CanExecute去调用
// 在构造函数里面给a1和f1赋值
public MyCommand(Action<object> a1) : this(a1,null)
{
this.a1 = a1;
}
// 可以传递俩个参数,MyCommand(f1,f2);
public MyCommand(Action<object> a1,Func<object,bool> f1)
{
this.a1 = a1;
this.f1 = f1;
}
// 当修改指令状态时候 需要手动调用该函数
public void OnCanExeutedChanged()
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
在Windows后台cs中进行数据绑定
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new UserViewModel();
}
}