第六章:自建MVVM模式的简易项目框架

时间:2021-01-14 01:24:00

总目录



前言

前面的几章为我们筑好了MVVM的基础,在这一章中我们将自己搭建一个MVVM模式下的简易项目框架。


一、MVVM是什么?

1.介绍

MVVM是一种模式,主要目的是降低界面和业务逻辑的耦合。
MVVM是Model-View-ViewModel(模型-视图-视图模型)的简写。是WPF开发过程中必不可少的一种开发模式,MVVM基本结构如下图所示:
第六章:自建MVVM模式的简易项目框架

  • View(视图) 就是交互界面,接受用户的输入和展示数据;
  • Model(模型) 是数据模型,项目中的对象,包含属性和行为,项目中的任何事物都可抽象为Model,例如项目中一个用户,我们可以抽象为一个用户的类,可以包含该用户的姓名,出生日期,电话等等信息
  • ViewModel(视图模型)是负责处理业务逻辑的核心。
  • 三者关系:在View和ViewModel中通过数据绑定将界面上元素的依赖属性与实现了INotifyPropertyChanged的ViewModel中的属性进行绑定,实现数据的实时更新机制。在View和ViewModel中通过命令绑定将界面上的操作用Command属性与ViewModel中实现了ICommand的命令进行绑定,实现了用户操作指令的下发。而Model 则是负责给ViewModel提供View所需的对象的数据模型。
  • 通过MVVM这种模式可以最大限度的降低UI界面,数据模型,逻辑业务之间的耦合。让View 将关注点优化呈现效果上,至于数据和操作只需绑定即可;让Model将关注点放在数据模型上,ViewModel将关注点放在处理核心业务逻辑上。

2.优势

  • 低耦合,View可以独立于Model变化和修改,一个ViewModel可以绑定到不同的”View”上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
  • 提高代码重用,可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑;
  • 独立开发,业务开发人员可以专注于业务逻辑和数据的开发,界面开发人员可以专注于页面的开发。

二、搭建MVVM项目框架

现在我们开始着手搭建MVVM项目框架,步骤如下:
(1)首先需要新建一个项目,创建Views,ViewModels,Models 三个文件夹,这样基本结构就搭建好了。

(2)将我们写的xaml页面放在Views目录下,一般就是命名为:xxxView,然后与之对应的视图模型命令为xxxViewModel放在ViewModels目录下,另外将需要的数据模型放置在Models目录下。

(3)再者需要创建一些辅助类的文件目录,一般会创建Resources用于存放一些资源如样式,字体,图片等等,创建一个Helper或者Common的目录,用于存放一些公用的方法类或者帮助类。

具体目录如下图所示:
第六章:自建MVVM模式的简易项目框架
这样一个简易的MVVM项目框架就搭建好了,当然在后面我们会接触到按照MVVM模式已搭建好的比较完善的框架供我们使用。

三、WPF中实现MVVM的重点内容

1.实现INotifyPropertyChanged接口

    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler? PropertyChanged;

        public void OnPropertyChanged([CallerMemberName] string name=null)
        {
            PropertyChanged?.Invoke(this,new PropertyChangedEventArgs(name));
        }

        public void Set<T>(ref T field, object value, [CallerMemberName] string name="")
        {
            field= (T?)value!;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));            
        }
    }

2.实现ICommand接口以及事件转命令

  • 实现实现ICommand接口
	public class DelegateCommand : ICommand
    {
        public event EventHandler? CanExecuteChanged;

        public DelegateCommand(Action executeActionNoPara, Func<bool> canExcuteFuncNoPara = null!)
        {
            this.ExecuteActionNoPara = executeActionNoPara;
            this.CanExcuteFuncNoPara = canExcuteFuncNoPara;
        }

        public DelegateCommand(Action<object?> executeAction, Func<object?, bool> canExcuteFunc = null!)
        {
            this.ExecuteAction= executeAction;
            this.CanExcuteFunc = canExcuteFunc;
        }

        public bool CanExecute(object? parameter=null)
        {
            if (parameter==null)   return this.CanExcuteFuncNoPara == null ? true : this.CanExcuteFuncNoPara.Invoke();
            return this.CanExcuteFunc == null ? true : this.CanExcuteFunc.Invoke(parameter);
        }

        public void Execute(object? parameter = null)
        {
            if (parameter == null) this.ExecuteActionNoPara?.Invoke();
            else this.ExecuteAction?.Invoke(parameter);
        }

        public Action ExecuteActionNoPara { get; set; }
        public Func<bool> CanExcuteFuncNoPara { get; set; }

        public Action<object?> ExecuteAction { get; set; }
        public Func<object?,bool> CanExcuteFunc { get; set; }
    }
  • 事件转命令
    • 1 首先添加名为 Microsoft.Xaml.Behaviors.Wpf 的NuGet包
    • 2 然后在界面中引用 xmlns:i=“http://schemas.microsoft.com/xaml/behaviors”
    • 3 使用,具体使用方法如下:
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Background="Red">
    <i:Interaction.Triggers>
         <i:EventTrigger EventName="MouseDown">
             <i:InvokeCommandAction Command="{Binding MouseDownCommand}"></i:InvokeCommandAction>
         </i:EventTrigger>
    </i:Interaction.Triggers>
</StackPanel>

3.实现窗体间的交互与数据传递

这一块儿的内容,当前系列之前并没有介绍,在这里介绍一下。
假如现在有这样一个需求,点击用户列表中的某一项,我需要打开一个窗口查看该项的详情页,那么必定就会涉及,新开一个窗口,并且窗体将需要传递数据。
如果我们在ViewModel 中按照下面代码实现:

        private void ShowDetail()
        {
            UserDetailView view = new UserDetailView();
            //.....
            UserDetailView.Show();
        }

功能是可以实现,但是显然,不符合解耦的目的。MVVM的目的就是解耦,如果在ViewModel中操作View的内容,无疑又干回去!那我们应该去如何操作呢?定义一个专用于窗体间交互与数据传递的类,
窗体间需要做任何事情,都找"它"。

形如:

    public class WindowManager
    {
        private static Dictionary<string, Action<object>> _dir = new Dictionary<string, Action<object>>();
        public static void Register(string key,Action<object> action)
        {
            if (!string.IsNullOrEmpty(key)&&!_dir.ContainsKey(key))
            {
                _dir.Add(key,action);
            }
        }

        public static void DoAction(string key,object obj)
        {
            if (!string.IsNullOrEmpty(key) && _dir.ContainsKey(key))
            {
                _dir[key]?.Invoke(obj);
            }
        }
    }

当然,我们还可以根据项目对该类做完善和扩展。

4.页面切换时数据的处理

未完待续

5.特别说明

  • View中依赖属性需要绑定ViewModel中的属性
  • 注意绑定的是ViewModel中的属性,不是字段。
  • 注意为了实现View和Model的隔离,View是绑定ViewModel中属性,而不可以直接绑定Model层,Model可以为ViewModel提供数据模型,如我们常见场景Model中有个UserInfo类,然后再在ViewModel将UserInfo 申明为一个属性User,View会绑定ViewModel中User属性
  • 那么何时将属性设置在ViewModel中,何时将属性放在Model*ViewModel使用呢?
    ①一般页面上展示集合的情况,都是需要将数据模型放在Model层,然后通过ViewModel申明为一个泛型集合来使用;
    ②在界面上有一些不同区域需要展示不同数据的情况,如,左边需要放用户数据,右边需要放订单数据或其他什么数据,就需要在Model中分别定义数据模型,然后ViewModel中使用;
    ③在少数情况下可以直接在属性申明在ViewModel中,而省去在Model中定义数据模型,这类情况一般就是该属性复用的地方极少,或者只有一两个零散的属性会在View中需要使用。
  • 在绑定集合的时候,若要设置动态绑定,以便集合中的插入或删除操作可以自动更新 UI,则集合必须实现 INotifyCollectionChanged 接口,WPF 提供 ObservableCollection 类,该类是公开 INotifyCollectionChanged 接口的数据集合的内置实现。如果还要实现集合每项对象属性值的变化通知到UI则还需要集合的数据对象实现INotifyCollectionChanged。
  • 解耦的根本在个人理解来看,就是加个"第三方代理商",有什么事情都找代理商,比如要将View和Model 解耦,就加了一个ViewModel用于两者的解耦。

结语

以上就是本文要介绍的内容,希望以上内容可以帮助到你,如文中有不对之处,还请批评指正。