文章目录
继续接着上篇讲解WPF中的MVVM模式,本文主要讲解的是视图模型(ViewModel First)优先的实现方式。
1.视图模型优先介绍
在上篇文章中我们讲到,视图优先(View First)就是视图负责创建视图模型,视图优先编程相对更容易一些,但是缺点就是视图和视图模型的耦合度比较高。而视图模型优先(ViewModel First)解决了耦合度的问题,可以将视图作为DataTemplate来重用,提高了代码复用。
2.视图模型优先实现
视图模型优先的原则是视图并不直接创建视图模型,视图模型直接在后台进行创建,由数据驱动前台界面更新,这显得更加优雅,要想实现视图模型优先,我们一般需要在前台xaml文件引入一个新的组件ContentControl:
2.1 ContentControl
ContentControl组件是WPF中一个非常重要的容器控件,它用于承载单个UI元素。ContentControl可以包含任意类型的UI元素,他的Content属性是依赖属性支持绑定,视图模型优先就是借助Content属性来完成视图模型的绑定。
2.2 实现代码
一般前台需要有一个ContentControl控件,该控件的Content属性应绑定至后台ViewModel的一个属性上,这样ViewModel这个属性改变,这个ContentControl控件所呈现的界面也会相应的改变,前台界面代码如下:
<Window x:Class="ViewModelFirstDemo.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:ViewModelFirstDemo"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ContentControl Content="{Binding ViewModel}"/>
</Grid>
</Window>
后台界面负责创建绑定的这个ViewModel属性:
namespace ViewModelFirstDemo
{
internal class MainWindowViewModel : NotifyPropertyChangedBase
{
public object ViewModel { get; set; }
public MainWindowViewModel()
{
ViewModel = new ProductViewModel();
}
}
}
当后台创建ProductViewModel并赋值给ViewModel时,ViewModel绑定到了前台ContentControl的Content属性,当赋值时前台界面应该会有所更新,咱们看一下具体的效果:
从图中可以看出,ViewModel属性的改变确实引起了前台界面的更新,但是这个并不是我们想要的,我们预期的现象应该是前台应该显示ProductViewModel所对应的视图,但是实际现象并不是如此,这是因为ContentControl的Content属性目前是一个ProductViewModel实例,它不知道如何将这个实例转换为对应的视图,因此我们需要在主窗口的ResourceDictionary中告诉UI框架如何渲染ProductViewModel实例,我们对MainWindow.xaml进行一下改造:
<Window x:Class="ViewModelFirstDemo.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:ViewModelFirstDemo"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type local:ProductViewModel}">
<local:ProductView/>
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding ViewModel}"/>
</Grid>
</Window>
在此次改造中,我们添加了一个DataTemplate(数据模板),这个数据模板的属性DataType填充了ProductViewModel的值,这个相当于告诉UI框架,当遇到ProductViewModel实例时,就给我渲染出来ProductView视图,看看改造后的运行效果:
改造后,ProductView视图成功显示了出来。
3.视图模型优先示例
继续使用上篇文章中的例子,这次将上篇文章的例子加以改造,实现视图模型优先相关功能。此次代码我们将引入2个ViewModel,上篇文章我们讲到每个ViewModel都需要实现INotifyPropertyChanged接口,这次我们有2个ViewModel,如果都实现INotifyPropertyChanged接口,写起来比较麻烦,因此我们封装一个基类来实现INotifyPropertyChanged接口,后续两个ViewModel直接继承这个基类就达到实现INotifyPropertyChanged接口的目的了,基类的名字为NotifyPropertyChangedBase,具体实现代码如下:
public class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
此次代码中需要两个ViewModel,分别是MainWindowViewModel和ProductViewModel,MainWindowViewModel和MainWindow的绑定我们还是使用ViewFirst方式进行绑定,ProductViewModel我们采用ViewModel First方式进行绑定,这次我们将上篇文章中的MainWindowViewModel里面的业务逻辑都转移至ProductViewModel。
首先来看MainWindowViewModel和MainWindow的绑定方式,MainWindow.xaml内容如下:
<Window x:Class="ViewModelFirstDemo.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:ViewModelFirstDemo"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type local:ProductViewModel}">
<local:ProductView/>
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding ViewModel}"/>
</Grid>
</Window>
MainWindow.cs代码中完成View First绑定:
namespace ViewModelFirstDemo
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
}
在MainWindowViewModel的构造函数中,我们实例化ProductViewModel:
namespace ViewModelFirstDemo
{
internal class MainWindowViewModel : NotifyPropertyChangedBase
{
public object ViewModel { get; set; }
public MainWindowViewModel()
{
ViewModel = new ProductViewModel(new Product()
{
Id = 1,
Name = "Test",
Price = 1
});
}
}
}
我们再来看ProductViewModel视图模型内容:
namespace ViewModelFirstDemo
{
public class ProductViewModel : NotifyPropertyChangedBase
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyChanged("Name");
}
}
private int _id;
public int Id
{
get { return _id; }
set
{
_id = value;
NotifyChanged(nameof(Id));
}
}
private double _price;
public double Price
{
get { return _price; }
set
{
_price = value;
NotifyChanged(nameof(Price));
}
}
public ProductViewModel(Product product)
{
Id = product.Id;
Name = product.Name;
Price = product.Price;
SaveCommand = new RelayCommand(x => Save());
}
public ICommand SaveCommand { get; set; }
/// <summary>
/// 保存修改
/// </summary>
public void Save()
{
// 实际应用的时候,保存可以保存到数据库、文件等,此处仅显示修改后的值
MessageBox.Show($"Id={Id},Name={Name},Price={Price}");
}
}
}
再看一下ProductViewModel对应的视图:
<UserControl x:Class="ViewModelFirstDemo.ProductView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ViewModelFirstDemo"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0, 4">
<TextBlock Text="Id:" MinWidth="100"/>
<TextBox Text="{Binding Id}" MinWidth="100"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0, 4">
<TextBlock Text="名称:" MinWidth="100"/>
<TextBox Text="{Binding Name}" MinWidth="100"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0, 4">
<TextBlock Text="价格:" MinWidth="100"/>
<TextBox Text="{Binding Price}" MinWidth="100"/>
</StackPanel>
<Button Content="保存" Command="{Binding SaveCommand}"/>
</StackPanel>
</UserControl>
业务逻辑跟上篇文章介绍的一样,只是实现方式由View First方式转换为了ViewModel First方式,我们来看应用程序运行的效果:
修改商品信息后,点击保存按钮后的效果:
4.总结
本文介绍了WPF MVVM模式中视图模型优先(ViewModel First)的实现方式,视图模型优先最大的好处就是解除视图和视图模型的耦合,后续我们会引入Caliburn.Micro框架来实现视图模型优先代码,很多商业级WPF程序均采用此库,Caliburn.Micro采用MIT宽松许可,可直接应用于商业软件,下一篇讲解Caliburn.Micro的使用方法及常用的依赖注入框架的使用(Autofac及Caliburn.Micro自带的IoC容器),感兴趣的小伙伴记得关注一下。
标签:优先,MVVM,模型,ViewModel,视图,ProductViewModel,详解,WPF,public From: https://blog.csdn.net/2402_88871930/article/details/143824358