Prism中的事件聚合器相当于MvvmToolkit中的Messenger
,可以进行订阅、发布等。
在Prism框架中,有一些类型对象是一开始就内置在IOC容器中的,例如接下来用到的IEventAggregator
事件集合器对象。
IEventAggregator
:Prism框架中的内置于IOC容器中的事件聚合器类型,通过IEventAggregator
对象可以进行事件的注册以及从IOC容器中获取事件对象。
常用成员
TEventType GetEvent
:获取容器中指定类型的事件对象。
EventBase
类型的子类,一般会通过继承EventBase
的子类PubSubEvent
或PubSubEvent
来创建事件类型。PubSubEvent
:EventBase
的子类,可以通过泛型来指定发布的信息类型。
常用成员
SubscriptionToken Subscribe(Action
:PubSubEvent
的实例方法,传入有参的Action
委托(事件执行方法),来订阅事件。
Publish(TPayload payload)
:PubSubEvent
的实例方法,发布事件,TPayload
发布的消息对象,要与创建事件类型时,继承PubSubEvent
所指定的泛型类型一致。
Unsubscribe(Action
:PubSubEvent
的实例方法,取消订阅,只有在订阅时keepSubscriberReferenceAlive
参数为true
才需要调用,默认情况下是不需要的。
PubSubEvent
:EventBase
的子类,可以定义无参事件。
常用成员
SubscriptionToken Subscribe(Action action)
:PubSubEvent
的实例方法,传入无参Action
委托(事件执行方法),来订阅事件。
Publish()
:PubSubEvent
的实例方法,发布事件。
Unsubscribe(Action subscriber)
:PubSubEvent
的实例方法,取消订阅,只有在订阅时keepSubscriberReferenceAlive参数为true
才需要调用,默认情况下是不需要的。
创建事件类型
一般会在程序集中新建Events文件夹,将事件类型统一放在里面。
如果仅仅是为了发布,而不需要携带任何信息,可以直接继承非范型的PubSubEvent
即可。
public class MessageEvent:PubSubEvent<string>
{
}
ViewModel层中订阅、发布事件
通过IEventAggregator
的GetEvent
注册或获取IOC容器中指定类型的事件对象。
通过PubSubEvent
的Subscribe(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>
在订阅时,可以通过Subscribe
方法,进行以下设置。
设置执行线程
Subscribe(Action
:threadOption用于设置action在哪种线程上调用,为ThreadOption
枚举。
ThreadOption.PublisherThread
:在发布者的执行线程上执行action。ThreadOption.UIThread
:不管在哪个线程发布,action都在UI线程中执行。ThreadOption.BackgroundThread
:不管在哪个线程发布,action都在新建的后台线程中执行。订阅卸载
Subscribe(Action
:keepSubscriberReferenceAlive用于设置当窗口关闭时,是否保持订阅,默认为false
,也就是当窗口关闭时就会取消action对该事件的订阅,下次打开窗口又重新订阅。设置为true
时,则保持订阅,会增加一丢丢效率,注意设置为true后要主动调用Unsubscribe
方法才可以取消订阅。一般使用默认的false
就可以了。
消息过滤器
Subscribe(Action
:filter用于对发送的消息进行过滤,返回true
时顺利发送,否则拦截。filter是一个返回bool
值的带参委托。
_eventAggregator.GetEvent<MessageEvent>().Subscribe(EventHandle, (msg) => msg.Contains("我想过滤啥就过滤啥"));
在开发过程中,有些情况需要跨模块弹出窗口,接下来学习一下在View层以外的地方实现窗口的弹出。
①、创建用户控件
跨模块的窗口弹出,只需要创建窗口的内容即可,也就是用户控件,这里是在Views文件夹下,创建DialogContentView用户控件。
需要注意的是,默认情况下,如果需要对弹出窗口进行样式设置的话,需要通过prism:Dialog.WindowStyle
来进行设置。
具体代码
<UserControl x:Class="Zhaoxi.PrismDialog.Views.DialogContentView"
xmlns:prism="http://prismlibrary.com/"
......>
<prism:Dialog.WindowStyle>
<Style TargetType="Window">
"Width" Value="500"/>
"Height" Value="400"/>
"WindowChrome.WindowChrome">
"-1"/>
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
:注册对话类型,可以传入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>
在上面的实现过程中,我们只创建了用户控件,而用户控件的父类容器,即弹出来的窗口对象则使用了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
:注册对话的父类窗口类型。
需要注意的是,要进行注册的对话父类窗口的后台代码中都必须实现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");
});
}
}
当注册了多个父类窗体时,可以通过名称来区分。
①、注册多个父类窗口
具体代码
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");
});
}
}
当需要向弹出窗体的内容(即我们创建的用户控件)传递一些数据的时候,可以通过ShowDialog
方法来实现。
①、传递数据
ShowDialog(string name, IDialogParameters parameters, Action
:展示对话窗口。
DialogParameters
:IDialogParameters
的子类,用法跟字典是一样的,可以用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);
}
}
}
当弹出的窗口关闭时需要执行回调函数,也是通过ShowDialog
方法来实现的。
①、设置回调函数
ShowDialog(string name, IDialogParameters parameters, Action
:展示对话窗口。
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);
});
}
}