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属性操作数据,倘若前端需求发生改变,则不仅前端代码需要修改,后端代码亦然