介绍 Calcium for Xamarin.Forms





5.00/5 (29投票s)
使用 Calcium for Xamarin.Forms 创建一流的跨平台 MVVM 应用程序。
系列介绍
Xamarin.Forms 是 Xamarin 的新跨平台 UI 框架,允许您使用 XAML 为 iOS、Android 和 Windows Phone 构建用户界面。
在本系列中,您将学习如何使用 Xamarin Forms 做一些在其他地方找不到的事情。例如,您将看到如何使用 Xamarin Forms 原生渲染 API 创建一个跨平台应用程序栏组件,该组件支持 iOS、Android 和 Windows Phone 上的菜单项,并且比内置的 Windows Phone 应用程序栏提供更多功能。
您将学习如何将图像像 Windows Phone 一样放置在共享项目中的任何位置作为内容资源,并以相同的方式在所有三个平台中使用它们。
您将学习如何使用 T4 模板为您的 Android 应用程序启用本地化。您还将看到如何使用 T4 生成的设计器类创建与 .resx 文件的静态绑定;允许您以相同的方式在所有三个平台中本地化您的应用程序。
您将看到如何超越当前的 Xamarin Forms API 功能,实现准数据模板选择器,以 MVVM 兼容的方式实例化视图模型。
您将学习如何组装您的 Visual Studio 项目,以在使用新的共享项目类型共享代码时减少摩擦。
此外,在本系列文章中,您将了解使用 Xamarin.Forms 开发应用程序的基础知识。您将学习如何使用 Calcium MVVM 框架通过 Xamarin Forms 构建多方面的跨平台应用程序。
如果您已经有一些 Xamarin Forms 的经验,那么本文可能非常适合您。如果您在 WPF、Silverlight 或 WinRT 的 UI 开发方面拥有 XAML 经验,并且了解 MVVM、控制反转 (IoC) 和依赖注入 (DI);那么我认为这篇文章绝对值得一读。
- 第1部分。介绍 Calcium for Xamarin Forms (本文)
- 第2部分。使用 MVVM 和 Calcium for Xamarin Forms 创建选项卡式界面
- 第3部分。使用 Calcium for Xamarin Forms 构建可本地化的跨平台应用程序
- 第4部分。使用 Calcium for Xamarin Forms 创建跨平台应用程序栏
- 第5部分。使用 Calcium for Xamarin Forms 在共享资产项目中解析图像
- 第6部分。使用 Calcium for Windows Phone 创建用户选项页面
本文内容
在本文中,您将了解 Calcium 的起源及其一些主要功能。您将看到如何安装 Calcium NuGet 包。您将了解如何创建 Xamarin Forms 共享项目,以及如何使用 Bootstrapper 初始化您的应用程序。您将看到如何使用 XAML 创建一个基本页面,以及如何将该页面绑定到视图模型。最后,您将看到 Calcium 的一些默认服务的概述,包括对话服务、设置服务和用户选项服务。
背景
Xamarin.Android 和 Xamarin.iOS 是创建这两个平台应用程序的两个截然不同的工具。它们各自拥有自己的一套用于创建用户界面的 API,这些 API 镜像了底层平台的 API。就跨平台开发而言,我怀疑 Xamarin 的重点从来都不是将这两种技术用作创建跨平台应用程序的手段。在我看来,其目的是允许开发人员利用他们的 C# 技能为 iOS 或 Android 进行开发;而不是两者兼顾。
我一直希望,我相信许多其他开发人员也一样,Xamarin 能够承担起提供统一 UI 层的工作。现在 Xamarin 确实做到了。此外,随着 Xamarin 和 Microsoft 的合作,我们看到 Windows Phone 也加入了支持平台的行列。
正如您在本系列文章中看到的,Xamarin 在提供统一 UI 层方面取得了长足的进步,但是 iOS、Android 和 Windows Phone 之间存在差异,这在开发跨平台应用程序时可能会造成摩擦。这些差异包括本地化差异、资源和图像解析差异以及在 XAML 中管理程序集 XML 命名空间。然而,尽管存在这些差异,在我看来,Xamarin Forms 为创建原生跨平台移动应用程序提供了最好的工具。在本系列文章中,您将看到一些方法来克服一些现有的痛点,例如在不牺牲结构的情况下在项目之间共享图像,并避免 iOS 和 Android 的单一图像目录要求。
什么是 Calcium?
Calcium 最初是一个 MVVM 框架,也是 Prism(前身为 Composite Application Library)的扩展。时代变了,Calcium 此后大部分放弃了对 Prism 的依附(桌面版除外),Prism 中利用的大部分内容都已在 Calcium 自身中重新实现(或因效率不高而被放弃)。Calcium 最初是一个 WPF MVVM 框架,但后来开始涉足移动领域,特别是 Windows Phone,现在是 Xamarin Forms。
Calcium 使我能够在两天内为 Windows Phone 构建 Intellicam,并且是 Surfy 的支柱,Surfy 可以说是 Windows Phone 上最好的第三方网络浏览器。
Calcium 随着时间的推移不断发展壮大。以至于有时我会忘记其中有多少精彩之处。事实上,只有写这样一篇文章,我才会被提醒过去几年积累的价值。是的,它很大,而且相当成熟;我于 2009 年开始开发 Calcium。但多年来它一直被周期性地揉捏和增强,并用于相当多的商业应用程序中。
在多年的 MVVM 应用程序开发过程中,我将那些我认为固有有用的东西整合到 Calcium 中。我开始将 Calcium 作为一个框架来编写。与许多其他有良好意图的框架一样,一开始它是这样的:假定价值第一,现实价值第二。但后来,随着时间的推移,这种情况发生了变化,我所构建的应用程序中最好的想法逐渐进入了 Calcium;事后。Calcium 用于商业产品中,并已开发用于解决生产应用程序中面临的常见挑战。然而,Calcium 不受特定领域代码的束缚。
Calcium 不仅仅是一个 MVVM 框架。它包含一系列组件,这些组件组合在一起,解决了您日常面临的所有常见应用程序开发挑战。Calcium 几乎包含了您期望成熟 MVVM 框架所具备的所有关键功能。但在许多地方,我认为它提供的特性超越了您可能更熟悉的框架。以下是一些示例:
增强的命令支持
Calcium 的 ICommand 实现使用参数类型值强制转换。例如,枚举类型可以在 XAML 中用作命令参数,并且在它们到达您的视图模型时会自动转换为正确的类型。这意味着您可以在 XAML 中利用泛型 ICommand,并且无需对参数值进行假设或执行类型转换。这项工作受到了 Pete Blois 关于隐式类型强制转换的出色工作的启发。
UICommand 提供 Visibility、Text 和 Icon 属性,这些属性可以根据命令逻辑所封装的逻辑进行更改。它们还与许多控件协同工作,包括 Calcium for Xamarin Forms 的 AppBar 和 Calcium 的用户选项系统。您将在接下来的多篇文章中看到这些功能的示例。
无摩擦的 INotifyPropertyChanged (INPC) 基础设施
Calcium 的 INPC 系统具有自动 UI 线程亲和性。如果需要,从非 UI 线程修改视图模型(ObservableObject)中的属性会自动在 UI 线程上引发属性更改事件。此外,PropertyChangeNotifier 可以应用于任何类,并使用弱引用来防止内存泄漏。此外,在 PropertyChanged 事件之前会引发 PropertyChanging 事件,以便其他组件有机会取消更改。
设置服务
Calcium 的设置服务允许您在应用程序运行的任何平台上保存设置。它包括允许您取消设置更改和在设置更改发生前收到通知的通知。它还提供了序列化值、复合对象,甚至通过 IXmlConvertible 接口自定义对象序列化的方法。
选项系统
Calcium 的选项系统使您能够快速为应用程序创建用户选项页面。可以仅用一行代码添加分类选项。模板控制选项类型的显示方式。选项系统与设置服务协同工作,自动将更改写回持久存储。
控制反转和依赖注入
Calcium 包含一个内置的 IoC 容器,支持依赖注入;基于属性的构造函数和属性注入,并且是可互换的。如果 Calcium 的 IoC 容器或 Microsoft Unity 不符合要求,您可以为您希望使用的任何实现创建适配器。
跨平台对话服务
Calcium 的 DialogService 具有异步 API,为每个平台实现,并具有可扩展性模型,允许您向用户显示对话框并轻松接收用户反馈。
可扩展字符串解析器
Calcium 包含一个可扩展的字符串解析器服务,您可以为其创建可识别的标签,这些标签在运行时进行处理以将信息插入字符串中。这提供了创建动态可本地化字符串的能力。
应用程序的撤销重做堆栈
Calcium 包含一个撤销重做任务系统,该系统允许应用程序的用户撤销操作或重做该操作。
弱引用消息系统
弱引用消息系统,称为 Messenger,使用声明式接口方法,允许对象接收应用程序中另一个组件的事件通知。包含一组标准消息类,允许您将有效负载对象发送给侦听器,甚至取消消息。
集合,例如 ObservableList
Calcium 包括 ObservableList 等集合,允许您推迟集合更改事件以执行批量更新,而不会导致 UI 控件的多次更新并减慢 UI 线程。
输入验证系统
Calcium 包含一个基于属性的异步输入验证系统,允许您轻松验证表单。我尚未为 Xamarin Forms 中的验证错误可视化创建 UI。
ViewModel 状态持久化系统
Calcium 允许您使用基于属性的系统或编程属性注册系统,在 Tombstoning(Windows Phone)期间保持应用程序的状态。我尚未为 Xamarin Forms 连接此功能。
现在,大部分基础设施都可以在 Windows、Windows Phone 以及借助 Xamarin iOS 和 Android 上使用。本系列文章涵盖了 Calcium 中包含的内容,并且是未来 Calcium for Xamarin Forms 中不可避免会包含的内容的预览。
要求
这些文章是从同时拥有 Xamarin iOS 和 Android 商业安装的角度撰写的。如果您只有其中一个,那么您可以忽略不适用于您的许可证的部分。如果您拥有 Indie 许可证,那么 Visual Studio 特定指南将不适用于您。尽管如此,您仍然可以利用 Calcium 代码库。
Xamarin 工具在 Windows 上运行良好。然而,事实是,如果您想构建 iOS 应用程序,您需要了解 OSX。事实上,Mac 是构建 iOS 应用程序的必备条件。我本科期间和作为 Java 程序员的早期职业生涯中大量使用 Linux,所以去年我的办公桌上有一台新的 Mac Book 感觉很熟悉也很有趣。如果您打算进入 iOS 开发领域,我希望您也有类似的体验。
Calcium 和示例应用程序的源代码
Calcium for Xamarin.Forms 及示例的源代码位于 https://calcium.codeplex.com/SourceControl/latest
存储库中有各种解决方案。您感兴趣的解决方案位于 `\Trunk\Source\Calcium\Xamarin\Installation` 目录中,名为 _CalciumTemplates.Xamarin.sln_。
CalciumTemplates.Xamarin 解决方案的结构如图 1 所示。您可以看到示例“模板”项目已被突出显示。这些项目包含了本文系列中呈现的大部分示例源代码。
图 1。CalciumTemplates.Xamarin 解决方案的结构
在我们了解如何将 Calcium 与 Xamarin Forms 结合使用之前,让我们首先简要介绍一下入门指南。
Xamarin Forms 入门
让我们从创建一个新的 Xamarin 移动共享应用程序开始。如果愿意,您可以选择可移植库方法,但在本系列文章中,我将使用共享项目类型。图 2 显示了 Visual Studio 新项目对话框,其中选择了共享资产项目类型。
图 2。创建新的 Xamarin Forms 项目。
如果您同时安装了 Xamarin.iOS 和 Xamarin.Android,选择 Xamarin Mobile Shared App 会创建四个项目;每个平台一个:Windows Phone、iOS 和 Android,以及一个共享项目,该项目会自动将共享项目中的所有文件链接到特定于平台的项目。我建议不要将特定于平台的代码放入可启动的特定于平台的项目。我知道这听起来很奇怪。我之所以这样说,是因为移动操作系统更新可能频繁发生,您可能会发现在单个特定于平台的项目中尝试支持两个或更多版本的 SDK。我最近在 Windows Phone 8.1 上就遇到了这种情况。链接文件很麻烦,这就是为什么新的共享项目类型对于这种类型的工作来说是福音。
在 Visual Studio 中开发跨平台应用程序时,您可以选择花时间调试 iOS 项目、Android 项目(取决于您的许可证)或 Windows Phone 项目。Visual Studio 对 Windows Phone 的工具支持优于 Android 和 iOS 工具。这是可以预料的,微软在 Windows Phone 开发工具上投入了大量资金。使用 Windows Phone 模拟器时,调试的部署和启动时间要短得多。因此,如果您出于任何原因考虑放弃 Windows Phone 项目并将精力集中在另一个平台上,我建议不要这样做;如果仅仅是为了生产力。
避免 XML 命名空间混乱
在 XAML 文件中设置 XML 命名空间时,务必注意您的程序集名称。如果您希望跨多个平台定位类库,那么 XAML 文件中的 XML 命名空间定义必须包含程序集的名称。在这种情况下,XAML 文件必须在 xmlns 定义中包含程序集部分,因此如果您打算重用自定义控件,则为特定于平台的项目使用相同的程序集名称非常重要。这是使用共享项目而非可移植类库项目的最大缺点。当您的类型位于可移植库中时,您无需担心项目之间的命名空间差异;使用共享项目则需要。尽管如此,我仍然更喜欢共享项目类型的灵活性。
提示。我通常喜欢统一跨平台的程序集和命名空间,因为我发现这使得共享代码更简单。
当更改新生成的 Windows Phone 项目中的默认命名空间时,您将收到与生成的 LocalizedResources 类相关的构建错误。在即将发布的一篇文章中,您将看到如何在应用程序中实现本地化,该本地化在所有三个平台上都是兼容的。您将不需要 LocalizedResources.cs 文件。
如果您希望在 resx 文件中共享本地化资源,统一命名空间也是一个重要步骤。您将在稍后的部分中看到如何共享本地化资源。
注意:虽然 Xamarin Forms 包含一组有用的内置机制,允许您定位和排除特定平台,但没有任何机制可以更改 XAML 文件中的 XML 命名空间定义。Xamarin Forms 应用程序不提供将 CLR 命名空间与 XML 命名空间定义进行编程映射的方法,例如使用 XmlnsDefinitionAttribute,就像在 WPF 或 Silverlight 应用程序中一样。否则,使用编译符号有条件地向程序集添加 XmlnsDefinition 属性将是轻而易举的事。我希望未来 Xamarin 能在这方面提供更多解决方案。
与此缺失的 XML 命名空间映射功能相关的是 Xamarin Forms 中缺乏智能感知。我发现目前编写 Xamarin Forms 应用程序的挑战之一是 Visual Studio XAML 编辑器中缺乏智能感知。
安装 Calcium NuGet 包
当我当年开始研究 Calcium 时,NuGet 并不存在。我花费了大量时间制作自定义 Visual Studio 安装程序。结果却发现它们在每个新版本的 Visual Studio 中都会损坏。因此,为了减少我的精神衰退速度,我现在使用 NuGet 来分发 Calcium。
要安装 Calcium,请右键单击新创建的 Xamarin 解决方案,然后选择“管理解决方案的 NuGet 包...”
图 3 显示了“管理 NuGet 包”对话框。选择“Calcium for Xamarin Forms (所有平台)”项。如果您只针对特定平台,那么您可能需要更具体地选择您选择的包。
您可能会注意到我正在使用“LocalFeed”源作为 NuGet 包。当您阅读本文时,这些包将在 nuget.org 上提供。此外,根据您阅读本文的时间,您可能还需要从下拉列表中选择“包含预发布”选项。
图 3。选择“管理解决方案的 NuGet 包”
请注意,这些包是仅包含程序集的包。您的项目中不会放置其他任何工件。这意味着您可以添加和删除包,而不用担心项目内容被搞乱。但我不能对依赖包说同样的话。我相当不喜欢那些选择安装图标之类的包。我说的是 Windows Phone Toolkit。;)
图 4 显示了用于选择 Calcium 包的“管理 NuGet 包”对话框。
图 4。选择 Xamarin Forms (所有平台)
默认的 Xamarin 模板会为您提供一个共享的 App 类。该类有一个名为 GetMainPage 的方法,如清单 1 所示。GetMainPage 方法是应用程序 Xamarin.Forms 部分的共同入口点,分别由 iOS、Android 和 Windows Phone 项目使用。
清单 1。App.GetMainPage 方法
public class App
{
public static Page GetMainPage()
{
return new ContentPage
{
Content = new Label {
Text = "Hello, Forms !",
VerticalOptions = LayoutOptions.CenterAndExpand,
HorizontalOptions = LayoutOptions.CenterAndExpand,
},
};
}
}
Xamarin Forms 将从 GetMainPage 方法返回的 Xamarin Page 实例转换为平台特定类型。见清单 2。
顺便说一句,我惊讶地看到这是如何在幕后实现的。例如,对于 Windows Phone 平台,Xamarin 平台实例通过隐式运算符转换为 UIElement。我有点困惑为什么采用这种方法。我猜是为了方便,允许“平台”代码集成到原生 UIElement 中,同时允许 Xamarin Forms IPlatform 实现保持内部。但是,在撰写本文时,IPlatform 在 Windows Phone 库中是公共的。
清单 2。Forms.ConvertPageToUIElement 方法
public static UIElement ConvertPageToUIElement(this Page page, PhoneApplicationPage applicationPage)
{
Platform platform = new Platform(applicationPage);
platform.SetPage(page);
return (UIElement) platform;
}
除了好奇心之外,我启动 Reflector 并深入研究 Xamarin Forms 代码的第一个原因是为了找到一种方法来获取 Xamarin Forms Platform 对象。你看,我之前在 Calcium 中的导航方法一直依赖于一个可以独立于 Page 模型执行页面导航的对象(例如 Frame)。事实证明,在 Windows Phone 上运行时,你可以检索 IPlatform 对象的实例,其中有一个 INavigation 对象。然而,Android 并非如此。当我发现这一点时,我很快意识到不同“原生”导航模型的平台意味着全局处理导航不容易实现。于是,我改变了策略。我不得不转而将 Xamarin Forms VisualElement 的 INavigation 对象连接到每个视图模型,当 VisualElement(页面)创建时。这与我所希望的有所不同,但它有效。
Calcium 的 ViewBinderExtensions 类的 BindToInfrastructure 方法接受一个 VisualElement,并使用 IViewBinder 实现将 ViewModel 的 Navigation 属性绑定到页面的 Navigation 属性。见清单 3。
清单 3。ViewBinderExtensions 类
public static class ViewBinderExtensions
{
public static void BindToInfrastructure(this VisualElement visualElement)
{
var viewBinder = Dependency.Resolve<IViewBinder, ViewBinder>();
viewBinder.BindToInfrastructure(visualElement);
}
}
BindToInfrastructure 方法的基础是 IViewBinder 对象。BindToInfrastructure 扩展方法使用 ViewBinder 类的实例通过反射将视图的 Navigation 属性设置为 ViewModel 的 Navigation 属性。参见清单 4。如果 VisualElement 的 Navigation 属性不为 null,并且数据绑定到 VisualElement 的对象具有 INavigation 类型的 Navigation 属性,则设置该属性。
将 INavigation 对象附加到视图模型也可以通过数据绑定实现;将 Navigation 属性绑定到视图模型。但是,我选择了不采用这种方法,因为它感觉过于复杂,而且诊断绑定失败错误比逐步执行方法要困难得多。
在 BindToInfrastructure 方法中,我们还将页面的 IsBusy 和 Title 属性绑定到 Calcium 的 ViewModelBase 类中的相应属性。巧合的是,我不需要修改 Calcium 来支持这一点,因为这些属性已经存在于 Calcium 的 ViewModelBase 类中。
如果您希望以与默认 ViewBinder 类不同的方式进行操作,您可以使用 Dependency 系统注册您自己的 IViewBinder 对象。
注意。 如果您尝试同时为许多 VisualElement 调用 BindToInfrastructure,您可能需要考虑缓存 PropertyInfo 值以提高性能。如果您正在这样做,请告诉我。
清单 4。ViewBinder.BindToInfrastructure 方法
public void BindToInfrastructure(VisualElement visualElement)
{
var viewModel = visualElement.BindingContext;
if (viewModel != null && visualElement.Navigation != null)
{
INavigation navigation = visualElement.Navigation;
if (navigation != null)
{
Type viewModelType = viewModel.GetType();
PropertyInfo propertyInfo = viewModelType.GetProperty(
"Navigation", typeof(INavigation));
if (propertyInfo != null)
{
propertyInfo.SetValue(viewModel, navigation);
}
Dependency.Register<INavigation>(navigation);
}
}
if (visualElement is Page)
{
visualElement.SetBinding(Page.IsBusyProperty, new Binding("Busy"));
visualElement.SetBinding(Page.TitleProperty, new Binding("Title"));
}
}
在 Xamarin Forms 中构建 XAML 页面
碰巧,我不太喜欢用代码构建界面。我喜欢 Xamarin Forms 的一个主要原因在于它允许我使用 XAML 以声明方式标记我的 UI。因此,让我们用 XAML 创建一个登陆页面。您可以通过创建一个新的 ContentPage 来实现,这在 Visual Studio 的“添加新项”对话框中显示为“Forms Xaml Page”。这会生成一个新的 XAML 页面,它类似于您在 WPF、Silverlight 或 WinRT 项目中看到的 XAML 页面。参见清单 5。
默认模板包含一个 ContentPage 根元素和一个 Label,该 Label 数据绑定到页面的绑定上下文的 MainText 属性。
清单 5。新创建的 Xamarin Forms ContentPage
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CalciumSampleApp.Views.MainView">
<Label Text="{Binding MainText}" VerticalOptions="Center" HorizontalOptions="Center" />
</ContentPage>
Xamarin Forms 中的绑定
如果您还不熟悉 XAML 中的数据绑定,数据绑定通过将绑定上下文(可以是任何对象)分配给视觉元素(BindableObject)来工作。然后,您可以在 XAML 中使用绑定表达式来获取和设置绑定上下文的属性值。此外,绑定上下文还允许元素从父元素继承绑定上下文。
注意:在 Xamarin.Forms 中,BindableObject 的 BindingContext 属性类似于 WPF 或 Silverlight 中 FrameworkElement 的 DataContext 属性。
了解 Xamarin Forms 页面类层次结构
ContentPage 通常是应用程序中定义页面的首选。此外,NavigationPage 可用于指示 Xamarin Forms 该页面参与导航,并且应为 iOS 和 Android 渲染导航栏。Windows Phone 放弃了屏幕导航,转而仅依靠硬件返回按钮进行导航。
Xamarin Forms CarouselPage 允许您显示零个或多个子页面并在它们之间滑动,就像画廊一样。TabbedPage 也这样做,但 TabbedPage 还为用户提供一个导航控件,用于在页面之间移动。在 Windows Phone 中,TabbedPage 呈现为 Pivot,而 CarouselPage 呈现为 Panorama。您将在第 2 部分:《使用 MVVM 和 Calcium for Xamarin Forms 创建选项卡式界面》中更详细地了解 TabbedPage,以及如何使用 Calcium 将 TabbedPage 绑定到视图模型集合,每个视图模型代表一个不同的视图,这在 Xamarin Forms 中并非开箱即用。
Xamarin Forms API 使将任何页面转换为 NavigationPage 变得容易,只需将您的页面包装在 NavigationPage 实例中即可。实现方式如下:
ContentPage page = new MyPage();
NavigationPage navigationPage = new NavigationPage(page);
Xamarin Forms 可视化树中存在的对象都派生自 BindableObject。页面也是如此。参见图 4。
图 4。页面类层次结构
在 XAML 中定义了视图后,我们可以返回共享项目中的 App 类。现在,我们不再通过代码构建页面,而是能够创建并返回 MainView 页面的实例。参见清单 6。
Calcium 的 Dependency 类用于解析 MainView 类的实例。我们在这里使用控制反转,以便视图自动填充其所需的任何服务,并且我们依靠依赖注入 (DI) 将 MainViewModel 的实例传递给视图的构造函数。
清单 6。新的 App.GetMainPage 方法。
public class App
{
public static Page GetMainPage()
{
var bootstrapper = new Bootstrapper();
bootstrapper.Run();
MainView result = Dependency.Resolve<MainView>();
return new NavigationPage(result);
}
}
MainView 页面的伴随代码文件包含两个构造函数:一个无参数构造函数和一个需要 MainViewModel 实例的构造函数。Calcium DI 引擎根据以下标准选择要使用的构造函数:
- 如果构造函数用 InjectDependencies 属性修饰,则它具有最高优先级。
- 否则,选择参数最多的公共构造函数。
- 如果不存在公共构造函数,SimpleContainer 会尝试使用内部构造函数。
注意。在 Windows Phone 中,由于安全限制,如果类型与调用代码不在同一程序集中,则禁止实例化带有内部构造函数的类型(会引发 SecurityException)。我倾向于在可能的情况下,对类型采用内部可见性而不是公共可见性。原因是大多数我使用过的混淆器在安全隐藏内部类型的名称方面做得更好、更可靠。
您会注意到我保留了一个默认的无参数构造函数,期望 Xamarin 在某个时候会发布一个 XAML 文件的设计器界面,这可能需要一个无参数构造函数。
检查 MainView 构造函数,您会看到对 InitializeComponent 的调用(参见清单 7)。这是一个自动生成的方法,存在于以您的 XAML 文件命名(例如 MainView.xaml.g.cs)的文件中,必须由用户代码调用。文件名中的“g”代表生成。其目的是从程序集中检索和加载 XAML。
页面的 BindingContext 设置为视图模式。您之前看到的 BindToInfrastructure 扩展方法用于将 INavigation 对象传递给视图模型,并在视图和视图模型之间附加一些默认绑定。
清单 7。MainView 构造函数被注入 MainViewModel 的实例
public MainView(MainViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
this.BindToInfrastructure();
Appearing += HandleAppearing;
}
Page 类的 Appearing 事件可用于执行在页面实例化后但在用户界面中具体化之前应发生的活动。相反,有一个 Disappearing 事件,当页面离开视图时发生。这些事件类似于 WPF 和 Silverlight 中存在的 VisibilityChanged 或 Loaded 事件。
注意。Appearing 和 Disappearing 事件可能会发生多次。例如,当您导航回以前在 UI 中显示的页面时,会发生 Appearing 事件。还要注意这些事件发生的顺序。Disappearing 事件发生在当前页面中,在导航到的页面中发生 Appearing 事件之后。
使用 Bootstrapper 初始化应用程序
在示例应用程序中,您会看到我已将 GetMainPage 方法的内容替换为清单 6 的内容。在主页面解析之前,会调用 Bootstrapper 类的 Run 方法。
Bootstrapper 类提供了一种集中管理应用程序启动过程的方法。在 Calcium 中,Bootstrapper 用于初始化 IoC 容器,注册 IoC 和 DI 系统使用的默认类型映射,并执行任何其他与启动相关的任务。我们稍后会更仔细地研究这个过程。
一旦 Bootstrapper 完成运行,MainView 将使用 Calcium 的 Dependency 类的静态 Resolve 方法进行解析。
在本节中,我们简要回顾 Bootstrapper 的活动,并概述注册到 Calcium IoC 容器的类型。
Xamarin.Forms 有自己的 IoC 容器,您可能想知道为什么要使用 Calcium 的容器而不是它?好吧,如果您愿意,可以自由使用 Xamarin 的容器。Calcium 允许您替换底层 IoC 容器。在内部,Calcium 的默认 DI 和 IoC 系统使用一个名为 SimpleContainer 的类,它实现了 Calcium 的 IContainer 接口。因此,您可以自由地为您希望使用的任何 IoC 容器实现适配器。Calcium 包含一个用于 Unity 的适配器,当然还有默认容器。我发现默认容器绰绰有余。
在清单 6 中,您看到 MainView 对象在从未进行显式类型注册的情况下被解析。因为类型参数是具体类型,如果 IoC 容器无法解析注册的类型,那么它会实例化一个实例,并自动解析它所需的任何依赖项。
Bootstrapper 类的一个关键职责是初始化 IoC 容器。您可以使用 InitializeContainer 扩展方法向 IoC 和 DI 系统注册 IContainer,如下所示:
SimpleContainer container = new SimpleContainer();
container.InitializeContainer();
在幕后,这会使用 SimpleContainer 实例初始化 Microsoft.Practices.ServiceLocation.ServiceLocator,并扩展它以提供一些在 Microsoft ServiceLocator API 中明显缺失的其他服务注册功能。
Bootstrapper 中注册服务的概述
容器注册后,Bootstrapper 会注册许多默认服务,我们现在简要检查一下。
设置服务
Calcium 抽象了每个平台应用程序设置的存储方式。它通过使用 SettingsStore 类的平台特定实例初始化 Calcium SettingsService 来实现这一点。请参见以下摘录:
SettingsStore settingsStore = new SettingsStore();
SettingsService settingsService = new SettingsService(settingsStore);
Windows Phone 的 SettingsStore 使用 IsolatedStorageSettings API。Android 使用 ISharedPreferences API,iOS 使用自定义 IsolatedStorageSettings API 和 Silverlight Serializer;顺便说一句,这使得序列化几乎任何东西都变得轻而易举。ISettingsStore 接口如清单 8 所示。
清单 8。ISettingsStore 接口。
public interface ISettingsStore
{
bool TryGetValue(string key, out object value);
bool Contains(string key);
bool Remove(string key);
Task Clear();
Task Save();
object this[string key] { get; set; }
}
SettingsService 类调用 ISettingStore 实现,并提供一些超出 ISettingsStore 实现的序列化功能和对象类型强制转换。此外,SettingsServices 提供了一个设置更改事件和一个可取消的设置更改事件。更改通知可以使用常规 CLR 事件或 Calcium 的弱引用消息系统接收。您稍后会看到更多关于此主题的内容。
对话服务
接下来在 Bootstrapper 中注册的是 DialogService。DialogService 是一个异步用户消息系统,允许您使用抽象的 API 向用户显示消息对话框和吐司,并向用户提问。同样,每个平台都有一个特定的实现。
例如,要向用户提出一个简单的“是/否”问题,您可以这样做:
bool response = await DialogService.AskYesNoQuestionAsync(
"Allow app to run under the lock screen?",
"Lock Screen");
DialogService 的优点之一是它可以通过简单地重写 DialogServiceBase 类中的两个方法来扩展以覆盖新平台或修改新行为。对于特定场景,我甚至创建了一个命令行版本的 DialogService。
当 DialogService 在 Bootstrapper 中初始化时,如果您愿意,可以提供默认的对话框标题,如下所示:
var dialogService = new DialogService
{
/* Lambda expressions are used to retrieve the default message service captions,
* which allows the language to be changed at run-time. */
DefaultMessageCaptionFunc = () => AppResources.DialogService_DefaultMessageCaption,
};
Dependency.Register<IDialogService>(dialogService);
DialogService 允许您使用本地化字符串覆盖默认标题。暂时忽略此本地化方面。在第 3 部分:《使用 Calcium for Xamarin Forms 构建可本地化跨平台应用》中将有一个大篇幅描述跨平台本地化。
UI 同步上下文
Calcium 中的几个组件依赖于能够向 UI 线程发布和发送操作。Calcium 使用 UIContextProvider 的平台特定实现,该实现能够为每个平台检索同步上下文。它在 Bootstrapper 中注册,如下所示:
Dependency.Register<IProvider<ISynchronizationContext>, UIContextProvider>();
要在 UI 线程上调用操作,您始终可以这样做:
var context = Dependency.Resolve<IProvider<ISynchronizationContext>>().ProvidedItem;
context.InvokeWithoutBlocking(() => { DoSomethingOnTheUIThread(); });
向弱引用侦听器发送消息
像其他一些 MVVM 框架一样,Calcium 具有弱引用消息系统。在 Calcium 中,Messenger 类使用基于声明式接口的方法来订阅通知。如果一个对象希望收到事件通知,它会实现 IMessageSubscriber<T> 接口。其中 T 表示消息类型。例如,ViewModel 类,它是您应用程序的可自定义基础视图模型,实现了 IMessageSubscriber<ApplicationLifeCycleMessage>
当您的应用程序启动、停用、退出等时,应用程序中的任何订阅者都会收到通知。请注意,虽然有 Windows Phone 实现,但我尚未在 iOS 和 Android 中实现 ApplicationLifeCycleMessage 事件。
集成用户选项页面
您在构建任何应用程序时都会遇到的最常见任务之一是创建选项页面。这是 Calcium 真正出彩的地方。您看,Calcium 有一个抽象的选项系统,允许您用一行代码定义一个选项。然后,该选项会自动显示在应用程序的选项视图中,当用户修改该选项时,选项子系统负责持久化更改。
选项系统在设计时就考虑到了本地化。如果您想看看 Calcium 选项系统实际运行的真实示例,请查看 Windows Phone 版 Surfy 中的选项屏幕。
创建一个选项就像编写以下内容一样简单:
new BooleanUserOption(() => "Example Title", "A key for the settings service", () => aDefaultValue)
然后,您将该选项添加到一组选项中,并将其提交给 IUserOptionsService。Calcium 会处理其余的事情。Calcium 将自动检索并将值保存到设置服务。
您将在第 6 部分:《使用 Calcium for Windows Phone 创建用户选项页面》中更详细地了解用户选项系统。
Bootstrapper 定义了许多其他服务,例如 IMarketplaceService,它允许您跳转到应用程序的平台特定评分页面,或购买您的应用程序。
结论
在本文中,您了解了 Calcium 的起源并窥探了其一些关键功能。您了解了如何安装 Calcium NuGet 包。您了解了如何创建 Xamarin Forms 共享项目,以及如何使用 Bootstrapper 初始化您的应用程序。您了解了如何使用 XAML 创建一个基本页面以及如何将该页面绑定到视图模型。最后,您看到了 Calcium 的一些默认服务的概述,包括对话服务、设置服务和用户选项服务。在后续文章中,您将深入探讨 Calcium 的核心服务。
在下一篇文章中,您将看到如何创建一个完全通过数据绑定和自定义模板系统填充的选项卡式页面或轮播页面。
我希望您觉得这个项目有用。如果有用,我将不胜感激您能对其进行评分和/或在下方留下反馈。这将帮助我写出更好的下一篇文章。
历史
2014年9月
- 首次发布。