首页 > 其他分享 >Prism-AutoWireViewModel

Prism-AutoWireViewModel

时间:2023-08-23 23:12:57浏览次数:32  
标签:ViewModelLocationProvider ViewModel AutoWireViewModel Prism 实例 Type View

目录

静态类ViewModelLocationProvider维护一张View.GetType().ToString() --> ViewModel实例的映射表,以及一张View.GetType().ToString() --> ViewModel Type的映射表,以及根据View的FullName推算出ViewModel Type 的FullName的策略,还有一个根据ViewModel的Type构造出1个ViewModel实例的策略,最终得到适配View的ViewModel实例赋值给其DataContext.

快速实践

在Application的OnStartup(StartupEventArgs startupEventArgs)中

 ViewModelLocationProvider.SetDefaultViewModelFactory((vmType) =>
 {
     // 返回一个ViewModel实例。一般会从DI容器取出ViewModel实例。实现这个则下面的不用实现了。
 });

 ViewModelLocationProvider.SetDefaultViewModelFactory((view, vmType) =>
 {
     // 返回一个ViewModel实例。一般会从DI容器取出ViewModel实例。实现这个则上面的不用实现了。
 });

 ViewModelLocationProvider.Register<View>(()=>new ViewModel()); // 注册工厂委托到字典
 ViewModelLocationProvider.Register<View,ViewModelType>();      // 注册类型到字典,不"劳烦"ViewTypeToViewModelTypeResolver解析。

 ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>
 {
     // 返回1个ViewModel Type。
	 // 可以覆盖Prism 默认的确定ViewModel Type的策略。
	 // 一般不会选择重新定制此策略,而是使用Prism默认的策略
 });

在每个View中的XAML中

<Window x:Class="Demo.Views.MainWindow"
    ...
    xmlns:prism="http://prismlibrary.com/"
    prism:ViewModelLocator.AutoWireViewModel="True">

或在每个View的CS文件合适的位置(一般是构造函数或Load函数)中

ViewModelLocator.SetAutoWireViewModel(view,true);

View.DataContext = GetViewModelInstance()的时机

View的附加属性ViewModelLocator.AutoWireViewModel设置成True时,会触发ViewModelLocationProvider去为View寻找合适的ViewModel实例。

若View要利用Prism自动定位ViewModel功能,需添加附加属性ViewModelLocator.AutoWireViewModel并设置成True。

<Window x:Class="Demo.Views.MainWindow"
    ...
    xmlns:prism="http://prismlibrary.com/"
    prism:ViewModelLocator.AutoWireViewModel="True">

附加属性的定义细节。被定义在ViewModelLocator类,重点是其元数据中的回调函数PropertyChangedCallback,它会在AutoWireViewModelProperty=True后被触发完成ViewModel的构建和DataContext的赋值。

public static class ViewModelLocator
{
    public static DependencyProperty AutoWireViewModelProperty = DependencyProperty.RegisterAttached("AutoWireViewModel", typeof(bool?), typeof(ViewModelLocator), new PropertyMetadata(defaultValue: null, propertyChangedCallback: AutoWireViewModelChanged));
}

分析ViewModelLocator的方法AutoWireViewModelChanged

private static void AutoWireViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
_WINUI
    if (!DesignerProperties.GetIsInDesignMode(d))

    {
        var value = (bool?)e.NewValue;
        if (value.HasValue && value.Value) // View的依赖属性AutoWireViewModelProperty被赋值成True时才会真的去寻找合适ViewModel实例作为其DataContext。View不添加附加属性或添加附加属性但初值是False,Prism都不会为View自动构建ViewModel.
        {
            ViewModelLocationProvider.AutoWireViewModelChanged(d, Bind); // d是View实例,根据View.GetType().ToString()和View的FullName构建出适配的ViewModel,然后Bind方法完成View.DataContext=ViewModel.
        }
    }
}

static void Bind(object view, object viewModel)
{
    if (view is FrameworkElement element) // 只要是FrameworkElement,就可以做View。(WPF元素继承链从FrameworkElemt开始才具有DataContext属性)。
        element.DataContext = viewModel;
}

View的附加属性ViewModelLocator.AutoWireViewModel设置成True时,才会触发构建ViewModel实例并完成View.DataContetx=ViewModel。按照习惯,通常在XAML中添加prism:ViewModelLocator.AutoWireViewModel="True",所以一般是在View的构造函数中完成DataContext的赋值的。我们完全可以挪到View的Load函数中:ViewModelLocator.SetAutoWireViewModel(view,true);

自动为View构建合适的ViewModel的策略

public static void AutoWireViewModelChanged(object view, Action<object, object> setDataContextCallback)
{
    // Try mappings first
    object viewModel = GetViewModelForView(view); // 检查委托工厂

    // try to use ViewModel type
    if (viewModel == null)
    {
        //check type mappings
        var viewModelType = GetViewModelTypeForView(view.GetType()); // 检查ViewModel Type 缓存

        // fallback to convention based
        if (viewModelType == null)
            viewModelType = _defaultViewTypeToViewModelTypeResolver(view.GetType()); // 利用定位功能

        if (viewModelType == null)
            return;
        // 实例化ViewModel
        viewModel = _defaultViewModelFactoryWithViewParameter != null ? _defaultViewModelFactoryWithViewParameter(view, viewModelType) : _defaultViewModelFactory(viewModelType);
    }

    // 完成DataContext = ViewModel
    setDataContextCallback(view, viewModel);
}

以View.GetType().ToString()为Key,在字典(Dictionary<string, Func<object>>())中寻找返回ViewModel实例的Func,拿到ViewModel实例。

上一步成功,直接View.DataContext = ViewModel. 上一步失败,下一步先确定View所需要的ViewModel的类型,拿到类型再实例化。

以View.GetType().ToString()为Key,去字典(Dictionary<string, Type>())中找到ViewModel的Type.

上一步成功,直接跳到根据Type实例化ViewModel的步骤,否则,利用Prism确认ViewModel类型规则继续确认ViewModel的Type.

如果默认规则成功确定Type,则实例化完成Wire,否则,因无法得到ViewModel实例导致一张DataContext为NULL的View诞生。(并不会抛出异常提醒,这是合理的,可能有的View没遵守MVVM开发,后台逻辑放到View的XAML和CS文件中了)。

Prism自动确认ViewModel Type的规则

static Func<Type, Type> _defaultViewTypeToViewModelTypeResolver =
    viewType =>
    {
        var viewName = viewType.FullName;
        viewName = viewName.Replace(".Views.", ".ViewModels.");
        var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
        var suffix = viewName.EndsWith("View") ? "Model" : "ViewModel";
        var viewModelName = String.Format(CultureInfo.InvariantCulture, "{0}{1}, {2}", viewName, suffix, viewAssemblyName);
        return Type.GetType(viewModelName);
    };

先确定ViewModel的FullName字符串,即Namespace和Name.

ViewModel的名称

  1. View如果以View结尾,追加Model,否则追加ViewModel。

例:

EmployeeList,则ViewModel是EmployeeListViewModel

EmployeeListView,则ViewModel是EmployeeListViewModel

ViewModel命名空间

将View命名空间中的Views替换成ViewModels即可。如果View的命名空间没有Views字眼,那么ViewModel的命名空间和View的一模一样。

例:

A.B.Views.EmployeeList A.B.ViewModels.EmployeeListViewModel

A.B.EmployeeListView A.B.EmployeeListViewModel

  1. 在View所在程序集中寻找FullName是上一步得到的FullName字符串的Type。存在这样的Type,则实例化作为DataContext,否则View的DataContext为NULL。

小结

View的名称可以带View也可以不带View,但ViewModel一定要以ViewModel结尾。

Prism默认的确认ViewModel策略与文件夹无关,只与命名空间有关,只是命名空间受文件夹影响而已。

实例化策略

viewModel = _defaultViewModelFactoryWithViewParameter != null ? _defaultViewModelFactoryWithViewParameter(view, viewModelType) : _defaultViewModelFactory(viewModelType);
static Func<Type, object> _defaultViewModelFactory = type => Activator.CreateInstance(type);
static Func<object, Type, object> _defaultViewModelFactoryWithViewParameter;

得到ViewModel的Type,下一步就是构造ViewModel实例。上面代码是默认实现。

defaultViewModelFactoryWithViewParameter始终是NULL,所以一直采用defaultViewModelFactory构造。但defaultViewModelFactory只是利用反射调用ViewModel的无参构造函数实例化,缺点就是如果ViewModel的不含无参构造函数,或者想利用有参构造函数实例化,又或者ViewModel的构造函数参数有接口类型想借助依赖注入容器实例化,这些_defaultViewModelFactory都做不到。

所以定制实例化策略是很经常很有必要的基操。

ViewModelLocationProvider.SetDefaultViewModelFactory(viewModelType) =>
    {
        return viewModel instance;
    });


ViewModelLocationProvider.SetDefaultViewModelFactory((view, viewModelType) =>
    {
        switch (view)
        {
            case Window window:
                //your logic
                break;
            case UserControl userControl:
                //your logic
                break;
        }
           return viewModel instance;
    }

上述代码完成对委托类型的静态字段defaultViewModelFactoryWithViewParameter和defaultViewModelFactory的赋值。实现其中1个就可以了,如果2个都实现,Prism只会优先使用defaultViewModelFactoryWithViewParameter。2者之间的差别就是前者的参数多个View,如果需要的话,在内部可利用View做一些额外的判断,仅此而已。

依赖注入无关性

回顾一下Prism自动定位ViewModel的全貌,只与两个静态类ViewModelLocator和ViewModelLocationProvider有关,前者提供附加属性用于触发定位机制和负责将DataContext=ViewModel实例,后者负责存储View和ViewModel的映射关系以及实例化ViewModel,全程与依赖注入无关。

所以,即使当前项目已经基于其他MVVM开发,也能单独使用Prism的自动定位功能。

Prism的定位功能可以分成3个部分,查找工厂委托字典、定位ViewModel Type、实例化ViewModel。我们甚至可以将所有View的ViewModel构造方法存储到字典中,根本不会使用Prism定位功能的后半部分:智能确认ViewModel Type和实例化策略。

要不要在项目中使用自动定位呢? 建议用,可以强制团队成员规范项目文件夹结构。

开发规范

  1. 尽量所有的View和其ViewModel都遵守命名规约

    View和ViewModel的命名空间完全相同,除了Views和ViewModels字符串不匹配是允许的,且ViewModel的命名空间不能包含.Views.;View的名称无要求,但习惯是以View结尾,但ViewModel名称必须以ViewModel结尾。

    假设

    View命名空间: A.B.C.D.E

    ViewModel命名空间: a.b.c.d.e

    必须满足:View点分成5段,ViewModel也必须要点分成5段。对应序号的段相互比较(A和a比较,B和b比较...),必须完全相同,除非View段是Views,ViewModel段是ViewModels也被认为段相同,其他情况一律是段不同匹配失败!


    下面的例子是合规的,即使Views和ViewModels不在命名空间的末尾。

​ View : A.B.Views.C.EmployeeListView.xaml

​ ViewModel : A.B.ViewModels.C.EmployeeListViewModel.cs

​ 下面的例子也是合规的,即使View文件不以View结尾。

​ View : A.B.EmployeeList.xaml

​ ViewModel : A.B.EmployeeListViewModel.cs

  1. 所有的View开启自动确认ViewModel功能,不遵守命名规约的可以将其ViewModel的Type添加到字典,不让Prism费劲去分析FullName。

ViewModelLocationProvider.Register(typeof(MainWindow).ToString(), typeof(CustomViewModel));

ViewModelLocationProvider.Register<MainWindow, CustomViewModel>();

​ 不遵守命名规约或虽然遵守但是ViewModel的实例化比较异类特殊,也可以将ViewModel的工厂委托添加到字典。

ViewModelLocationProvider.Register(typeof(MainWindow).ToString(), () => Container.Resolve<CustomViewModel>());

ViewModelLocationProvider.Register<MainWindow>(() => Container.Resolve<CustomViewModel>());

如果根据ViewModel Type进行实例化的defaultViewModelFactoryWithViewParameter和defaultViewModelFactory以及工厂委托使用依赖注入容器解析ViewModel的话,别忘了先注册ViewModel到容器。另外,记得工厂委托会优先于实例化策略来提供ViewModel实例。

标签:ViewModelLocationProvider,ViewModel,AutoWireViewModel,Prism,实例,Type,View
From: https://www.cnblogs.com/LiuwayLi/p/17644714.html

相关文章

  • Prisma - 入门
    Prisma是一个开源的数据库工具链项目,支持PostgreSQL、MySQL、MongoDB、SQLServer和SQLite。下面我会使用Mysql+Typescript来作为演示。安装Prisma首选你需要有一个Node环境。然后新建一个文件夹,初始化Node项目。npminit-ynpmitypescriptts-node--save-devnpmipris......
  • 为WPF框架Prism注册Nlog日志服务
    这篇文章介绍了为WPF框架Prism注册Nlog日志服务的方法,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 无论是Nlog还是Serilog,它们都提供了如何快速在各类应用程序当中的快速使用方法。尽管,你现在无论是在WPF或者ASP.NETCore当中,......
  • 1.Prism
    Region(区域)在程序编写的过程中我们肯定会遇到在一个区域上显示不同的内容,这些内容可能属于不同窗口,之前是弄个panel,需要显示哪个窗口就给让panel显示。1.定义区域2.提供对区域的访问。3.对区域的注册。有三个主要接口IContainerExtension_container;IRegionManager_region......
  • Prism IoC 依赖注入
    现有2个项目,SinglePageApp是基于Prism创建的WPF项目,框架使用的是Prism.DryIoc,SinglePageApp.Services是C#类库,包含多种服务,下面通过使用Prism中的依赖注入方式,将自定义的服务注册到SinglePageApp项目中。 1.认识Prism中的依赖注入Prism项目中的App继承于PrismAppl......
  • WPF如何构建MVVM+Prism+HandyControl ,模块化的桌面应用
    为何模块化模块化是一种分治思想,不仅可以分离复杂的业务逻辑,还可以进行不同任务的分工。模块与模块之间相互独立,从而构建一种松耦合的应用程序,便于开发和维护。开发技术.Net6+WPF+Prism(v8.0.0.1909)+HandyControl(v3.4.0)知识准备什么是MVVMModel-View-ViewModel......
  • Avalonia如何快熟使用PrismAvalonia+FluentAvaloniaUI开发
    如何使用PrismAvalonia可以翻一下我前面的博客,里面有比较详细的使用接下来介绍一下FluentAvaloniaUI,github地址:amwx/FluentAvalonia:ControllibraryfocusedonfluentdesignandbringingmoreWinUIcontrolsintoAvalonia(github.com)文档地址:Home-FluentAvaloniaDo......
  • 医学绘图分析软件prism9 mac版功能强大
    GraphPadPrism9是一款医学绘图分析软件,由美国的一家公司开发。prism9具有直观、友好的用户界面,用户可以利用这款软件在屏幕上进行数据绘制和统计分析。该软件是专门为临床研究人员设计的,是一款功能强大的绘图工具,能够为临床医生提供方便、高效的数据输入。通过使用此软件,您可......
  • Prism-BindableObject
    Prism提供BindableObject作为ViewModel的基类。个人认为Prism的BindableObject不如CommunityToolkit的ObservableObject功能丰富和强大。如:SetProperty只支持back-fieldmemoryProperty,不支持non-back-fieldcalculateProperty。SetProperty不支持自定义判等器。不支持Ta......
  • wpf的动态Tab的例子,使用Prism
    引用Prism.Core,Prism.Wpf和Prism.Unity修改App.xaml的类型替换为 PrismApplication 修改App.xaml.cs:///<summary>///InteractionlogicforApp.xaml///</summary>publicpartialclassApp:PrismApplication{protectedoverride......
  • WPF+Prism基础教程
    Prism框架介绍Prism是一个用于构建松耦合、可维护和可测试的XAML应用的框架,它支持所有还活着的基于XAML的平台,包括WPF、XamarinForms、WinUI和Uwp、Uno。Prism提供了一组设计模式的实现,这些模式有助于编写结构良好且可维护的XAML应用程序,包括MVVM、依赖项注入、命......