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

Caliburn.Micro 中的视图组件与组合模式详解

访客 技术 2026年7月29日 1

在 Caliburn.Micro 框架中,Screen、Conductor 与 Composition 是构建复杂可维护用户界面的核心机制。虽然 Actions、Coroutines 与 Conventions 也极具价值,但深入理解 Screen 与 Conductor 的协作关系,对实现清晰的 UI 架构至关重要。

这些概念源自 Jeremy Miller 在《Presentation Patterns》一书中提出的架构思想,在 CM 框架中通过继承特定基类或实现接口来具体化。它们应被视为"角色"而非普通视图模型(ViewModel),因为一个 Screen 可能对应一个 UserControl、Presenter,甚至是一个独立的业务逻辑单元。


一、核心概念解析

1. Screen(屏幕)

代表应用中具有状态的独立交互单元,如编辑器窗口、设置对话框或页面视图。它不依赖于主外壳(Shell),可独立存在并拥有自己的生命周期。

每个 Screen 支持激活(Activate)与停用(Deactivate)操作。例如,在 Visual Studio 中切换代码标签页时,工具栏图标随之变化——这正是由当前活跃 Screen 控制的。这种行为通常通过自定义激活/停用逻辑实现。

  • 关键接口
  • IActivate:提供 Activate()IsActive 属性及 Activated 事件。
  • IDeactivate:支持带布尔参数的 Deactivate(bool close) 方法,以及 AttemptingDeactivationDeactivated 事件。
  • IGuardClose:定义异步关闭检查逻辑,接受一个回调函数用于通知是否允许关闭。

此外,还包含辅助接口如:

  • IHaveDisplayName:支持显示名称绑定。
  • INotifyPropertyChangedEx:增强版属性变更通知,支持线程安全与强类型更新。
  • IViewAware:处理视图关联,包括 AttachViewGetViewViewAttached 事件。

2. Conductor(导引者)

负责管理多个 Screen 的激活状态流转。当切换内容时,Conductor 判定目标项是否可激活,并处理前一项的停用或移除。

其核心职责包括:

  • 激活指定项目:ActivateItem()
  • 停用并可选择关闭项目:DeactivateItem(close: bool)
  • 提供当前活动项引用:ActiveItem
  • 查询所有跟踪项:GetChildren()

值得注意的是,Conductor 并不要求其管理的项必须实现 IScreen 接口。只要对象具备所需生命周期接口(如 IActivate, IGuardClose 等),即可被纳入控制范围。

3. Screen Collection(屏幕集合)

用于维护一组打开的 Screen 实例,常见于多文档界面(MDI)或标签页式应用。该集合保持唯一活跃项,其余处于非激活状态。

典型场景下,新文档打开时加入集合并变为活动项;关闭时则从集合中移除。是否真正关闭取决于 CanClose 逻辑的返回结果。同时,关闭后需决定下一个激活项。


二、实际实现方式

1. 基于框架的基类与接口

Caliburn.Micro 提供了以下便捷基类与接口:

  • PropertyChangedBase:自动处理属性变更通知,支持 lambda 式更新,且确保事件在线程上下文中触发。
  • BindableCollection<T>:继承自 ObservableCollection<T>,实现 IObservableCollection<T>,并保证所有变更事件均在主线程执行。
  • IScreen:聚合了 IHaveDisplayNameIActivateIDeactivateIGuardCloseINotifyPropertyChangedEx
  • Screen:继承 PropertyChangedBase 并实现 IScreen,同时支持 IChildIViewAware

使用建议:若需完整生命周期管理,优先继承 Screen;否则使用 PropertyChangedBase

重写方法说明:
  • OnInitialize():仅首次激活时调用,初始化完成后 IsInitialized 为真。
  • OnActivate():每次激活后执行。
  • OnDeactivate():关闭或停用时调用,返回值决定是否仍保留在内存中。
  • CanClose():默认允许关闭,可覆盖以添加验证逻辑。
  • OnViewLoaded():在视图加载完成后触发,适用于需要访问视图实例的场景。
  • TryClose():尝试关闭当前屏幕,会触发 CanClose 逻辑,并通知 Conductor 进行处理。

2. Conductor 实现策略

框架内置三种标准 conductor:

  • Conductor<T> 通用导引者,支持强类型操作。激活新项时,旧项将被停用并关闭。适用于单页面导航。

  • Conductor<T>.Collection.OneActive 维护一个集合,允许多个项存在,但仅一个处于激活状态。关闭某项需显式调用 CloseItem()

  • Conductor<T>.Collection.AllActive 允许同时激活多个项。关闭某项仅使其失效并从集合中移除。

⚠️ 注意:若子项在未激活的 Conductor 内被激活,实际上不会立即生效,直到 Conductor 自身被激活为止。这是常见误解来源。

此外,所有内置 conductor 均继承自 Screen,这意味着它们自身也具备生命周期,且会级联影响所管理的子项。例如,当一个 conductor 被停用时,其所有子项也将自动停用;若尝试关闭 conductor,所有可关闭的子项也会被一并处理。


3. 非传统导引者:伪导引者(Quasi-Conductors)

并非所有生命周期管理都来自显式 Conductor。以下情况也扮演类似角色:

  • Bootstrapper:引导程序虽非 Conductor,但会主动调用根 ViewModel 的激活逻辑。
  • WindowManager:负责模态窗口的生命周期管理,类似于轻量级 Conductor。
  • NavigationService(WP7/Silverlight):CM 提供适配器(如 PhoneBootstrapper)将其桥接到 ViewModel 层,使导航过程可响应 IGuardClose 等接口。

因此,任何具备小生命周期接口的对象,均可被框架识别并参与管理。


三、实战示例:简单导航壳层

以下演示如何使用 Conductor<object> 构建基础导航结构。

public class ShellViewModel : Conductor<object>
{
    public ShellViewModel()
    {
        ShowPageOne();
    }

    public void ShowPageOne()
    {
        ActivateItem(new PageOneViewModel());
    }

    public void ShowPageTwo()
    {
        ActivateItem(new PageTwoViewModel());
    }
}

对应的视图定义如下:

<UserControl x:Class="SimpleNavigation.ShellView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:tc="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit">
    <tc:DockPanel>
        <StackPanel Orientation="Horizontal"
                   HorizontalAlignment="Center"
                   tc:DockPanel.Dock="Top">
            <Button x:Name="ShowPageOne" Content="跳转到第一页" />
            <Button x:Name="ShowPageTwo" Content="跳转到第二页" />
        </StackPanel>
        <ContentControl x:Name="ActiveItem" />
    </tc:DockPanel>
</UserControl>

此设计实现了典型的"点击按钮 → 切换视图"的导航模式,其中 ActiveItem 自动绑定至当前激活的 ViewModel。


四、总结

  • Screen 是可独立存在的有状态单元,拥有完整的生命周期。
  • Conductor 是状态协调者,负责管理多个 Screen 之间的激活/停用流程。
  • Composition 是通过集合与嵌套结构构建复杂界面的方式,支持多层级、多模式布局。
  • 使用 Screen 作为基类可获得开箱即用的生命周期支持。
  • 合理利用 Conductor<T>.Collection.OneActive 可轻松实现标签页或多文档界面。
  • 所有生命周期元素必须由某个顶层容器(如 Bootstrapper、WindowManager、Conductor)启动,否则无法正常工作。

掌握这些机制,便能在 Caliburn.Micro 中构建出结构清晰、易于扩展的现代桌面或移动应用。

相关文章

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...

发表评论

访客

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