当前位置:   article > 正文

【WPF】Prism框架基础_wpf prism

wpf prism

Prism框架:

初识:

我们创建一个基于net5.0的WPF应用程序

创建完之后在VS里面添加相关的包

请添加图片描述

修改app继承的基类

public partial class App : PrismApplication
{
}
  • 1
  • 2
  • 3

请添加图片描述

这时候会报错,我们去修改前台的代码

<prism:PrismApplication x:Class="PrisimDemo.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:PrisimDemo"
             xmlns:prism="http://prismlibrary.com/">
    <Application.Resources>

    </Application.Resources>
</prism:PrismApplication>


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

修改完并保存之后我们重新生成一下解决方案

请添加图片描述

重新生成项目之后我们需要实现基类PrismApplication的方法

    public partial class App : PrismApplication
    {
        //这个方法要返回一个Window,我们直接通过Container容器返回我们的主窗体
        //在App.xaml文件中,不再设置StartupUri
        protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }

        //第二个方法实现了依赖注入的功能 一开始我们没有就不添加了
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {

        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

这样就将我们创建的一个默认的WPF应用程序改造成基于Prism框架的程序了

我们启动一下,如果弹出两个框说明app中的starturl那一行没有删除

那么上述方法还是有些繁琐的,每次创建一个新项目就要修改很多东西,我们去扩展中下载一个模板

请添加图片描述

tips: vs2019下载扩展时,会出现下载失败的情况,可用电脑连接手机热点下载

下载完之后我们重新启动一下VS2019

安装完毕之后我们重新启动VS,并创建一个新项目

请添加图片描述

默认官方提供的容器有DryIoc和unity,我们选择DryIoc就行

请添加图片描述

进入项目我们可以看到FullApp1中app.xaml中与我们之前自己手动修改的代码一样,设置FullApp1为启动项目,启动后我们就可以看到

请添加图片描述

区域介绍:

为了引入区域的概念我们先来使用我们第一个创建的项目,并实现点击按钮切换按钮下面内容的功能

先布好局

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="auto"></RowDefinition>
        <RowDefinition></RowDefinition>
    </Grid.RowDefinitions>
    <StackPanel Orientation="Horizontal">
        <Button Margin="5" Content="打开模块A" Command="{Binding OpenCommand}" CommandParameter="ViewA"></Button>
        <Button Margin="5" Content="打开模块B"  Command="{Binding OpenCommand}" CommandParameter="ViewB"></Button>
        <Button Margin="5" Content="打开模块C"  Command="{Binding OpenCommand}" CommandParameter="ViewC"></Button>
    </StackPanel>
    <ContentControl Grid.Row="1" Content="{Binding Body}"></ContentControl>

</Grid>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

创建一个Views文件夹,里面创建三个View,分别是ViewA,ViewB,ViewC

        <TextBlock FontSize="80" Text="我是模块A"></TextBlock>
  • 1

为了实现切换功能我们创建一个ViewModels文件夹,里面创建一个MainViewModel类

    public class MainViewModel : BindableBase
    {
        public DelegateCommand<string> OpenCommand { get; private set; }
        public MainViewModel()
        {
            OpenCommand = new DelegateCommand<string>(Open);
        }
        private Object body;
        public Object Body
        {
            get { return body; }
            set { body = value; RaisePropertyChanged(); }
        }
        private void Open(string obj)
        {
            switch (obj)
            {
                case "ViewA": Body = new ViewA(); break;
                case "ViewB": Body = new ViewB(); break;
                case "ViewC": Body = new ViewC(); break;
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

这样就实现了数据和命令的绑定,但是我们没有给MainWindows添加上下文DataContext,我们可以利用Prism自动绑定上下文。但是这两个名字要有对应的关系MainView和MainViewModel,按照Prism的标准我们给MainWindow改名为MainView,在对应的地方都将MainWindow改为MainView。并且在前台上添加代码

        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
  • 1
  • 2
<Window x:Class="PrisimDemo.Views.MainView"
        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:PrisimDemo"
        xmlns:prism="http://prismlibrary.com/"

        mc:Ignorable="d"
        Title="MainView" Height="450" Width="800"
        prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <Button Margin="5" Content="打开模块A" Command="{Binding OpenCommand}" CommandParameter="ViewA"></Button>
            <Button Margin="5" Content="打开模块B"  Command="{Binding OpenCommand}" CommandParameter="ViewB"></Button>
            <Button Margin="5" Content="打开模块C"  Command="{Binding OpenCommand}" CommandParameter="ViewC"></Button>
        </StackPanel>
        <ContentControl Grid.Row="1" Content="{Binding Body}"></ContentControl>

    </Grid>
</Window>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

请添加图片描述

但是这么做我们发现我们这样写代码的耦合性太高,仅仅实现这样一个简单的功能MainVIewModel就有一长段代码了,我们就使用Region来解决这个问题。

在前台删除ContentControl的绑定数据并指定一个RegionName

代表我们给中间这一块区域定义了一个名字

<ContentControl Grid.Row="1" prism:RegionManager.RegionName="COntentRegion"></ContentControl>
  • 1

我们就可以在MainViewModel里通过IRegionManager拿到我们上面定义的Region区域

namespace PrisimDemo.ViewModels
{
    public class MainViewModel : BindableBase
    {
        private readonly IRegionManager regionManager;
        public DelegateCommand<string> OpenCommand { get; private set; }
        public MainViewModel(IRegionManager regionManager)
        {
            OpenCommand = new DelegateCommand<string>(Open);
            this.regionManager = regionManager;
        }

        private void Open(string obj)
        {
            //首先通过IRegionManager接口获取全局定义可用区域
            //往这个区域动态设置内容
            //设置内容的方式是通过依赖注入的形式
            regionManager.Regions["ContentRegion"].RequestNavigate(obj);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

我们怎么通过一个字符串俩获取在区域内设置的内容就要靠之前在app文件中的依赖注入。

    //第二个方法实现了依赖注入的功能 一开始我们没有就不添加了
    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
        //在这里面注册ViewA,B,C,括号里面的是别名
        containerRegistry.RegisterForNavigation<ViewA>();
        containerRegistry.RegisterForNavigation<ViewB>();
        containerRegistry.RegisterForNavigation<ViewC>();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这样就修改好了之前的代码,可以发现比之前简洁很多!

模块化

我们的功能模块不一定写在一个项目当中,我们往往会分为多个模块来进行开发。我们修改前面的代码,将View文件夹中的ViewA和ViewB删掉。给解决方案添加两个项目粉分别为ModuleA和ModuleB,并在这两文件夹里面添加Views文件夹,里面分别存放ViewA.xaml和ViewB.xaml
请添加图片描述

我们还要将这两个View注册到容器中,然后再将这两个Module添加到主窗体中,理论上就实现了模块化的功能。

首先我们给两个Module在依赖里面添加对Prism.DryIoc的支持,安装完毕之后给两个Module分别添加类ModuleAProfile与ModuleBProfile

namespace ModuleA
{
    public class ModuleAProfile : IModule
    {
        public void OnInitialized(IContainerProvider containerProvider)
        {
        }

        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterForNavigation<ViewA>();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

接下来就是去主窗体的app文件中修改代码,首先我们删除Views文件夹中的ViewA,ViewB和ViewC

在主程序中引用相应的模块

请添加图片描述

给app文件重写ConfigureModuleCatalog方法,利用ModuleAProfile和ModuleBProfile将两个模块的内容添加到主程序中

    protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
    {
        moduleCatalog.AddModule<ModuleAProfile>();
        moduleCatalog.AddModule<ModuleBProfile>();
        base.ConfigureModuleCatalog(moduleCatalog);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这样就是实现了模块化,可以发现ModuleA与ModuleB完全与主程序分离出来了

导航功能

我们之前就通过导航实现了各个模块之前的切换

    private void Open(string obj)
    {
        //首先通过IRegionManager接口获取全局定义可用区域
        //往这个区域动态设置内容
        //设置内容的方式是通过依赖注入的形式
        regionManager.Regions["ContentRegion"].RequestNavigate(obj);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

如果我们想要模块支持导航功能我们就必须在容器中去注册这个模块

之前我们是通过Prism自动匹配页面的上下文,我们也可以在注册的时候手动配置,一般我们都不使用自动匹配

         containerRegistry.RegisterForNavigation<ViewA,ViewAViewModel>();
  • 1

当我们导航到ViewA时,我们可以传递一些参数

    private void Open(string obj)
    {
        //通过字典来传递参数
        NavigationParameters keys = new NavigationParameters();
        keys.Add("Title", "Hello");
        //首先通过IRegionManager接口获取全局定义可用区域
        //往这个区域动态设置内容
        //设置内容的方式是通过依赖注入的形式
            regionManager.Regions["ContentRegion"].RequestNavigate(obj,keys);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

我们在VIewAViewModel中实现INavigationAware来接收参数

创建ViewA的上下文ViewAViewModel

namespace ModuleA.ViewModels
{
    public class ViewAViewModel : BindableBase, IConfirmNavigationRequest
    {
        public ViewAViewModel()
        {

        }
        private string title;
        public string Title
        {
            get { return title; }
            set { title = value; RaisePropertyChanged(); }
        }

        //每次重新导航的时候 该实例是否重用原来的实例
        public bool IsNavigationTarget(NavigationContext navigationContext)
        {
            return true;
        }
        //拦截导航请求
        public void OnNavigatedFrom(NavigationContext navigationContext)
        {

        }
        //接受参数
        public void OnNavigatedTo(NavigationContext navigationContext)
        {
            if(navigationContext.Parameters.ContainsKey("Title"))
            Title = navigationContext.Parameters.GetValue<string>("Title");
        }

        //验证是否允许切换
        public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
        {
            bool result = true;
            if (MessageBox.Show("确认导航?", "温馨提示", MessageBoxButton.YesNo) == MessageBoxResult.No)
            {
                result = false;
            }
            continuationCallback(result);
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

修改前台代码,让Title显示出来

        <TextBlock Text="{Binding Title}" FontSize="80" ></TextBlock>
  • 1

导航还能实现返回上一页下一页的功能

这里面涉及到导航日志的概念

我们在主程序前台上添加一个返回上一页的按钮

 <Button Margin="5" Command="{Binding BackCommand}" CommandParameter="ViewB" Content="返回上一步"></Button>
  • 1

修改MainViewModel 的代码

namespace PrisimDemo.ViewModels
{
    public class MainViewModel : BindableBase
    {
        private readonly IRegionManager regionManager;
        
        //导航日志
        private IRegionNavigationJournal journal;
        public DelegateCommand<string> OpenCommand { get; private set; }
        
        public DelegateCommand BackCommand { get; private set; }
        public MainViewModel(IRegionManager regionManager)
        {
            OpenCommand = new DelegateCommand<string>(Open);
            BackCommand = new DelegateCommand(Back);
            this.regionManager = regionManager;
        }

        private void Back()
        {
            //上一步
            if(journal.CanGoBack)
            journal.GoBack();
        }

        private void Open(string obj)
        {
            NavigationParameters keys = new NavigationParameters();
            keys.Add("Title", "Hello");
            //首先通过IRegionManager接口获取全局定义可用区域
            //往这个区域动态设置内容
            //设置内容的方式是通过依赖注入的形式
            
            regionManager.Regions["ContentRegion"].RequestNavigate(obj,callBack=> {
                //我们每次导航都会有一个回调,我们可以通过回调的结果来获取导航日志
                if ((bool)callBack.Result)
                {
                    journal = callBack.Context.NavigationService.Journal;
                }
            }, keys);
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

这样就实现了返回上一页的功能!

对话服务

简单点讲就是封装好的弹窗的API

我们在ViewAModule模块下的Views里面新建一个ViewC用户控件

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"></RowDefinition>
            <RowDefinition Height="auto"></RowDefinition>
            <RowDefinition Height="auto"></RowDefinition>
        </Grid.RowDefinitions>
        <TextBlock Text="温馨提示?"></TextBlock>
        <TextBlock Text="{Binding Title}" Grid.Row="1" VerticalAlignment="Center" FontSize="80"></TextBlock>
        <StackPanel Grid.Row="2" Orientation="Horizontal">
            <Button Content="确定" Command="{Binding SaveCommand}" Height="80" Width="100"></Button>
            <Button  Content="取消" Command="{Binding CancelCommand}"  Height="80" Width="100"></Button>
        </StackPanel>
    </Grid>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

然后将VIewC绑定到容器中

        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterDialog<ViewC,ViewCViewModel>();
            containerRegistry.RegisterForNavigation<ViewA,ViewAViewModel>();
        }
  • 1
  • 2
  • 3
  • 4
  • 5

我们还要配置ViewC的上下文,创建ViewCViewModel并是实现IDialogAware这个类

namespace ModuleA
{
    public class ViewCViewModel : IDialogAware
    {
        public ViewCViewModel()
        {
            CancelCommand = new DelegateCommand(Cancel);
            SaveCommand = new DelegateCommand(Save);
        }

        private void Cancel()
        {
            RequestClose?.Invoke(new DialogResult(ButtonResult.No));
        }

        private void Save()
        {
            OnDialogClosed();
        }

        public string Title { get; set; }
        public DelegateCommand CancelCommand { get; set; }
        public DelegateCommand SaveCommand { get; set; }


        public event Action<IDialogResult> RequestClose;

        
        //是否允许关闭
        public bool CanCloseDialog()
        {
            return true;
        }

        public void OnDialogClosed()
        {
            DialogParameters keys = new DialogParameters();
            keys.Add("Value", "111111");//返回的结果 名字为Value的变量值为关闭
            RequestClose?.Invoke(new DialogResult(ButtonResult.OK, keys));
        }

        public void OnDialogOpened(IDialogParameters parameters)
        {
            //接受传递过来的参数
            Title = parameters.GetValue<string>("Title");
        }
    }
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

在MainViewModel中

namespace PrisimDemo.ViewModels
{
    public class MainViewModel : BindableBase
    {
        private readonly IDialogService dialogService;
        public MainViewModelIDialogService dialogService)
        {
            this.dialogService = dialogService;
        }

        private void Open(string obj)
        {
            //传递过去的参数
            DialogParameters keys = new DialogParameters();
            keys.Add("Title", "Hello·······");
            dialogService.ShowDialog(obj,keys,callback=>
            {
                //接受回调过来的参数
                if (callback.Result == ButtonResult.OK)
                {
                    string result = callback.Parameters.GetValue<string>("Value");
                }
            });

        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

发布订阅功能

实现了发送消息的功能,我们在view或者viewmodel中发布消息那么在view或者viewmodel只要订阅了这个消息都可以接收到发送过来的消息。

例:我们在ModuleA中新建一个Event文件夹在里面创建一个MessageEvent类

public class MessageEvent:PubSubEvent<string>
{
}
  • 1
  • 2
  • 3

MessageEvent这个类继承了PubSubEvent就是一个string类型的消息模型

我们在ViewCViewModel中向MessageEvent发布一个消息

        private readonly IEventAggregator aggregator;
        public ViewCViewModel(IEventAggregator aggregator)
        {
            CancelCommand = new DelegateCommand(Cancel);
            SaveCommand = new DelegateCommand(Save);
            this.aggregator = aggregator;
           
        }

        private void Cancel()
        {
            //向MessageEvent 发布一个hello
            aggregator.GetEvent<MessageEvent>().Publish("Hello");
           // RequestClose?.Invoke(new DialogResult(ButtonResult.No));
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

我们在ViewC中接受这个消息

    public ViewC(IEventAggregator aggregator)
    {
        InitializeComponent();
        aggregator.GetEvent<MessageEvent>().Subscribe(arg =>
        {
            MessageBox.Show("接收到了" + arg);
        });
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这样就实现了发布订阅的功能,同时也可以取消订阅

    public ViewC(IEventAggregator aggregator)
    {
        InitializeComponent();
        aggregator.GetEvent<MessageEvent>().Subscribe(SubMessage);
        aggregator.GetEvent<MessageEvent>().Unsubscribe(SubMessage);
    }

    private void SubMessage(string obj)
    {
        MessageBox.Show("接受到了" + obj);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

我们也可以定义一个复杂消息模型

public class TestEvent : PubSubEvent<Test>
{

}

public class Test
{
    public string Id { get; set; }
    public string Name { get; set; }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/煮酒与君饮/article/detail/746097
推荐阅读
相关标签
  

闽ICP备14008679号