解耦弹出窗口实现与事件聚合器模式

在软件开发中,解耦是一个重要的概念,它有助于提高代码的可维护性和可测试性。本文将介绍如何使用SilverlightPrism框架来实现弹出窗口的解耦,并展示如何通过事件聚合器模式进行单元测试

创建消息载体

首先,需要构建一个消息载体,用于传递弹出窗口的消息。消息载体的定义如下:

public class MessageBoxPayload { public object DataContext { get; set; } public bool AllowCancel { get; set; } public string Title { get; set; } public string Message { get; set; } public Action<MessageBoxPayload> ResultHandler { get; set; } public bool Result { get; set; } private MessageBoxPayload() { } public static MessageBoxPayload GeneratePayload(object dataContext, bool allowCancel, string title, string message, Action<MessageBoxPayload> resultHandler) { MessageBoxPayload retVal = new MessageBoxPayload { AllowCancel = allowCancel, Title = title, Message = message, ResultHandler = resultHandler, DataContext = dataContext }; return retVal; } }

在这个载体中,不假设任何状态,而是通过一个Action来传递载体类型,允许请求者提供一个方法,在对话框关闭时被调用。

定义事件

接下来,定义一个事件,用于请求弹出消息框。这个事件基于Prism提供的事件机制:

public class MessageBoxEvent : CompositePresentationEvent<MessageBoxPayload> { }

这里继承了CompositePresentationEvent,这是一个所有参与事件聚合器服务的事件的基类。

发布事件

现在可以轻松地开始发布事件。例如,如果有一个带有删除按钮的数据网格,事件可能看起来像这样:

private void Button_Delete_Click(object sender, System.Windows.RoutedEventArgs e) { Button src = e.OriginalSource as Button; if (src != null) { _eventService.GetEvent<MessageBoxEvent>().Publish( MessageBoxPayload.GeneratePayload(src.DataContext, true, "请确认", "确定要删除这个项目吗?", DeleteItem)); } } public void DeleteItem(MessageBoxPayload payload) { if (payload.Result) { MyItem item = payload.DataContext as MyItem; Delete(item); } }

在这个例子中,删除点击事件将消息包装成载体并发布事件。提供了一个委托来回调"DeleteItem",以获取对话框的结果。如果用户确认,则使用数据上下文来获取给定行的实体,并执行删除命令。

事件服务的定义

_eventService的定义如下:

private readonly IEventAggregator _eventService;

IEventAggregator是在视图的构造函数中作为参数提供的,它由依赖注入框架自动连接。要将事件聚合器传递到对象中,需要两个步骤。

注册事件聚合器

首先,在引导程序中注册一个单一实例:

protected override void ConfigureContainer() { base.ConfigureContainer(); // 提供事件聚合器的引用 Container.RegisterInstance<IEventAggregator>(Container.Resolve<EventAggregator>()); }

这里使用"resolve"来获取实例。这是在使用依赖注入/控制反转框架时的良好实践。当创建对象时,必须选择适当的构造函数并注入适当的值。通过调用Resolve,要求容器根据当前的配置和注册提供实现。

注入服务到适当的处理程序

接下来,将服务注入到适当的弹出窗口处理程序中。最简单的方法是创建一个公共模块。该模块可以包含在项目之间共享的元素。首先,将创建一个新的子窗口并称之为"Popup"。XAML代码如下:

<controls:ChildWindow x:Class="Modules.Common.Views.Popup" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls" Width="Auto" Height="Auto" Title="{Binding Title}"> <Grid x:Name="LayoutRoot" Margin="2"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <TextBlock TextWrapping="Wrap" Text="{Binding Message}" FontFamily="Arial" FontSize="12" TextAlignment="Center" Grid.Row="0"/> <Button x:Name="CancelButton" Content="取消" Click="CancelButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1"/> <Button x:Name="OKButton" Content="确定" Click="OKButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1"/> </Grid> </controls:ChildWindow>

主要需要注意的是使用Auto来确保对话框根据内容调整大小。接下来是代码背后的实现。

弹出窗口的实现

弹出窗口的构造函数接受载体并设置数据上下文,然后设置取消按钮的可见性。然后,按钮的事件绑定将设置适当的结果,然后调用对话框关闭事件的处理器。

public partial class Popup { public Popup() { InitializeComponent(); } public Popup(MessageBoxPayload payload) { InitializeComponent(); DataContext = payload; CancelButton.Visibility = payload.AllowCancel ? Visibility.Visible : Visibility.Collapsed; } private void OKButton_Click(object sender, RoutedEventArgs e) { DialogResult = true; MessageBoxPayload result = (MessageBoxPayload)DataContext; result.Result = true; result.ResultHandler(result); } private void CancelButton_Click(object sender, RoutedEventArgs e) { DialogResult = false; MessageBoxPayload result = (MessageBoxPayload)DataContext; result.Result = false; result.ResultHandler(result); } }

这里的关键是一个接受载体的构造函数,并设置数据上下文,然后设置取消按钮的可见性。然后,按钮的事件绑定将设置适当的结果,然后调用对话框关闭事件的处理器。

如何将视图引入应用程序

如果这是一个真正解耦的模块,它将如何"知道"定义的区域?此外,即使迭代区域集合并将它注入到找到的第一个区域,会发现一个奇怪的、空的弹出窗口只是挂在那里。这不是想要的!为了管理这个弹出窗口,将使用一个控制器。

弹出窗口控制器的实现

弹出窗口控制器看起来像这样:

public class PopupController { public PopupController(IEventAggregator eventService) { eventService.GetEvent<MessageBoxEvent>().Subscribe(PopupShow); } public void PopupShow(MessageBoxPayload payload) { Popup popupWindow = new Popup(payload); popupWindow.Show(); } }

现在可以设置模块来调用控制器。

模块的初始化

公共模块的初始化如下:

public class CommonModule : IModule { private readonly IUnityContainer _container; public CommonModule(IUnityContainer container) { _container = container; } public void Initialize() { _container.RegisterInstance(_container.Resolve(typeof(PopupController))); } }

注意没有使用区域进行注册。相反,只是解析控制器的单一实例。这将订阅弹出窗口事件。因为使用容器来解析控制器,容器将自动引用EventAggregator并将其注入到构造函数中。最后,这个模块只需要在模块目录中注册:

模块目录的注册

模块目录的注册如下:

protected override IModuleCatalog GetModuleCatalog() { ModuleCatalog catalog = new ModuleCatalog(); catalog.AddModule(typeof(MySpecificModule)); catalog.AddModule(typeof(CommonModule)); return catalog; }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485