详解WPF中的MVVM模式(二)


继续接着上篇讲解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容器),感兴趣的小伙伴记得关注一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值