首页 > 其他分享 >详解WPF中的MVVM模式(二)

详解WPF中的MVVM模式(二)

时间:2024-11-16 23:17:01浏览次数:3  
标签:优先 MVVM 模型 ViewModel 视图 ProductViewModel 详解 WPF public

文章目录


继续接着上篇讲解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

相关文章

  • Jarvis March算法详解及Python实现(附设计模式案例)
    目录JarvisMarch算法详解及Python实现(附设计模式案例)第一部分:JarvisMarch算法概述与原理1.1什么是JarvisMarch算法?1.2算法原理1.3算法流程1.4时间复杂度第二部分:JarvisMarch算法的Python实现(面向对象设计)2.1面向对象设计2.2代码实现2.3代......
  • Redis7.x安装系列教程(二)主从部署&原理详解
    Redis7.x安装系列教程(二)主从部署&原理详解1、什么是主从复制主从复制是指将一台Redis服务器的数据,复制到其他的Redis服务器上。前者称为主节点(master),后者是从节点(slave),数据的复制是单向的,只能是从主节点到从节点。每台Redis服务器未修改配置前都是主节点,一个主节点可以0~N......
  • 【C++】深入理解自定义 list 容器中的 list_iterator:迭代器实现详解
    个人主页:起名字真南的CSDN博客个人专栏:【数据结构初阶】......
  • JUC---ThreadLocal原理详解
    什么是ThreadLocal?通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢?JDK中自带的ThreadLocal类正是为了解决这样的问题。ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻......
  • PostgreSQL 配置文件详解
    配置文件pg_hba.conf详解[postgres@localhostdata]$vipg_hba.conf#TYPE DATABASE       USER           ADDRESS                METHOD#"local"isforUnixdomainsocketconnectionsonlylocal  all        ......
  • 【Linux进程篇1】认识冯·诺依曼体系结构(引出进程详解)
    ---------------------------------------------------------------------------------------------------------------------------------每日鸡汤:用这生命中的每一秒,给自己一个不后悔的未来。-------------------------------------------------------------------------......
  • Nuxt.js 应用中的 vite:extendConfig 事件钩子详解
    title:Nuxt.js应用中的vite:extendConfig事件钩子date:2024/11/16updated:2024/11/16author:cmdragonexcerpt:通过合理使用vite:extendConfig钩子,开发者可以极大地增强Nuxt3项目的灵活性和功能性,为不同的项目需求量身定制Vite配置。无论是添加插件、调整构建......
  • C#可空类型详解:定义、判断值与访问方法
    C#中的可空类型(NullableTypes)在C#中,值类型(如int、float、struct等)默认情况下不能表示“无值”或“未知”的状态。为了解决这个问题,C#引入了可空类型(NullableTypes),它允许值类型表示一个额外的状态:已赋值(HasValue)或未赋值(无值,即Null)。可空类型是通过在值类型后面加上问号(?......
  • 一文详解Java反射技术
    Java反射什么是Java反射以及引出反射的背景?Class类如何获取一个class的Class实例Class类的使用获取属性获取调用方法获取注解信息获取构造方法反射的应用什么是Java反射以及引出反射的背景?Java程序中的对象有两种类型,编译时类型和运行时类型,而很多时候编译......
  • Java反序列化-Commons Collections3利用链分析详解
    介绍CC3与CC1和CC6的主要区别在于,CC1和CC6依赖反射机制来执行Runtime.getRuntime().exec()等危险命令,而如果服务器将这些方法列入黑名单,这两种方式就会失效。相比之下,CC3通过类加载器动态加载恶意类来执行危险函数,绕过黑名单限制,从而达到命令执行的目的。公众号:T......