WPF入门到跪下 第十一章 Prism(六)跨模块交互

跨模块交互

一、事件聚合器

Prism中的事件聚合器相当于MvvmToolkit中的Messenger,可以进行订阅、发布等。

在Prism框架中,有一些类型对象是一开始就内置在IOC容器中的,例如接下来用到的IEventAggregator事件集合器对象。

1、IEventAggregator

IEventAggregator:Prism框架中的内置于IOC容器中的事件聚合器类型,通过IEventAggregator对象可以进行事件的注册以及从IOC容器中获取事件对象。

常用成员

TEventType GetEvent<TEventType>():获取容器中指定类型的事件对象。

  • 泛型中指定的事件对象必须是EventBase类型的子类,一般会通过继承EventBase的子类PubSubEvent<TPayload>PubSubEvent来创建事件类型。

2、PubSubEvent与PubSubEvent

PubSubEvent<TPayload>EventBase的子类,可以通过泛型来指定发布的信息类型。

常用成员

SubscriptionToken Subscribe(Action<TPayload> action)PubSubEvent<TPayload>的实例方法,传入有参的Action委托(事件执行方法),来订阅事件。

Publish(TPayload payload)PubSubEvent<TPayload>的实例方法,发布事件,TPayload 发布的消息对象,要与创建事件类型时,继承PubSubEvent<TPayload>所指定的泛型类型一致。

Unsubscribe(Action<TPayload> subscriber)PubSubEvent<TPayload>的实例方法,取消订阅,只有在订阅时keepSubscriberReferenceAlive参数为true才需要调用,默认情况下是不需要的。


PubSubEventEventBase的子类,可以定义无参事件。

常用成员

SubscriptionToken Subscribe(Action action)PubSubEvent的实例方法,传入无参Action委托(事件执行方法),来订阅事件。

Publish()PubSubEvent的实例方法,发布事件。

Unsubscribe(Action subscriber)PubSubEvent的实例方法,取消订阅,只有在订阅时keepSubscriberReferenceAlive参数为true才需要调用,默认情况下是不需要的。

3、完整实现

  • 创建事件类型

    一般会在程序集中新建Events文件夹,将事件类型统一放在里面。

    如果仅仅是为了发布,而不需要携带任何信息,可以直接继承非范型的PubSubEvent即可。

    public class MessageEvent:PubSubEvent<string>
    {
    }
    
  • ViewModel层中订阅、发布事件

    通过IEventAggregatorGetEvent<TEventType>()注册或获取IOC容器中指定类型的事件对象。

    通过PubSubEvent<TPayload>Subscribe(Action<TPayload> action)方法和Publish(TPayload payload)方法进行事件的订阅与发布。

    public class MainWindowViewModel:BindableBase
    {
        private string _message;
    
        public string Message
        {
            get { return _message; }
            set 
            { 
                SetProperty(ref _message, value); 
            }
        }
    
        private IEventAggregator _eventAggregator;
    
        //构造函数方式的IOC依赖注入,注入事件聚合器对象
        public MainWindowViewModel(IEventAggregator eventAggregator)
        {
            _eventAggregator = eventAggregator;
            //进行事件的注册及订阅
            _eventAggregator.GetEvent<MessageEvent>().Subscribe(EventHandle);
        }
    
        private void EventHandle(string msg)
        {
            //事件的执行内容
            Message = msg;
        }
    
        //定义命令来出发事件(发布)
        public ICommand BtnCommand {
            get => new DelegateCommand(() =>
            {
                _eventAggregator.GetEvent<MessageEvent>().Publish("this is my first prism message");
            });
        }
    
    }
    
  • View层中绑定命令,触发事件发布

    <Window ......>
        <Grid>
            <StackPanel>
                <TextBlock Text="{Binding Message}"/>
                <Button Content="Send message" Command="{Binding BtnCommand}"/>
            </StackPanel>
        </Grid>
    </Window>
    

4、订阅的参数设置

在订阅时,可以通过Subscribe方法,进行以下设置。

设置执行线程

Subscribe(Action<TPayload> action, ThreadOption threadOption):threadOption用于设置action在哪种线程上调用,为ThreadOption枚举。

  • ThreadOption.PublisherThread:在发布者的执行线程上执行action。
  • ThreadOption.UIThread:不管在哪个线程发布,action都在UI线程中执行。
  • ThreadOption.BackgroundThread:不管在哪个线程发布,action都在新建的后台线程中执行。

订阅卸载

Subscribe(Action<TPayload> action, bool keepSubscriberReferenceAlive):keepSubscriberReferenceAlive用于设置当窗口关闭时,是否保持订阅,默认为false,也就是当窗口关闭时就会取消action对该事件的订阅,下次打开窗口又重新订阅。设置为true时,则保持订阅,会增加一丢丢效率,注意设置为true后要主动调用Unsubscribe方法才可以取消订阅。一般使用默认的false就可以了。

消息过滤器

Subscribe(Action<TPayload> action, Predicate<TPayload> filter):filter用于对发送的消息进行过滤,返回true时顺利发送,否则拦截。filter是一个返回bool值的带参委托。

  • 注意,无参事件无法设置消息过滤器。
_eventAggregator.GetEvent<MessageEvent>().Subscribe(EventHandle, (msg) => msg.Contains("我想过滤啥就过滤啥"));

二、弹出窗口

在开发过程中,有些情况需要跨模块弹出窗口,接下来学习一下在View层以外的地方实现窗口的弹出。

1、简单实现

①、创建用户控件

跨模块的窗口弹出,只需要创建窗口的内容即可,也就是用户控件,这里是在Views文件夹下,创建DialogContentView用户控件。

需要注意的是,默认情况下,如果需要对弹出窗口进行样式设置的话,需要通过prism:Dialog.WindowStyle来进行设置。

  • 具体代码

    <UserControl x:Class="Zhaoxi.PrismDialog.Views.DialogContentView" 
    						 xmlns:prism="http://prismlibrary.com/"
    						 ......>
        <prism:Dialog.WindowStyle>
            <Style TargetType="Window">
                <Setter Property="Width" Value="500"/>
                <Setter Property="Height" Value="400"/>
                <!--设置WindowStyle方式的无边款-->
                <!--<Setter Property="WindowStyle" Value="None"/>
                <Setter Property="AllowsTransparency" Value="True"/>-->
                <!--设置WindowChrome的无边款-->
                <Setter Property="WindowChrome.WindowChrome">
                    <Setter.Value>
                        <WindowChrome GlassFrameThickness="-1"/>
                    </Setter.Value>
                </Setter>
            </Style>
        </prism:Dialog.WindowStyle>
    </UserControl>
    

②、创建ViewModel

在ViewModels文件夹中创建DialogContentViewModel类并实现IDialogAware接口。

  • 具体代码

    public class DialogContentViewModel : IDialogAware
    {
        public string Title => "跨域弹出来的子窗口标题";
    
        //关闭弹窗的操作
        public event Action<IDialogResult> RequestClose;
    
        //是否允许关闭窗口
        public bool CanCloseDialog()
        {
            //可以根据情况做一下业务判断
            return true;
        }
        //窗口关闭或主动触发RequestClose事件时调用
        public void OnDialogClosed()
        {
            
        }
        //窗口打开时调用
        public void OnDialogOpened(IDialogParameters parameters)
        {
            
        }
    }
    

③、注册View

在App的RegisterTypes方法中通过调用IContainerRegistry对象的RegisterDialog方法进行DialogContentView的注册

RegisterDialog<TView>(string name = null):注册对话类型,可以传入string作为使用时的key。

  • 需要注意的是,如果没有传入name参数,使用ShowDialog展示时,则根据注册的对话类型名称来查找。如果传入了name,则必须根据name来查找。

  • 具体代码

    public partial class App : PrismApplication
    {
        protected override Window CreateShell()
        {
            return Container.Resolve<Views.MainWindow>();
        }
    
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
    			  //注册一个窗口类型
            containerRegistry.RegisterDialog<Views.DialogContentView>();
        }
    }
    

④、定义弹出窗口命令

通过IOC依赖注入,获得IDialogService属性对象。

定义命令,在命令中调用IDialogService对象的ShowDialog方法来展示串口。

ShowDialog(string name):展示模态对话框,如果注册时,指定了对话名称,则根据名称在IOC的对话容器中查找。否则就根据对话类型的名称进行查找。至于对话的父类窗口,默认情况下会自动使用Prism内置的默认窗口对象。

  • Show(string name):的用法与ShowDialog(string name)用法一样,只不过展示的是非模态对话框。

  • 具体代码

    public class MainWindowViewModel:BindableBase
    {
        private string _btnContent = "跨域弹窗";
    
        public string BtnContent
        {
            get { return _btnContent; }
            set 
            {
                SetProperty(ref _btnContent, value);
            }
        }
    
    		//IOC依赖注入
        [Dependency]
        public IDialogService _dialogService { get; set; }
    
        public ICommand BtnCommand 
        {
            get => new DelegateCommand(() =>
            {
    					  //展示窗口
                _dialogService.ShowDialog("DialogContentView");
            });
        }
    }
    

⑤、主窗口中绑定命令

  • 具体代码

    <Window ......>
        <Grid>
            <StackPanel>
                <Button Content="{Binding BtnContent}" Command="{Binding BtnCommand}"/>
            </StackPanel>
        </Grid>
    </Window>
    

2、注册父类窗口

在上面的实现过程中,我们只创建了用户控件,而用户控件的父类容器,即弹出来的窗口对象则使用了Prism中默认的Dialog窗口。也因此,默认情况下,如果需要对弹出的窗口进行设置,要在用户控件中通过prism:Dialog.WindowStyle来进行默认窗口的样式设置。

然而通过prism:Dialog.WindowStyle来进行窗口的样式设置的做法本来就不是很符合xaml的设计规范,而且这只能对当前的用户控件起效果,如果有多个控件希望进行统一的窗口样式处理的话,在每个用户控件中都编写一样的代码会显得很冗余。较好的解决方案就是重新创建并设置好一个父类窗体,然后注册到IOC的对话窗口容器中。

成功为对话设置了父类窗口后,父类窗口的样式就可以跟平常的WPF窗口一样直接在XAML中进行设置了。其在XAML中设置的样式的优先级高于prism:Dialog.WindowStyle

①、创建父类窗体

在程序集中新建DialogBase文件夹(文件夹名称并不重要,甚至可以不用创建)并在其中创建DialogWindowBase窗口。

在后台代码实现IDialogWindow,简化一下Result属性

  • 具体代码

    xaml代码

    <Window ......
            WindowStartupLocation="CenterScreen" Background="Transparent"
            ResizeMode="NoResize"
            Title="DialogWindowBase" Height="300" Width="500">
        <WindowChrome.WindowChrome>
            <WindowChrome GlassFrameThickness="-1"/>
        </WindowChrome.WindowChrome>
        <Grid>
    
        </Grid>
    </Window>
    

    后台代码

    public partial class DialogWindowBase : Window, IDialogWindow
    {
        public DialogWindowBase()
        {
            InitializeComponent();
        }
    
        public IDialogResult Result { get; set; }
    }
    

②、注册父类窗体类型

通过RegisterDialogWindow进行对话父类窗口类型的注册后,会优先使用,而不会自动使用默认的对话窗口类型对象。

RegisterDialogWindow<TWindow>([string name]):注册对话的父类窗口类型。

  • 当注册多个父类窗口类型时,可以传入name方便后面展示时通过key来指定窗口类型,需要注意注册时一旦指定了name,展示时就必须要指定name,否则找不到对应的父类窗口对象。

需要注意的是,要进行注册的对话父类窗口的后台代码中都必须实现IDialogWindow接口,否则无法注册。

  • 具体代码

    public partial class App : PrismApplication
    {
        protected override Window CreateShell()
        {
            return Container.Resolve<Views.MainWindow>();
        }
    
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            //注册一个对话类型
            containerRegistry.RegisterDialog<DialogContentView>();
            //注册一个对话父类窗口类型
            containerRegistry.RegisterDialogWindow<DialogWindowBase>();
            //还可以注册多个父类窗口类型,然后根据key来指定
        }
    }
    

③、展示窗口

通过IOC依赖注入,获得IDialogService属性对象。

定义命令,在命令中调用IDialogService对象的ShowDialog方法来展示串口。

  • 当注册的对话父类窗口类型只有一个且没有指定名称时,prism会自动去使用,因此只要指定对话类型即可。

  • 具体代码

    public class MainWindowViewModel:BindableBase
    {
        ......
        [Dependency]
        public IDialogService _dialogService { get; set; }
    
        public ICommand BtnCommand 
        {
            get => new DelegateCommand(() =>
            {
                _dialogService.ShowDialog("DialogContentView");
            });
        }
    }
    
    

3、指定父类窗体

当注册了多个父类窗体时,可以通过名称来区分。

①、注册多个父类窗口

  • 具体代码

    public partial class App : PrismApplication
    {
        protected override Window CreateShell()
        {
            return Container.Resolve<Views.MainWindow>();
        }
    
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            //注册一个对话类型
            containerRegistry.RegisterDialog<DialogContentView>("DialogOne");
            containerRegistry.RegisterDialog<DialogContentView>("DialogTwo"); //这里为了方便就不再创建一个新的用户控件了
            //注册一个对话父类窗口类型
            containerRegistry.RegisterDialogWindow<DialogWindowBase>("windowBase");
            //可以注册多个父类窗口类型,然后根据key来指定
            containerRegistry.RegisterDialogWindow<DialogWindowOne>("windowOne");
        }
    }
    

②、指定展示

需要注意的是,如果注册的对话指定了名称,展示时没有使用匹配的名称会导致找不到而报异常;如果注册的窗体类型都指定了名称,展示时没有指定名称则会使用prism框架中默认的窗体类型。

  • 具体代码

    public class MainWindowViewModel:BindableBase
    {
        ......
    
        [Dependency]
        public IDialogService _dialogService { get; set; }
    
        public ICommand BtnCommand 
        {
            get => new DelegateCommand(() =>
            {
    						//指定展示的对话及父类窗体
                _dialogService.ShowDialog("DialogOne",null,null, "windowBase");
            });
        }
    }
    

4、数据传递

当需要向弹出窗体的内容(即我们创建的用户控件)传递一些数据的时候,可以通过ShowDialog方法来实现。

①、传递数据

ShowDialog(string name, IDialogParameters parameters, Action<IDialogResult> callback, string windowName):展示对话窗口。

  • parameters:向对话窗口传递的数据对象。

DialogParametersIDialogParameters的子类,用法跟字典是一样的,可以用DialogParameters对象封装数据后传递给对话窗口。

  • 具体代码

    public class MainWindowViewModel:BindableBase
    {
        ......
    
        [Dependency]
        public IDialogService _dialogService { get; set; }
    
        public ICommand BtnCommand 
        {
            get => new DelegateCommand(() =>
            {
    						//创建数据载体
                var dialogParameters = new DialogParameters();
                dialogParameters.Add("paramKey", "paramValue");
    						//展示窗口并进行数据传递
                _dialogService.ShowDialog("DialogOne", dialogParameters, null, "windowBase");
            });
        }
    }
    

②、数据处理

传递的数据对象会在窗口打开时,传递给对应的IDialogAware子类对象(一般就是弹出窗口的ViewModel)的OnDialogOpened方法。因此,可以在OnDialogOpened方法中对数据进行处理。

  • 具体代码

    public class DialogContentViewModel : BindableBase,IDialogAware
    {
        ......
    
        public void OnDialogOpened(IDialogParameters parameters)
        {
            PassValue = parameters.GetValue<string>("paramKey");
        }
    
        private string _passValue;
    
        public string PassValue
        {
            get { return _passValue; }
            set 
            { 
                 SetProperty(ref _passValue, value);
            }
        }
    }
    

5、回调函数

当弹出的窗口关闭时需要执行回调函数,也是通过ShowDialog方法来实现的。

①、设置回调函数

ShowDialog(string name, IDialogParameters parameters, Action<IDialogResult> callback, string windowName):展示对话窗口。

  • callback:对话窗口的回调函数,为一个接收IDialogResult类型参数的Action对象。设置后会自动挂载到对应的IDialogAware子类对象(一般就是弹出窗口的ViewModel)RequestClose事件上,也就是可以通过RequestClose事件来调用callback回调函数。

  • 需要注意的是RequestClose事件的触发,会先调用窗口的关闭函数,再执行回调函数。对话窗口的正常关闭也会执行回调函数。

  • 具体代码

    public class MainWindowViewModel:BindableBase
    {
    		......
        [Dependency]
        public IDialogService _dialogService { get; set; }
    
        public ICommand BtnCommand 
        {
            get => new DelegateCommand(() =>
            {
                var dialogParameters = new DialogParameters();
                dialogParameters.Add("paramKey", "paramValue");
                _dialogService.ShowDialog("DialogOne", dialogParameters, DialogCallBack, "windowBase");
            });
        }
    
        //对话窗口的回调函数
        private void DialogCallBack(IDialogResult result)
        {
            //可以根据传过来的result做业务逻辑处理
        }
    }
    

②、调用回调函数
在对应的ViewModel上定义命令,触发RequestClose事件。

  • 具体代码
    public class DialogContentViewModel : BindableBase,IDialogAware
    {
    		......
    		
        public event Action<IDialogResult> RequestClose;
        public ICommand CallBackCommand 
        {
            get => new DelegateCommand(() =>
            {
                DialogResult dialogResult = new DialogResult(ButtonResult.OK);
                RequestClose?.Invoke(dialogResult);
            });
        }
    }
    
  • 17
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
WPF Prism是一种用于构建可扩展且可维护的模块WPF应用程序的框架。在WPF Prism中,模块是应用程序的功能单元,可以根据需要进行加载和卸载。 下面是一个简单的WPF Prism模块动态加载的示例: 1. 创建一个名为ModuleA的WPF Prism模块项目。 2. 在ModuleA项目的App.xaml.cs文件中,添加以下代码: ``` protected override void RegisterTypes(IContainerRegistry containerRegistry) { // 注册ModuleA的服务 } protected override void OnInitialized() { // 初始化ModuleA } public void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) { moduleCatalog.AddModule(new ModuleInfo() { ModuleName = "ModuleA", ModuleType = typeof(ModuleAModule).AssemblyQualifiedName, InitializationMode = InitializationMode.WhenAvailable }); } ``` 3. 创建一个名为ModuleB的WPF Prism模块项目。 4. 在ModuleB项目的App.xaml.cs文件中,添加以下代码: ``` protected override void RegisterTypes(IContainerRegistry containerRegistry) { // 注册ModuleB的服务 } protected override void OnInitialized() { // 初始化ModuleB } public void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) { moduleCatalog.AddModule(new ModuleInfo() { ModuleName = "ModuleB", ModuleType = typeof(ModuleBModule).AssemblyQualifiedName, InitializationMode = InitializationMode.WhenAvailable }); } ``` 5. 在Shell项目的App.xaml.cs文件中,添加以下代码: ``` protected override void RegisterTypes(IContainerRegistry containerRegistry) { // 注册Shell的服务 } protected override Window CreateShell() { // 创建Shell窗口 } protected override void InitializeModules() { base.InitializeModules(); // 加载ModuleA和ModuleB模块 var moduleCatalog = (ModuleCatalog)ModuleCatalog; moduleCatalog.AddModule(typeof(ModuleAModule)); moduleCatalog.AddModule(typeof(ModuleBModule)); } ``` 通过以上的步骤,我们实现了ModuleA和ModuleB模块的动态加载。在应用程序启动时,Shell项目会加载ModuleA和ModuleB模块,并根据需要进行初始化和注销。 这样,我们就可以使用WPF Prism实现模块化的应用程序,并根据需求动态加载模块,提高应用程序的可扩展性和可维护性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SchuylerEX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值