­

三、从GitHub浏览Prism示例代码的方式入门WPF下的Prism之Mvvm的08-12示例

这一篇是学习了前2篇RegionManager关联视图,和通过不同的方式加载Module示例之后的开始进入MVVM了。

从第08示例开始,进入了MVVM部分。

从08示例开始学习Prism下的MVVM思想

观察08-ViewModelLocator示例

08示例只有一个工程,添加了Prism.Unity的包

1、分析ViewModelLocator工程

1.1、App.xaml

添加了命名空间xmlns:prism=”//prismlibrary.com/

修改了Application为prism:PrismApplication

移出了StartupUri属性

1.2、App.cs

修改了App继承自PrismApplication

重写CreateShell()设置主页面

重写RegisterTypes()

1.3、Views下的MainWindow.xaml

添加了显示控件ContentControl并设置了附加依赖项属性,区域名称:ContentRegion

设置和Title{Binding Title}

设置了prism:ViewModelLocator.AutoWireViewModel=”True” 属性。

cs文件下无新增代码

1.4、ViewModels文件夹下的MainWindowVieModel.cs

MainWindowViewModel继承自Prism.Mvvm.Bindable类,该类继承自INotifyPropertyChanged

并创建了属性Title并在Set的时候调用了SetProperty实现基于Bindable封装的属性通知。

2、运行代码

界面标题显示Prism Unity Application;

总结:在工程中引用Prism.Unity后,不需要额外去写DataContent代码,就可以自动关联ViewModel和View。ViewModel 又有封装比较好的BindableBase。只需要在VM下继承自BindableBase就可以了,至于如何关联上ViewModel和View的,这里还没有深入了解,只是分析示例,没有看到有额外配置的地方,凭经验MVC的经验,感觉是同名ViewModels和View。需要写代码验证;

分析09-ChangeConvention示例

ChangeConvention示例的工程名字为ViewModelLocator,只有一个工程,添加了对Prism.Unity包的引用

1、ViewModelLocator工程

1.1、App.xaml

添加了命名空间 xmlns:prism=”//prismlibrary.com/

修改了Application继承自prism:PrismApplication

移除了StartupURI属性

1.2、App.cs

重写了CreateShell(),使用Container.Resolve解析MainWindow作为返回主窗体

重写了ConfigureViewModelLocator()方法,主要的内容都在这里。把断点打 var viewName = viewType.FullName;这一行,我们观察在干什么,

 protected override void ConfigureViewModelLocator()
        {
            base.ConfigureViewModelLocator();

            ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>
            {
                var viewName = viewType.FullName;
                var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
                var viewModelName = $"{viewName}ViewModel, {viewAssemblyName}";
                return Type.GetType(viewModelName);
            });
        }

我们首先观察方法ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver()

F12跳转过去之后,从注释上理解是将默认视图类型设置为视图模型类型解析程序。

通过重写ConfigureViewModelLocator()方法,在这里重新关联View和ViewModel的关系。

通过上面的截图我们看到了ViewName、ViewModelName被重新做了关联,ViewModel被指定到了ViewModelLocator.Views.MainWindowViewModel。我们在Views目录下看到了MainWindowViewModel.cs。

1.3、Views下的MainWindow.xaml

设置了显示控件的ContentControl设置了RegionName为ContentRegion

设置了窗口的Title绑定{binding Title}

设置了Prism:ViewModelLocator.AutoWireViewModel=”True”

cs文件中无修改

1.4、Views下的MainWindowViewModel.cs

继承自BindableBase。 创建了属性Ttile使用SetProperty进行属性更改通知。

1.5、运行代码

标题修改为Prism Unity Application

总结:这个Demo主要是在讲解在App.cs下重写ConfigureViewModelLocator()方法,重新设置了View和对应ViewModel的关联。一般情形下,这个应该用不到把,毕竟Views和ViewModels是2个独立的文件夹。

分析10-CustomRegistrations示例

经过了前面这么多例子的学习,现在开始加快进度,每个工程打开后我们都会梳理一遍工程,主要梳理工程引用关系、引用的包、加载启动项,后续的过程中如果没有额外需要注意的,那么梳理过程教程中就省略了,因为前面这么多例子下来,应该都知道关注哪些内容了。

1、Views下的MainWindow.xaml

prism:ViewModelLocator.AutoWireViewModel=True;

Title={binding Title}

添加ContentControl并设置了附加依赖项属性RegionName为ContentRegion,cs文件中无新增代码

2、ViewModels 下的CustomViewModel.cs

继承自Prism.Mvvm.BindableBase

创建属性Title 并在set中使用SetPriperty实现通知;

3、重写ConfigureViewModelLocator()

代码中提供了4种方法。

 protected override void ConfigureViewModelLocator()
        {
            base.ConfigureViewModelLocator();

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

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

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

            // generic type
            ViewModelLocationProvider.Register<MainWindow, CustomViewModel>();
        }

从代码中直观来看这四种都是实现一个事情的,第四种看上去最简洁,所以,我们用第四种,使用ViewModelLocationProvider类的Register关联2个对象。一个View、一个ViewModel。用于关联View和ViewModel。这种写法比上一个例子感觉优雅很多。

启动代码

界面Title正常显示在ViewModel中设置的Title属性为Custom ViewModel Application;

总结:在App.cs中重写ConfigureViewModelLocator()方法,使用ViewModelLocationProvider.Register自定义关联View和ViewModel的关系;

分析11-CustomRegistrations示例

工程引用Prism.Unity包;App.xaml继承自PrismApplication;App.cs重写CreateShell()设置启动窗体;

重点在MainWindow.xaml和MainWindowViewModel.cs

前面的示例学习我们知道了,Views和ViewModels文件夹内的同名View和ViewModel会在Prism下自动关联,不需要我们在ConfigureViewModelLocator()中从新绑定关系;也不需要写DataContent。

1、MainWindow.xaml

添加命名空间 xmlns:prism=”//pismlibrary.com/

添加附加依赖项属性:prism:ViewModelLocator.AutoWireViewModel=True

<Window x:Class="UsingDelegateCommands.Views.MainWindow"
        xmlns="//schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="//schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="//prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="Using DelegateCommand" Width="350" Height="275">
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <CheckBox IsChecked="{Binding IsEnabled}" Content="Can Execute Command" Margin="10"/>
        <Button Command="{Binding ExecuteDelegateCommand}" Content="DelegateCommand" Margin="10"/>
        <Button Command="{Binding DelegateCommandObservesProperty}" Content="DelegateCommand ObservesProperty" Margin="10"/>
        <Button Command="{Binding DelegateCommandObservesCanExecute}" Content="DelegateCommand ObservesCanExecute" Margin="10"/>
        <Button Command="{Binding ExecuteGenericDelegateCommand}" CommandParameter="Passed Parameter" Content="DelegateCommand Generic" Margin="10"/>
        <TextBlock Text="{Binding UpdateText}" Margin="10" FontSize="22"/>
    </StackPanel>
</Window>

同时打开ViewModels下的MainWindowViewModel.cs,左右分屏对照着看,MainWindowViewModel继承自BindableBase;

在Xaml中CheckBox设置了IsChecked属性绑定了ViewModel下的IsEnabled属性,打开ViewModel下的IsEnabled属性发现在Set时不仅有SetProperty用于属性通知,还有一个ExecuteDelegateCommand.RaiseCanExecuteChanged()方法,暂且不管这里再干啥,我们继续向下看。因为这里有4种做一件事情的写法,我们可以了解四种,但是用一种就行;

Xaml中4个Button分别绑定了ExecuteDelegateCommand、DelegateCommandObservesProperty、DelegateCommandObservesCanExecute、ExecuuteGenericDelegateCommand 命名绑定的同时传入了CommandParameter参数为“Passed Parameter”。

有一个TextBlock 控件, Text绑定为ViewModel中的UpdateText;

1.1、 我们来分析MainWindowViewModel.cs代码;

MainWindowViewModel继承自BindableBase类,再set中使用SetProperty做属性更改通知;

该类定义了IsEnable属性和UpdateText属性;和4个DelegateCommand、

在构造函数中初始化了4个DelegateCommand、在初始化过程中,除了最后一个ExecuteGenericDelegateCommand方法需要传入一个参数,其他的都不需要,这4种写法,都包含了2个逻辑,一个是执行Command、另外一个是是否可以执行,就是CanExecute用于返回当前指令是否可用;

当点击Xaml的对应的Button的时候,就会触发对应的Command、我喜欢用第一种写法,拓展性比较好,也比较简单明了;

 ExecuteDelegateCommand = new DelegateCommand(Execute, CanExecute);

需要注意的是下面这个

ExecuteGenericDelegateCommand = new DelegateCommand<string>(ExecuteGeneric).ObservesCanExecute(() => IsEnabled);

这个Command在初始化的时候,需要传入一个参数,为字符串类型的。后面的都一样。调用一个方法,验证Command是否允许调用。

  public MainWindowViewModel()
        {
            ExecuteDelegateCommand = new DelegateCommand(Execute, CanExecute);

            DelegateCommandObservesProperty = new DelegateCommand(Execute, CanExecute).ObservesProperty(() => IsEnabled);

            DelegateCommandObservesCanExecute = new DelegateCommand(Execute).ObservesCanExecute(() => IsEnabled);

            ExecuteGenericDelegateCommand = new DelegateCommand<string>(ExecuteGeneric).ObservesCanExecute(() => IsEnabled);
        }

        private void Execute()
        {
            UpdateText = $"Updated: {DateTime.Now}";
        }

        private void ExecuteGeneric(string parameter)
        {
            UpdateText = parameter;
        }

        private bool CanExecute()
        {
            return IsEnabled;
        }

运行代码

显示了标题为Using DelegateCommand的窗体;对照前面的调用关系,和属性绑定关系,Prism下面如何在View和ViewModel中使用binding和Command,这里就不讲这2个细节了,第一个基础系列WPF讲过了。这里只往返观察一下View的代码和ViewModel的代码中Prism是如何使用的。尝试点击各个按钮,看代码都运行到哪里了。

总结:在Prism中使用binding还是比较方便的。保持Views和ViewModels的路径关系,在MainWindow中设置Prism:ViewModelLocator.AutoWireViewModel=True,就自动关联了2个路径下的View和ViewModel的关系;

然后就可以使用Binding等一系列内容拉,不需要写DataContent。Command和Binding的用于都一样,ViewModel下需要继承自BindableBase类,剩下的实现方法都一样了。主要是Prism帮忙封装了INotifyPropertyChanged 不需要自己在封装了。

分析12-UsingCompositeCommands示例

这个示例是写到现在终于遇到一个复杂的拉,包含3个工程我们来梳理一下引用关系

ModuleA工程引用Prism.Wpf包、UsingCompositeCommands.Core;

UsingCompositeCommands工程引用Prism.Unity包、ModuleA、UsingCompositeCommands.Core

UsingCompositeCommands.Core工程引用了Prism.Core

是不是有点多,这个代码慢慢来。因为这里包含了Command的使用,涉及到了MVVM。如果不熟悉MVVM的话。这里会有点懵,第一个WPF系列中这里有讲到,去翻我博客就好拉。

1、分析引用关系最小的UsingCompositeCommands.Core工程

UsingCompositeCommands.Core引用了Prism.Core,核心就在Prism.Core。

1.1、新增ApplicationCommands.cs

新建InterFace接口 起名为IApplicationCommands、里面定义了一个只读的Prism.Commands下的CompositeCommand复合命令;

新建ApplicationCommands类继承IApplicationCommands接口,并创建了属性SaveCommand;

整个Core就结束了。这里定义了接口IApplicationCommands和实现接口的ApplicationCommands;

具体怎么实现的我们不去关注,我们就关注,UsingCompositeCommands.Core工程引用了Prism.Core

我们先学如何使用;只有这么多代码。

using Prism.Commands;

namespace UsingCompositeCommands.Core
{
    public interface IApplicationCommands
    {
        CompositeCommand SaveCommand { get; }
    }

    public class ApplicationCommands : IApplicationCommands
    {
        private CompositeCommand _saveCommand = new CompositeCommand();
        public CompositeCommand SaveCommand
        {
            get { return _saveCommand; }
        }
    }
}

2、分析相对简单逻辑内容较少的UsingCompositeCommands主工程;

因为UsingCompositeCommands.Core写的内容注意再主工程UsingCompositeCommands中使用了。

先讲这个,逻辑可以关联上,最后讲XAML上绑定的Commands;

UsingCompositeCommands引用了Prism.Unity包;

项目ModuleA;

项目UsingCompositeCommands.Core;

2.1、App.Xaml

添加命名空间xmlns:prism=”//prismlibrary.com/

修改Application为PrismApplication

去掉StartupUri属性

2.2、App.cs

重写CreateShell()方法,解析返回启动界面

重写ConfigureModuleCatalog()

通过代码加载ModuleA工程下的ModuleAModule,哈哈,上一篇再学习Module加载时,我也喜欢并且推荐的这种写法啊。在这里AddModule之后会走ModuleA对应的Module的OnInitialized方法初始化。

重写RegisterTypes()

这个方法我们前面加上07的五个例子,一共15个例子我们都在重写RegisterTypes()方法,又不知道干啥用。又不能去掉,就报错。这里终于知道了。注册单例,使用传入的ContainerRegistry注册RegisterSingleton接口和关联对应的类。通过这个方法,在主工程中就可以使用IApplicationCommands接口下的类了。

2.3Views下的MainWindow.xaml

在MainWindow.xaml下添加了命名空间xmlns:prism=”//prismlibrary.com/

添加了附加依赖属性prism:ViewModelLocator.AutoWireViewModel=true

Title绑定了{Title}

添加了资源并绑定了所有的TabItem中Header值绑定为Title

界面显示中放置了一个TabControl控件,设置了RegionName为ContentRegion和Button,Button绑定了一个Command,注意,这里有个误导人的地方,当我们看到这个代码的时候我们是不是就以为这是下面Core中的ApplicationCommands了? 不是的,XAML和ViewModel才有直接的绑定关系,这个SaveCommand要在ViewModel中去找。前面的App.cs中是注册单例的ApplicationCommands;cs文件中无新增

  <Button Content="Save" Margin="10" Command="{Binding ApplicationCommands.SaveCommand}"/>

2.4ViewModels下的MainWindowViewModel.cs

MainWindowViewModel继承自BindableBase;

设置了Title,和ApplicationCommands属性

同时在ViewModel的构造函数中,依据传入的初始化了ApplicationCommands对象;

这个工程中就没有额外的代码了。但是大家发现一件事情了嘛,IApplicationCommands接口是有实现的SaveCommand方法的,但是这2个地方都没有实现。虽然在主工程的Button下也使用了SaveCommand。我们继续分析

3、分析ModuleA工程

ModuleA工程添加了Prism.Wpf

引用了UsingCompositeCommands.Core工程;

3.1、ModuleAModule.cs

继承自IModule,重写了OnInitialized()方法

我们找到MainWindow中ContentRegion的控件;是一个TabControl

这里是解析了3次TabView。使用SetTitle()方法获取View的TabViewModel并设置Title,然后添加到Region中。

3.2、Views下TabView.xaml

添加命名空间mlns:prism=”//prismlibrary.com/

添加附加依赖项属性prism:ViewModelLocator.AutoWireViewModel=True

界面上用到的binding、Command都是上一篇的知识。没有额外的这些就不分析了;

cs文件中无新增代码

3.3、ViewModels下的TabViewModel.cs

TabViewModel继承自BindableBase。

其他的不讲,就分析一下IApplicationCommands在TabViewModel中的使用

首先在构造函数中TabViewModel传入了IApplicationCommands,

上面创建了一个字段,接收这个applicationCommands,

使用_applicationCommands.SaveCommand调用RegisterCommand传入一个注册命令,关联两个Command。

这样的话。就关联了SaveCommand和UpdateCommand。在MainWindow.xaml中绑定的Commond的ApplicationSaveCommand就是在这里关联的UpdateCommand。因为ApplicationCommands是单例的,所以在ViewModel下的构造函数都可以请求带这个对象。使用RegisterCommand关联命令,使用UnregisterCommand取消关联;

4、运行代码

界面是显示一个TabControl包含3个子页面,和一个Save控件,点击Save会更新时间;

总结:我们主要重点分析ApplicationCommands功能,在一个单独的工程中引入了Prism.Code然后编写了一个实现IApplication接口的类,并封装了一个Command属性。在主工程的App.cs中通过重写RegisterTypes()来实现注册单例的ApplicationCommands,然后再UI的ViewModel中保存ApplicationCommands,使用对应的ApplicationCommands时需要实现的对象中关联对应的ApplicationCommands,比如ModuleA初始化的时候,添加了3个TabView,再每个TabView的ViewModel构造函数中都注册了SaeCommand的关联事件,因为是单例,每个又都注册了所以在点击最外层Save的时候,所有关联Command都会被执行,这里通过Prism.Code实现了IApplicationCommands的解耦。这里如果用好了,代码会非常整洁。

下图是整个代码的逻辑关系MainWindowViewModel->ModuleAmodule->TabViewModel

我创建了一个C#相关的交流群。用于分享学习资料和讨论问题。欢迎有兴趣的小伙伴:QQ群:542633085

今天受到了北京-关关的提醒,我添加了目录功能,可以更加方便的阅读拉。针对各种读的不方便的内容,欢迎提意见。