Catel - 第 n 部分 7:Catel 2.x 有什么新特性






4.75/5 (5投票s)
本文介绍 Catel 2.x 的新功能
文章浏览器
- Catel - 第 n 部分 1:数据处理之道
- Catel - 第 n 部分 2:使用 WPF 控件和主题
- Catel - 第 3 部分 (共 n 部分):MVVM 框架
- Catel - 第 n 部分 4:使用 Catel 进行单元测试
- Catel - 第 5 部分 (共 n 部分):在 1 小时内构建一个 Catel WPF 示例应用程序
- Catel - 第 6 部分(
共 n 部分): WP7 的 Bing Maps 应用程序 - Catel - 第 n 部分 7:Catel 2.x 有什么新特性
- Catel - 第 n 部分 8:WP7 Mango 和相机单元测试
目录
- 引言
- 左键单击 gadget 并拖动以移动它。左键单击 gadget 的右下角并拖动以调整其大小。右键单击 gadget 以访问其属性。
- IoC 容器 / ServiceLocator
- MVVM 行为
- WP7 Mango 支持
- 接下来是什么?
1. 引言
欢迎阅读 Catel 系列文章的第七部分。如果您还没有阅读过 Catel 的前几篇文章,建议您阅读。它们有编号,因此很容易找到。
本文将介绍 Catel 2.x 版本中的所有新功能。Catel 2.0 于 2011 年 8 月 4 日发布,包含大量新功能和改进。本文将介绍最重要的更改和新功能。
有些功能非常重要,需要在本文中提及,但又不足以单独成章。因此,其中一些更改将在通用部分进行描述。
2. 通用
本章介绍一些重要到需要提及但又不需要单独成章的功能,因为解释相对简单。
2.1. 行为
Catel 现在提供了一些新的开箱即用的行为。Catel 的开发者通常习惯于使用 WPF 开发应用程序,但有时他们需要使用 Silverlight 编写软件。使用 Silverlight 编写应用程序的一个缺点是,WPF 中许多您习惯的功能在 Silverlight 中不可用。其中两个简单功能是缺少 `DoubleClick` 事件以及缺少在属性更改时更新绑定而不是在失去焦点时更新绑定的选项。
2.1.1. 双击命令
为了让开发者轻松地在 Silverlight 中使用这些“便利”功能,开发了两种新行为。`DoubleClickToCommand` 允许开发者为支持 `LeftMouseButtonDown` 事件的任何 `FrameworkElement` 启用 `DoubleClick` 事件(该事件不可用)。因此,如果需要添加 `DoubleClick` 事件,只需使用此行为即可。假设有一个 `Image` 控件代表徽标,用户应该能够双击它来编辑徽标。以下代码足以创建此行为。
<Image Source="/Catel.Demo;component/resources/images/catel.png"> <i:Interaction.Triggers> <catel:DoubleClickToCommand Command="{Binding EditLogo}" /> </i:Interaction.Triggers> </Image>
2.1.2. TextChanged 时更新绑定
我们经常收到的一个问题是如何在用户在文本框中输入内容时更新绑定。默认情况下,绑定仅在文本框失去焦点时更新。有时,此行为还不够,因为开发者希望在用户输入文本框时立即提供反馈。或者,另一个用法是带有筛选器的文本框,筛选器应在键入文本框后立即应用。
解决此问题的一种方法是在代码隐藏中订阅 `TextChanged` 事件并显式更新绑定。更好的选择是使用 Catel 附带的 `UpdateBindingOnTextChanged` 行为。
<TextBox Text="{Binding SearchParameter, Mode=TwoWay}"> <i:Interaction.Behaviors> <catel:UpdateBindingOnTextChanged /> </i:Interaction.Behaviors> </TextBox>
2.2. Silverlight 的 IPleaseWaitService 实现
新版本中最受请求的功能之一是 `IPleaseWaitService` 在 Silverlight 中的实现。该服务已为 WPF 实现,但在 Silverlight 中,最终用户需要自己实现 `IPleaseWaitService`。大多数开发者使用 `BusyIndicator` 控件来实现此功能。因此,现在包含一个使用 `BusyIndicator` 的 `IPleaseWaitService` 的默认实现。
2.3. 所有控件的单一命名空间
所有可以在 xaml 文件中使用的控件、窗口、行为等都位于不同的命名空间中,以尽可能遵循 .NET Framework。在 Catel 的先前版本中,需要在 xaml 文件中手动包含所有使用的命名空间。例如,xaml 文件的 using 可能如下所示:
xmlns:Controls="clr-namespace:Catel.Windows.Controls;assembly=Catel.Silverlight" xmlns:Interactivity="clr-namespace:Catel.Windows.Interactivity;assembly=Catel.Silverlight"
现在,所有命名空间都映射到 *http://catel.codeplex.com*。这意味着上面的代码仍然有效,但从现在开始,编写基于 Catel 的 xaml 会更容易。
xmlns:catel="http://catel.codeplex.com"
上面的代码足以访问 Catel 提供的所有可以在 xaml 中使用的控件、行为和其他元素。
2.4. 审计
如果您正在开发一个真实的应用,那么您可能想知道您的用户是如何使用您的应用的。Catel 通过提供添加审计功能来解决这个问题。审计可用于收集统计数据,查看实际使用了哪些功能。通过审计,您可以创建和注册自定义审计器,这些审计器可以处理视图模型中的更改和事件。这样,您可以收集大量统计数据或任何您想收集的关于用户体验的信息。以下是可以处理的事件列表:
- OnViewModelCreating
- OnViewModelCreated
- OnPropertyChanging
- OnPropertyChanged
- OnCommandExecuting
- OnViewModelSaving
- OnViewModelSaved
- OnViewModelCanceling
- OnViewModelCanceled
- OnViewModelClosing
- OnViewModelClosed
开发者可以自由地处理审计器中的一个或多个方法。当然,也可以有多个审计器。让我们看看如何轻松地为 MVVM 应用添加审计。
2.4.1. 创建审计器
创建新的审计器非常简单。创建一个新类,继承自 `AuditorBase` 并重写您感兴趣的方法。类示例将事件跟踪到假的分析框架。
/// <summary>
/// Logs all commands to a custom analytics service.
/// </summary>
public class CommandAuditor : AuditorBase
{
private Analytics _analytics = new Analytics();
/// <summary>
/// Called when a command of a view model has just been executed.
/// </summary>
/// <param name="viewModel">The view model.</param>
/// <param name="commandName">Name of the command, which is the name of the command property.</param>
/// <param name="command">The command that has been executed.</param>
/// <param name="commandParameter">The command parameter.</param>
public override void OnCommandExecuted(IViewModel viewModel, string commandName, ICatelCommand command, object commandParameter)
{
_analytics.TrackEvent(viewModel.GetType(), "commandName");
}
}
2.4.2. 注册审计器
注册新的审计器非常容易,如以下代码所示。
AuditingManager.RegisterAuditor(new CommandAuditor());
3. IoC 容器 / ServiceLocator
在 Catel 2.0 之前,内部使用的 IoC 容器是 Unity。但是,这强制所有用户在其应用程序中使用和配置 Unity 作为 IoC 容器,并且还需要打包相应的库。自 Catel 2.0 起,采用了一种不同的技术,允许最终用户选择自己喜欢的 IoC 容器技术。
3.1. ServiceLocator 简介
Catel 使用自己的 `ServiceLocator` 来实现 `IServiceLocator`,用于收集 Catel 所需的所有服务。例如,默认服务包括 `IPleaseWaitService` 和 `IUIVisualizerService`。默认情况下,当第一个视图模型被实例化时,Catel 会将所有默认的开箱即用服务注册到 `ServiceLocator`。但是,只有当特定服务尚未注册时,它才会这样做。这允许最终用户在任何视图模型被实例化之前(例如,在应用程序启动时)注册自己的服务实现。
`ServiceLocator` 可以被实例化,但 Catel 实例化一个可以被同一 `AppDomain` 中所有对象使用和共享的实例。可以使用 `ServiceLocator.Instance` 来检索 `ServiceLocator`。`ViewModelBase` 实现 `GetService`,该方法内部调用 `ServiceLocator.ResolveType`。这样,最终用户就不必考虑实例化或检索正确的 `ServiceLocator`,而只需使用 `GetService` 来检索特定已注册接口的实现。
3.2. 使用 ServiceLocator
使用 `ServiceLocator` 有多种可用操作。以下是所有可用操作。
3.2.1. 注册类型
在 `ServiceLocator` 中注册类型非常简单,与其他 IoC 容器的工作方式类似。
ServiceLocator.Instance.RegisterType<IPleaseWaitService, PleaseWaitService>();
3.2.2. 注册类型实例
Catel 使用 `Activator.CreateInstance` 在对象首次解析时创建接口实现。但是,有时服务构造函数需要参数或构造起来很耗时。在这种情况下,建议手动创建类型并注册该类型的实例。
var pleaseWaitService = new PleaseWaitService(); ServiceLocator.Instance.RegisterInstance<IPleaseWaitService>(pleaseWaitService);
3.2.3. 通过 MissingType 事件注册类型
`ServiceLocator` 为最终用户提供了一个最后的机会,在类型未在 `ServiceLocator` 或任何外部容器中注册时注册该类型。此事件对于日志记录非常有用(然后日志中的开发者可以确切地知道 IoC 容器缺少什么类型),或者它可用于在运行时非常晚的阶段确定必须使用哪个服务实现。要通过事件注册类型,请订阅该事件,然后使用以下代码:
private void OnMissingType(object sender, MissingTypeEventArgs e)
{
if (e.InterfaceType == typeof(IPleaseWaitService))
{
// Register an instance
e.ImplementingInstance = new PleaseWaitService();
// Or a type
e.ImplementingType = typeof(PleaseWaitService);
}
}
如果 `ImplementingInstance` 和 `ImplementingType` 都已填充,则将使用 `ImplementingIntance`。
3.2.4. 解析类型
要检索服务的实现,请使用以下代码。
var pleaseWaitService = ServiceLocator.Instance.ResolveType<IPleaseWaitService>();
3.3. 外部容器
如前所述,`ServiceLocator` 支持外部容器,以确保最终用户可以使用自己选择的 IoC 容器,而不会被 Catel 强制使用任何特定的 IoC 实现。注册容器非常简单,可以随时进行(但当然必须在 `ServiceLocator` 本身可以使用外部容器中注册的服务之前进行)。
ServiceLocator.Instance.RegisterExternalContainer(myUnityContainer);
注册一个外部容器可以使 `ServiceLocator` 解析最初在外部容器中注册的类型。例如,数据库存储库在 Unity 中注册,但必须在视图模型中使用。这就是发生的情况:
- 最终用户在 Unity 容器中注册 `ICustomerRepository`。
- 最终用户在 Catel 的 `ServiceLocator` 中注册外部容器。
- 在视图模型中,开发者使用 `GetService<ICustomerRepository>()` 来检索存储库。
- 在内部,`ServiceLocator` 检查哪个外部容器拥有该类型,并使用该特定容器返回该类型。
3.4. 容器之间的同步
到目前为止,一切看起来都很不错。但是,有一些方面非常困难,必须加以考虑。例如,请考虑以下问题:
- 实例缓存
- 哪个容器是类型的实际所有者?
3.4.1. 从外部容器同步到 ServiceLocator
在内部,`ServiceLocator` 维护内部容器中所有已注册类型、所有已实例化类型以及每个类型的原始容器的引用。例如,考虑以下情况:
- 类型在 Unity 中注册(但 `ServiceLocator` 不知道它是类型还是实例,因为 Unity 之间没有区别)。
- 开发者在容器中解析类型。
`ServiceLocator` 检查是否已经创建了实例或开发者之前请求过。如果它已经在缓存中有一个实例,它将简单地从缓存中返回该实例。如果没有,它会检查哪个容器(可能是它本身,也可能是外部容器之一)是该类型的拥有者。如果该类型首先在该特定容器上找到,则该容器被视为拥有者。`ServiceLocator` 让外部容器(在本例中是 Unity)实例化该类型。然后 `ServiceLocator` 将实例存储在实例缓存中,以便在下次调用时轻松解析。
3.4.2. 从 ServiceLocator 同步到外部容器
好的,现在考虑这种情况:
- 类型在 `ServiceLocator` 中注册。
- 该类型必须使用 Unity 注入到构造函数中。
这有点难,因为该类型未在 Unity 中注册,但 Unity 使用它。幸运的是,`ServiceLocator` 能够在类型注册或解析后自动将类型注册到所有外部容器。这样,当一个类型在 `ServiceLocator` 中注册时,它会自动调用 `ExternalContainer.RegisterType`(或者在实例的情况下调用 `ExternalContainer.RegisterInstance`)到所有外部容器。这样,就可以在 `ServiceLocator` 中注册一个类型,但仍能使用 Unity 强大的依赖注入功能。
3.4.3. 自动或手动同步
默认情况下,`ServiceLocator` 会自动保持所有 IoC 容器同步,因此最终用户无需了解 `ServiceLocator` 的内部工作原理。有时,开发者想要更多的控制,并且需要禁用此自动行为。这可以使用以下属性来实现:
ServiceLocator.Instance.AutomaticallyKeepContainersSynchronized = false;
然后仍然可以使用以下方法手动同步容器:
- `ExportInstancesToExternalContainers` => 仅导出实例
- `ExportToExternalContainers` => 导出实例和已注册的类型
3.5. 支持的外部容器
以下外部 IoC 容器开箱即用支持:
IoC 容器 | 引入版本 | 备注 |
Unity | 2.0 | 完全按照 Unity 的设计方式实现。 |
MEF | 2.1 | MEF 仅支持实例。因此,一旦 MEF 容器被注册为外部容器,所有类型的实例将被创建并在外部 MEF 容器中注册。这样,就可以使用 MEF 来导入 `ServiceLocator` 或任何其他外部容器(如 Unity)公开的所有服务。 |
4. MVVM 行为
Catel 的一个缺点是需要将所有窗口和控件都派生自 `DataWindow<TViewModel>` 或 `UserControl<TViewModel>`。虽然这些控件非常强大和灵活,但有时无法派生自通用控件。例如,一家公司可能已经有一个所有控件都必须派生自的基类。
Catel 包含 WPF 和 Silverlight 的演示应用程序,展示了派生自通用视图基类与使用行为之间的区别。查找 `Catel.Examples.WPF.AdvancedDemo` 或 `Catel.Examples.SL4.AdvancedDemo`。
将逻辑与 `DataWindow<TViewModel>` 和 `UserControl<TViewModel>`(为简单起见,以下统称为 `ViewBase<TViewModel>`)分离是 Catel 2.0 发布最重要的目标之一。这个想法很简单:创建行为,允许最终用户将行为应用于任何控件或窗口,以获得与 `ViewBase<TViewModel>` 相同的强大行为。但是,由于泛型基类内部相当复杂,存在一些陷阱。
团队首先将逻辑与类分离到新的 `Logic` 类中。这些类包含应用于必须注入到 `Logic` 类中的外部控件的逻辑。由此产生了包含实际逻辑的 `UserControlLogic` 和 `WindowLogic` 类。`UserControl<TViewModel>` 和 `DataWindow<TViewModel>` 仅创建 `Logic` 类的实例,并将实际实现分派给 `Logic` 类。
第一步成功后,就该进行第二步了:创建可以附加到任何 `UserControl` 或 `Window` 的行为,而无需泛型基类。`UserControl` 实现相对简单,因为除了视图模型类型之外,与控件没有实际交互。`Window` 实现要困难得多,因为外部窗口应该能够使用 UI 控件保存、取消和关闭视图模型。接下来的部分将解释如何使用不同的行为。
4.1. WindowBehavior
`WindowBehavior` 允许开发者在任何窗口中提供 `DataWindow` 的 MVVM 行为。这是对“常规”MVVM 实现的极其强大的补充。要在窗口上启用该行为,只需在窗口定义内的以下 xaml 代码即可:
<i:Interaction.Behaviors> <catel:WindowBehavior x:Name="mvvmBehavior" ViewModelType="ViewModels:DemoWindowViewModel" SaveAndClose="saveAndCloseButton" CancelAndClose="cancelAndCloseButton" /> </i:Interaction.Behaviors>
`WindowBehavior` 上只需要一个属性:`ViewModelType`。`ViewModelType` 属性应填写视图被实例化时应创建的视图模型。在上面的示例中,`DemoWindowViewModel` 将在窗口创建时立即被实例化。然后,该视图模型将自动设置为窗口的 `DataContext`。
行为上还有一些其他(可选)属性:
属性 | 描述 |
保存 | 调用视图模型上的 `SaveViewModel`。 |
SaveAndClose | 调用视图模型上的 `SaveViewModel`,如果 `SaveViewModel` 返回 `true`,则还关闭窗口。 |
取消 | 调用视图模型上的 `CancelViewModel`。 |
CancelAndClose | 调用视图模型上的 `CancelViewModel`,但也会关闭窗口。 |
Close | 关闭窗口而不调用 `SaveViewModel` 或 `CancelViewModel`。 |
4.2. UserControlBehavior
`UserControlBehavior` 允许开发者在每个用户控件中提供 `UserControl` 的 MVVM 行为。`UserControlBehavior` 类负责用户控件和视图模型的所有 MVVM 集成。因此,以前您必须从 `UserControl<TViewModel>` 派生 `UserControl` 实现,现在您可以像任何应用程序一样创建一个新的 `UserControl`,然后添加此:
<i:Interaction.Behaviors> <catel:UserControlBehavior x:Name="mvvmBehavior" ViewModelType="ViewModels:DemoViewModel" /> </i:Interaction.Behaviors>
这看起来不错,但为什么还有 `UserControl<TViewModel>` 存在这个绝妙的解决方案?
首先,我们必须考虑所有已经在使用 Catel 的人。我们不想破坏他们的代码并提供向后兼容性。此外,`UserControl<TViewModel>` 实现 `IViewModelContainer` 接口,该接口允许在层次结构中链接视图模型。如果您不需要此功能,只需使用行为。如果您需要层次结构链,请让自定义 `UserControl` 实现它,或者使用 `UserControl<TViewModel>`。
为了支持嵌套用户控件及其验证,使用 `IViewModelContainer` 接口将视图链接在一起很重要。您可以选择不这样做,但出于性能原因,禁用 `SupportParentViewModelContainers` 很重要(否则,行为将继续在视觉树中搜索父视图模型)。
5. WP7 Mango 支持
Catel 2.x 完全支持 Windows Phone 7 Mango。周围的大多数 MVVM 框架都乐于宣布对 WP7 Mango 的支持,但 Catel 更进一步(嗯,甚至更多)。
5.1. 所有硬件功能均可模拟
Microsoft 为 Windows Phone 7 Mango 添加了一个新的 SDK。Windows Phone 模拟器可以选择模拟位置服务(GPS)。当 Microsoft 表明他们实现了对加速度计和位置服务的支持时,我非常高兴。但是,指南针和陀螺仪怎么样?关于这些传感器(您也想模拟它们,对吧?)没有任何话。
我们决定构建的一件事是为 Windows Phone 7 设备上 **所有** 可用传感器构建一个测试实现。这样,您就可以在 MVVM 方式的开发计算机上的模拟器中模拟所有传感器。
这意味着 Catel 中提供了以下服务(包括模拟服务):
- IAccelerometerService
- ICompassService
- IGyroscopeService
- ILocationService (GPS)
- IVibrateService
6. 下一步是什么?
自 2010 年 12 月 Catel 发布以来,它比任何其他 MVVM 框架/工具包都 **功能更全面**。我们仍然充满想法,并尽最大努力实现这些想法。在此期间,如果您有任何评论、想法、改进或任何其他内容,请随时发表评论或与我们联系。我们很乐意听取您的反馈。