自学内容网 自学内容网

WPF MVVM框架

一、MVVM简介

MVC Model View Control

MVP

MVVM即Model-View-ViewModel,MVVM模式与MVP(Model-View-Presenter)模式相似,主要目的是分离视图(View)和模型(Model),具有低耦合、可重用性、独立开发、可测试性等优点。

MVVM框架有很多,开源的主要有:

  • PRISM:由微软提供,和MEF/Unity一起用于依赖注入,支持组合命令,可以扩展MSDN上有详细的教程和演练。

  • MVVM Light Toolkit:有visual Studio和Expression Blend的项目和项的模板。更多信息请看这里,另外可以参考VSExpression Blend的使用教程。

  • Caliburn Micro:支持视图模型先行(ViewModel-First)和视图先行(View-First)两种开发方式,通过co-routine支持异步编程。

  • Simple MVVM Toolkit:提供VS项目和项的模板,依赖注入,支持深拷贝以及模型和视图模型之间的属性关联。

  • Catel:包含项目和项的模板,用户控件和企业类库。支持动态视图模型注入,视图模型的延迟加载和验证。还支持WP7专用的视图模型服务。

闭源框架主要有:

  • Intersoft ClientUI:付费的,只支持WPF和Silverlight,但是,除了MVVM框架,它还提供其它一些特性

  • Vidyano:免费但不开源。带有实体映射/虚拟持久化对象(数据容器),业务规则以及内置基于ACL的安全特性。

二、原生版本的MVVM实例

Models 存放的是数据模型

Service存放的是业务逻辑

ViewModels存放的便是视图模型

Views存放WPF窗口

2.1 Models文件夹中创建一个用户模型User.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
​
namespace Wpfmvvm_demo.Models
{
    public class User
    {
        /// <summary>
        /// 用户名
        /// </summary>
        public string? Name { get; set; }
        /// <summary>
        /// 密码
        /// </summary>
        public string? Password { get; set; }
    }
}

2.2 在Services文件夹中添加用户的业务逻辑UserService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Wpfmvvm_demo.Models;
​
namespace Wpfmvvm_demo.Services
{
    public class UserService
    {
        /// <summary>
        /// 获取所有用户方法
        /// </summary>
        /// <returns></returns>
        public List<User> GetAllUser()
        {
            List<User> users = new ();
​
            for (int i = 0; i < 3; i++)
            {
                var user = new User();
                user.Name = "用户" + i;
                user.Password = "密码" + i;
                users.Add(user);
            }
​
            return users;
        }
    }
}

2.3 在ViewModels中创建NotificationObject.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
​
namespace Wpfmvvm_demo.ViewModels
{
    public class NotificationObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler? PropertyChanged;
​
        public void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

2.4 在ViewModels文件中创建DelegateCommand.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
​
namespace Wpfmvvm_demo.ViewModels
{
    public class DelegateCommand : ICommand
    {
        public event EventHandler? CanExecuteChanged;
        public Func<object?,bool>? DoCanExecute { get; set; }
        public Action<object?>? DoExecute { get; set; }
​
        public bool CanExecute(object? parameter)
        {
            if (DoCanExecute != null)
            {
                return DoCanExecute(parameter);
            }
            return true;
        }
​
        public void Execute(object? parameter)
        {
            if (DoExecute != null)
            {
                DoExecute.Invoke(parameter);
            }
        }
    }
}

2.5 ViewModels中创建主窗口的视图模型MainWindowViewModel.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Wpfmvvm_demo.Models;
using Wpfmvvm_demo.Services;
​
namespace Wpfmvvm_demo.ViewModels
{
    public class MainWindowViewModel : NotificationObject
    {
        /// <summary>
        /// 用户List
        /// </summary>
        private List<User>? users;
        public List<User>? Users
        {
            get { return users; }
            set
            {
                users = value;
                RaisePropertyChanged("Users");
            }
        }
​
        /// <summary>
        /// 程序名
        /// </summary>
        public string AppName { get; set; }
​
        /// <summary>
        /// 电话
        /// </summary>
        private string? phone;
        public string? Phone
        {
            get { return phone; }
            set
            {
                phone = value;
                RaisePropertyChanged("Phone");
            }
        }
​
        /// <summary>
        /// 获取所有用户命令
        /// </summary>
        public DelegateCommand? GetAllUsersCommand { get; set; }
​
        /// <summary>
        /// 构造初始化
        /// </summary>
        public MainWindowViewModel()
        {
            AppName = "WPF MVVM 模式测试";
            Phone = "123456";
            GetAllUsersCommand = new DelegateCommand
            {
                DoExecute = new Action<object?>(GetAllUsersCommandExecute)
            };
        }
​
        /// <summary>
        /// 获取所有用户命令执行方法
        /// </summary>
        private void GetAllUsersCommandExecute(object? paramater)
        {
            Phone = Phone!.Equals("123456") ? "1234567" : "123456";
            UserService userService = new UserService();
            Users = userService.GetAllUser();
        }
    }
}

2.6 设计MainWindow.xaml界面

<Window x:Class="Wpfmvvm_demo.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:Wpfmvvm_demo"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="550">
    <Grid>
        <StackPanel Orientation="Vertical">
            <StackPanel Orientation="Horizontal">
                <Label Content="程序名"></Label>
                <Label Content="{Binding AppName}"></Label>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <Label Content="电话"></Label>
                <Label Content="{Binding Phone}"></Label>
            </StackPanel>
            <StackPanel>
                <Button Content="获取所有用户" Command="{Binding GetAllUsersCommand}"></Button>
            </StackPanel>
            <DataGrid ItemsSource="{Binding Users}" AutoGenerateColumns="False" GridLinesVisibility="All" CanUserDeleteRows="False" CanUserAddRows="False" >
                <DataGrid.Columns>
                    <DataGridTextColumn Header="用户名" Binding="{Binding Name}"></DataGridTextColumn>
                    <DataGridTextColumn Header="密码" Binding="{Binding Password}"></DataGridTextColumn>
                </DataGrid.Columns>
            </DataGrid>
        </StackPanel>
    </Grid>
</Window>

2.7 把ViewModel数据绑定到窗口

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Wpfmvvm_demo.ViewModels;
​
namespace Wpfmvvm_demo
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new MainWindowViewModel();
        }
    }
}

三、MVVM Toolkit

微软虽然提出了 MVVM,但又没有提供一个官方的 MVVM 库(多年前有过 Prism,但已经离家出走了)。每次有人提起 MVVM 库,有些人会推荐 Prism(例如我),有些人会推荐 MVVMLight。可是现在 Prism 已经决定不再支持 UWP , 而 MVVMLight 又不再更新,在这左右为难的时候 Windows Community Toolkit 挺身而出发布了 MVVM Toolkit。 MVVM Toolkit 延续了 MVVMLight 的风格,是一个轻量级的组件,而且它基于 .NET Standard 2.0,可用于UWP, WinForms, WPF, Xamarin, Uno 等多个平台。相比它的前身 MVVMLight,它有以下特点:

  • 更高:版本号更高,一出手就是 7.0。

  • 更快:速度更快,MVVM Toolkit 从一开始就以高性能为实现目标。

  • 更强:后台更强,MVVM Toolkit 的全称是 'Microsoft.Toolkit.Mvvm',根正苗红。

这个包主要提供了如下的

3.1 可观察对象

public class UserVM : ObservableObject
{
  private string name;
​
  public string Name
  {
    get => name;
    set => SetProperty(ref name, value);
  }
​
  private int age;
​
  public int Age
  {
    get => age;
    set => SetProperty(ref age, value);
  }
}

页面的类中添加

public partial class MainWindow : Window
{
  private UserVM userVM = new UserVM();
​
  public MainWindow()
  {
    InitializeComponent();
    DataContext = userVM;
    Task.Run(() =>
             {
               while (true)
               {
                 userVM.Age += 1;
                 Thread.Sleep(1000);
               }
             });
  }
}

页面中

<Grid>
  <StackPanel>
    <TextBlock FontSize="30" Text="{Binding Age}" />
  </StackPanel>
</Grid>

我们就可以看到数字就会一直递增了。

3.2 命令

页面中我们添加命令

<StackPanel Height="40" Orientation="Horizontal">
  <TextBlock FontSize="30" Text="{Binding Name}" />
  <TextBlock FontSize="30" Text="{Binding Age}" />
​
  <Button Command="{Binding IncrementAgeCommand}" Content="年龄递增" />
</StackPanel>

对应的ViewModel中添加命令及响应的事件

public class UserVM : ObservableObject
{
  private string name;
​
  public string Name
  {
    get => name;
    set => SetProperty(ref name, value);
  }
​
  private int age;
​
  public int Age
  {
    get => age;
    set => SetProperty(ref age, value);
  }
​
  public ICommand IncrementAgeCommand { get; }
  
  public UserVM()
  {
    IncrementAgeCommand = new RelayCommand(IncrementAge);
  }
  
  private void IncrementAge() => Age++;
}

这样只要我们点击按钮,年龄就会递增1。

3.3 消息机制

3.3.1 注册与发送

添加传递消息的类

public class ZMessage
{
  public int Code { get; set; }
​
  public string Message { get; set; }
​
  public ZMessage(int code, string msg)
  {
    Code = code;
    Message = msg;
  }
}

消息接收与发送

public MainWindow()
{
  InitializeComponent();
  initMessage();
}
​
private void initMessage()
{
  WeakReferenceMessenger.Default.Register<ZMessage>(
    this,
    (r, m) =>
    {
      Console.WriteLine("接收到信息:" + m.Message);
    }
  );
​
  WeakReferenceMessenger.Default.Send(new ZMessage(100, "Hello"));
}
3.3.2 可接收消息的类

当然我们也可以让我们的ViewModel接收消息

public class UserVM : ObservableRecipient, IRecipient<ZMessage>
{
  private string name;
​
  public string Name
  {
    get => name;
    set => SetProperty(ref name, value);
  }
​
  private int age;
​
  public int Age
  {
    get => age;
    set => SetProperty(ref age, value);
  }
​
  public UserVM()
  {
    IsActive = true;
  }
​
  public void Receive(ZMessage message)
  {
    Name = message.Message;
    Age = message.Code;
  }
}

这里一定要注意

  • 要继承ObservableRecipient类,实现IRecipient<>接口。

  • 只有设置IsActive = true;,才能接收消息。

我们还是在页面的类中发送消息

WeakReferenceMessenger.Default.Send(new ZMessage(18, "XiaoMing"));

页面也稍做修改

<StackPanel Orientation="Horizontal">
  <TextBlock FontSize="30" Text="{Binding Name}" />
  <TextBlock FontSize="30" Text="{Binding Age}" />
</StackPanel>

我们会发现页面上已经变更为我们发送消息的数据了。

3.4 控制反转(IOC)

添加依赖库Microsoft.Extensions.DependencyInjection

Install-Package Microsoft.Extensions.DependencyInjection -Version 6.0.0

创建我们要自动注入的类

加入如下是我们用户相关的服务

public interface IUserService
{
  string getUserName();
}

public class UserService : IUserService
{
  public string getUserName()
  {
    return "XiaoMing";
  }
}

添加注入的控制

public partial class App : Application
{
  public App()
  {
    Services = ConfigureServices();
​
    this.InitializeComponent();
  }
​
  public new static App Current => (App)Application.Current;
​
  public IServiceProvider Services { get; }
​
  private static IServiceProvider ConfigureServices()
  {
    var services = new ServiceCollection();
​
    services.AddSingleton<IUserService, UserService>();
​
    // ViewModels
    services.AddTransient<UserVM>();
​
    return services.BuildServiceProvider();
  }
}

其中

  • AddSingleton 单一实例服务

  • AddScoped 范围内的服务

  • AddTransient 暂时性服务

权重:

AddSingleton`→`AddTransient`→`AddScoped

生命周期:

  • AddSingleton 项目启动-项目关闭 相当于静态类 只会有一个 每一次获取的对象都是同一个

  • AddScoped 请求开始-请求结束 在这次请求中获取的对象都是同一个 请求时创建

  • AddTransient 请求获取-(GC回收-主动释放) 获取时创建 每一次获取的对象都不是同一个

注意:

由于AddScoped对象是在请求的时候创建的 所以不能在AddSingleton对象中使用 甚至也不能在AddTransient对象中使用

使用

private UserVM userVM = (UserVM)App.Current.Services.GetService(typeof(UserVM));
private IUserService userService = (IUserService)App.Current.Services.GetService(typeof(IUserService));

这样是不是感觉还麻烦了

但是如果我们的ViewModel是这样的

public class UserVM : ObservableObject
{
  private string name;
​
  public string Name
  {
    get => name;
    set => SetProperty(ref name, value);
  }
​
  private int age;
​
  public int Age
  {
    get => age;
    set => SetProperty(ref age, value);
  }
​
  public ICommand IncrementAgeCommand { get; }
​
  public IUserService userService { get; }
​
  public UserVM(IUserService _userService)
  {
    userService = _userService;
    IncrementAgeCommand = new RelayCommand(IncrementAge);
  }
​
  private void IncrementAge() => Age++;
}

这里的IUserService的实例并没有传入但是就可以用了,因为IOC框架已经自动注入了。

四、WPF Prism

4.1 常见的MVVM框架

众所周知, 如果你了解WPF当中的ICommand, INotifyPropertyChanged的作用, 就会发现众多框架都是基于这些进行扩展, 实现其通知、绑定、命令等功能。

对于不同的MVVM框架而言, 大体使用上会在声明方式上的差异, 以及特定功能上的差别。

下面列举了常用的3个MVVM框架,他们的一些差异。如下所示:

功能↓ / →框架名PrismMvvmlightMicorosoft.Toolkit.Mvvm
通知BindableBaseViewModelBaseObservableObject
命令DelegateCommandRelayCommandAsync/RelayCommand
聚合器IEventAggregatorIMessengerIMessenger
模块化××
容器××
依赖注入××
导航××
对话××

正如你所见, 各个框架之间都有各自的通知、绑定、事件聚合器等基础的功能, 而Prsim自带的依赖注入、容器、以及导航会话等功能, 可以为你提供更加强大的功能。

当然,在实际的开发过程当中, 可以根据实际的功能需求, 对不同的框架选型, 同时这也需要你对各个框架之间的优缺点进行判断。

那么, 下面将主要介绍Prism

4.2 BindableBase

在Prism当中, 你需要继承于BindableBase

public class TestViewModel : BindableBase
{
    private string _message;
​
    public string Message
    {
        get { return _message; }
        set { _message = value; RaisePropertyChanged(); }
    }
}

在Prism当中, 你可以使用DelegateCommand及带参数的Command

    public class TestViewModel : ViewModelBase
    {
        public DelegateCommand SendCommand { get; set; }
​
        public DelegateCommand<string> SendMessageCommand { get; set; }
    }
4.3 CompositeCommand

对于单个Command而言, 只是触发单个对应的功能, 而复合命令是Prism当中非常强大的功能, CompositeCommand简单来说是一个父命令, 它可以注册N个子命令

当父命令被激活, 它将触发对所有的子命令, 如果任意一个命令CanExecute=false,它将无法被激活,如下所示:

public class MyViewModel : NotificationObject
{
    private readonly CompositeCommand saveAllCommand;
​
    public ArticleViewModel(INewsFeedService newsFeedService,
                            IRegionManager regionManager,
                            IEventAggregator eventAggregator)
    {
        this.saveAllCommand = new CompositeCommand();
        this.saveAllCommand.RegisterCommand(new SaveProductsCommand());
        this.saveAllCommand.RegisterCommand(new SaveOrdersCommand());
    }
​
    public ICommand SaveAllCommand
    {
        get { return this.saveAllCommand; }
    }
}
4.4 IEventAggregator
  • 松耦合基于事件通讯

  • 多个发布者和订阅者

  • 微弱的事件

  • 过滤事件

  • 传递参数

  • 取消订阅

该功能主要作用为, 事件聚合器负责接收订阅以及发布消息。订阅者可以接收到发布者发送的内容。

 
//创建事件
public class SavedEvent : PubSubEvent<string> { }
​
//发布
IEventAggregator.GetEvent<SavedEvent>().Publish("some value");
​
//订阅
IEventAggregator.GetEvent<SavedEvent>().Subscribe(.Subscribe(message=>
                                                             {
                                                                 //do something
                                                             });
4.5 Filtering Events

在实际的开发过程当中,我们往往会在多个位置订阅一个事件, 但是对于订阅者而言, 他并不需要接收任何消息, 如下所示:

在Prism当中, 我们可以指定为事件指定过滤条件, 如下所示:

eventAggregator.GetEvent<MessageSentEvent>()
    .Subscribe(arg =>
               {
                   //do something
               },
               ThreadOption.PublisherThread,
               false,
               //设置条件为token等于“MessageListViewModel” 则接收消息
               message => message.token.Equals(nameof(MessageListViewModel)));

关于Subscribe当中的4个参数, 详解:

  • 1.action: 发布事件时执行的委托。

  • 2.ThreadOption枚举: 指定在哪个线程上接收委托回调。

  • 3.keepSubscriberReferenceAlive: 如果为true,则Prism.Events.PubSubEvent保留对订阅者的引用因此它不会收集垃圾。

  • 4.filter: 进行筛选以评估订阅者是否应接收事件。

4.6 Unsubscribe

为注册的消息取消订阅, Prism提供二种方式取消订阅,如下:

1.通过委托的方式取消订阅

var event = IEventAggregator.GetEvent<MessageSentEvent>();
​
event.Subscribe(OnMessageReceived);
​
event.Unsubscribe(OnMessageReceived);

2.通过获取订阅者token取消订阅

var _event = eventAggregator.GetEvent<MessageSentEvent>();
​
var token = _event.Subscribe(OnMessageReceived);
​
_event.Unsubscribe(token);


原文地址:https://blog.csdn.net/hccee/article/details/143799496

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!