当前位置:首页 > 技术 > 正文内容

Caliburn.Micro 中的事件聚合器:实现松耦合通信

访客 技术 2026年5月26日 3

在构建基于 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)在视图层转换为可视化资源,从而进一步降低耦合度。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。