Caliburn.Micro 中的事件聚合器:实现松耦合通信
在构建基于 MVVM 模式的 WPF 应用程序时,不同视图模型之间的通信是一个常见需求。当组件数量增多、结构变复杂时,直接引用会导致高耦合,难以维护。Caliburn.Micro 提供了一个轻量级但功能强大的解决方案——事件聚合器(Event Aggregator),它允许对象通过发布和订阅消息的方式进行解耦通信。
本文将带你完成一个示例:创建两个独立的视图模型,其中一个负责发送颜色变更指令,另一个接收并更新界面显示。整个过程不依赖任何直接引用,完全通过事件聚合机制实现交互。
1. 创建第二个视图与视图模型
为了演示跨视图模型通信,我们需要至少两个 ViewModel。现有项目中已有 AppViewModel,现在添加一个新的类:
[Export(typeof(ColorViewModel))]
public class ColorViewModel : PropertyChangedBase
{
private readonly IEventAggregator _eventAggregator;
[ImportingConstructor]
public ColorViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
}
}
同时创建对应的视图 ColorView.xaml,并在其中放置三个单选按钮:
<UserControl x:Class="YourNamespace.ColorView">
<StackPanel Margin="20">
<RadioButton Name="SelectRed" Content="红色" Foreground="White"/>
<RadioButton Name="SelectGreen" Content="绿色" Foreground="White" Margin="0,10"/>
<RadioButton Name="SelectBlue" Content="蓝色" Foreground="White"/>
</StackPanel>
</UserControl>
接下来修改主视图 AppView.xaml,将其布局分为两列,左侧显示颜色选择控件,右侧显示颜色反馈区域:
<Grid Width="400" Height="200" Background="LightGray">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ContentControl Grid.Column="0" Name="ColorViewModel" Margin="10"/>
<Rectangle Grid.Column="1" Width="100" Height="100" Fill="{Binding CurrentBrush}"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
注意:ContentControl 的名称与 AppViewModel 中的属性名匹配,框架会自动完成绑定。
2. 配置依赖注入容器
由于我们使用了构造函数注入,需确保引导程序正确配置 MEF 容器。更新 AppBootstrapper 类如下:
public class AppBootstrapper : Bootstrapper<AppViewModel>
{
private CompositionContainer _container;
protected override void Configure()
{
_container = new CompositionContainer(
new AggregateCatalog(AssemblySource.Instance
.Select(asm => new AssemblyCatalog(asm)))
);
var batch = new CompositionBatch();
batch.AddExportedValue<IWindowManager>(new WindowManager());
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
batch.AddExportedValue(_container);
_container.Compose(batch);
}
protected override object GetInstance(Type serviceType, string key)
{
return _container.GetExportedValues<object>(
AttributedModelServices.GetContractName(serviceType)
).FirstOrDefault() ?? throw new InvalidOperationException();
}
}
同时为 AppViewModel 添加导出标记和导入构造函数:
[Export(typeof(AppViewModel))]
public class AppViewModel : PropertyChangedBase, IHandle<ColorChangedMessage>
{
private Brush _currentBrush = Brushes.White;
public ColorViewModel ColorViewModel { get; private set; }
[ImportingConstructor]
public AppViewModel(ColorViewModel colorVm, IEventAggregator events)
{
ColorViewModel = colorVm;
events.Subscribe(this); // 订阅消息
}
public Brush CurrentBrush
{
get => _currentBrush;
set => Set(ref _currentBrush, value);
}
public void Handle(ColorChangedMessage message)
{
CurrentBrush = message.NewBrush;
}
}
3. 定义消息类型
定义一个简单的数据承载类用于传递颜色信息:
public class ColorChangedMessage
{
public Brush NewBrush { get; }
public ColorChangedMessage(Brush brush)
{
NewBrush = brush;
}
}
该类仅包含必要的数据字段,不涉及任何业务逻辑或 UI 操作,符合"消息即数据"的设计原则。
4. 发布事件
回到 ColorViewModel,为其添加命令方法以响应按钮点击,并发布颜色变更消息:
public void SelectRed() => PublishColor(Brushes.Red);
public void SelectGreen() => PublishColor(Brushes.Green);
public void SelectBlue() => PublishColor(Brushes.Blue);
private void PublishColor(Brush brush)
{
_eventAggregator.PublishOnUIThread(new ColorChangedMessage(brush));
}
这里使用 PublishOnUIThread 确保 UI 更新发生在主线程,避免跨线程异常。
5. 运行效果
启动应用程序后,左侧显示三个单选按钮,右侧显示一个矩形。点击任一按钮,事件被发布,AppViewModel 接收到消息并更新 CurrentBrush 属性,触发 UI 自动刷新。
值得注意的是,虽然本例中直接传递了 Brush 对象,但在实际开发中更推荐传递原始值(如字符串或枚举),并通过值转换器(IValueConverter)在视图层转换为可视化资源,从而进一步降低耦合度。