Catel - 第 6 部分(共 n 部分): WP7 的 Bing Maps 应用程序






4.74/5 (9投票s)
将 Catel 用作 Windows Phone 7 上的 MVVM 框架。
Catel 是一个全新的框架(或企业库,您喜欢怎么称呼它都可以),它集成了数据处理、诊断、日志记录、WPF 控件和 MVVM 框架。因此,Catel 不仅仅是另一个 MVVM 框架或一些好用的扩展方法。它更像是一个您希望在未来开发的所有(WPF)应用程序中包含的库。
本文解释了如何将 Catel 用作 Windows Phone 7 上的 MVVM 框架。
文章浏览器
- Catel - 第 n 部分的第 1 部分:
数据处理应有的样子 - Catel - 第 n 部分 2:使用 WPF 控件和主题
- Catel - 第 3 部分 (共 n 部分):MVVM 框架
- Catel - 第 n 部分 4:使用 Catel 进行单元测试
- Catel - 第 5 部分 (共 n 部分):在 1 小时内构建一个 Catel WPF 示例应用程序
- Catel - 第 6 部分 (共 n 部分): Bing Maps WP7 应用
- Catel - 第 n 部分 7:Catel 2.x 有什么新特性
- Catel - 第 n 部分 8:WP7 Mango 和相机单元测试
目录
1. 引言
欢迎阅读 Catel 系列文章的第 6 部分。如果您还没有阅读过 Catel 的前几篇文章,建议您阅读。它们已编号,因此查找它们应该不难。
这是关于将 Catel 用作 Windows Phone 7 MVVM 框架的第一篇文章。本文编写的软件非常简单,不包含任何导航。本文使用的软件基于 Joost van Schaik 在 .NET 杂志荷兰版上的一篇文章。下面是我们将在本文中创建的应用程序的最终结果。
自 1.4 版本起,Catel 完全支持 Windows Phone 7。本文旨在简单介绍 Catel 的 Windows Phone 7 实现。稍后将撰写更多关于 Catel 和 Windows Phone 7 的文章。如果您有好的文章想法,请告诉我们!
1.1. 先决条件
Catel 越来越完善。最新功能之一是 Visual Studio 2010 扩展,该扩展会安装 Catel 的项目和项模板。本文假设您已安装此 Catel 扩展。如果尚未安装,请阅读 有关如何安装扩展的文档。
本文还将使用 NuGet,这是获取最新开源项目版本的新方法。Catel 可通过 NuGet 获取,本文将解释如何获取最新版本的 Catel 并将其作为引用添加。如果愿意,也可以手动下载并引用 Catel 的程序集,但这超出了本文的范围。
另一个假设是您已安装 Windows Phone 7 开发人员工具,这是开发 Windows Phone 7 应用程序所必需的。
2. 创建项目
良好的软件开发在于组织代码,并确保您的开发团队中的每个人都能理解您的代码。当然,在座的各位都拥有软件开发的黑带水平,但也有一些天赋稍逊一筹的人需要理解您的天才代码。因此,一致地组织代码非常明智,本文也将从这里开始。
本文使用目录 *C:\Source\Articles* 作为基本目录,但您可以选择任何您想要的目录。在此目录中,创建一个名为 *BingMapsDemo* 的新目录。从现在起,这里将是我们存放所有文件(不仅是源文件)的地方,并称为 [basedir]。在 [basedir] 中,创建以下目录:
- *doc* (将所有文档放在此处)
- *lib* (将所有库放在此处)
- *src* (将所有源文件放在此处)
假设模板已安装到 Visual Studio,让我们为 Windows Phone 7 创建一个新项目。创建新项目时,*Silverlight for Windows Phone* 节点会包含一个名为 *Catel* 的子节点。选择 *Catel* 节点并创建一个新的 *Windows Phone Application with Catel*。
在 Catel 的解决方案中,此示例应用程序名为 *Catel.Examples.WP7.BingMaps*,因此最好为项目命名。您可以为项目指定任何名称,但本文代码中的命名空间假定为 *Catel.Examples.WP7.BingMaps*。确保将位置设置为 *[basedir]*。
项目现已准备就绪。现在是进行一致性和代码组织方面的繁琐工作。Visual Studio 并不按我(以及许多其他开发人员)喜欢的方式进行。因此,我们需要手动复制。关闭 Visual Studio,并将 *[basedir]\Catel.Examples.WP7.BingMaps* 中的所有内容复制到 *[basedir]\src*。最后删除目录 *[basedir]\Catel.Examples.WP7.BingMaps*。这将产生以下目录结构:
免责声明
您现在可能在想:这什么鬼?然而,一旦您学会了以这种方式组织所有软件项目,您只会从其他人那里得到关于您的软件项目多么有条理、干净且易于理解的赞扬。
3. 理解项目
我们刚刚使用 Windows Phone 7 应用程序模板和 Catel 创建了项目。在本文的这一部分,我们将引用最新的 Catel 程序集,并确保您完全理解项目模板为您创建了什么。
将 *[basedir]\src* 中的解决方案加载到 Visual Studio。当您尝试生成解决方案时,它将失败。我们现在就解决这个问题。
3.1. 通过 NuGet 引用 Catel
项目尚未引用 Catel。这是 NuGet 的任务,因为这样您可以确保获得最新的可用版本。本文撰写时 Catel 1.4 尚未发布,因此它使用的是本地存储库。当本文发布时,Catel 1.4 将已发布,您应该可以通过在线 NuGet 包源获取最新版本。
右键单击 *Catel.Examples.WP7.BingMaps* 项目,然后选择 *Add Library Package Reference*。搜索 *Catel*,选择正确的包 (Catel.WP7),最后单击 *Install*。NuGet 包现在将添加到您的项目中。
如果您现在尝试生成解决方案,它实际上可以编译。您刚刚成功使用 Catel 创建了第一个 Windows Phone 7 应用程序。有趣的是,您可以尝试运行它,您将在 Windows Phone 7 模拟器中看到一个空白页面。
3.2. 理解项目结构
理解项目结构非常重要。让我们从解决方案目录本身开始。它现在包含四个目录(*doc*、*lib*、*output* 和 *src*)。output 目录包含所有输出文件,因此您无需再搜索输出文件。这让事情变得容易多了,不是吗?:)
让我们看看项目本身。回到 Visual Studio,查看项目。下面是展开所有文件夹的项目图像:
首先注意到的是创建了一个新的 *UI* 文件夹,其中包含 *MainPage* 视图和 *MainPage* ViewModel。项目还包含一个 *Data* 文件夹,所有数据对象都应位于其中。当您查看 *MainPage.xaml.cs* 的代码隐藏文件时,您会发现有两个类。这看起来有点笨拙,但有一个非常好的理由。Silverlight(因此也是 Windows Phone 7)不允许类直接派生自泛型类。为了绕过这个问题,会创建一个中间类,这样 Silverlight 就不会再报错。以下情况是 MainPage
类层次结构的视觉表示:
您可以担心额外的类,也可以接受 Silverlight 的这种行为。如果您关心自己的健康,请选择后者。中间页面始终为您生成,因此您无需自己编写它们。
最后但同样重要的是,让我们看看实际定义视图逻辑的 MainPageViewModel
类。您会再次看到两个类:
MainPageViewModel
:实际的视图模型。它包含视图的所有实际逻辑。DesignMainPageViewModel
:视图模型的运行时实现。如果您不需要运行时,只需删除它。
目前,关于项目就理解到这里。下一章我们将深入探讨扩展功能。
4. 添加 Bing Maps
到目前为止您做得很好,而且并不难,对吧?添加 Bing Maps 需要一些工作,由于 Bing Maps 的实际工作原理超出了本文的范围,我决定采取捷径。我将解释您需要执行的所有步骤来添加支持,但不会解释 Bing Maps 的内部工作原理。
同样,实际 Bing Maps 实现的所有功劳都归于 Joost van Schaik。他最初在 .NET 杂志的文章中编写了它们,但他很乐意允许我将其作为与 Catel 结合的示例使用。
4.1. 添加引用
要启用 GPS 和 Bing Maps,请添加对以下程序集的引用:
- System.Device
- Microsoft.Phone.Controls.Maps
4.2. 添加源文件
实现 Bing Maps 需要大量源文件。我们在这里有点作弊,但我不想让您不得不自己复制所有源代码。因此,只需将 *BingMapsCode.zip* 中的所有文件解压缩到 *[basedir]\src\Catel.Examples.WP7.BingMaps*。
在 Visual Studio 中,
- 启用隐藏文件,并
- 像下图所示一样将项目项包含进来
4.3. 设置视图
MainPage
本身需要显示 Bing Maps 控件。以下是页面内容的源代码。用下面的 Grid
替换项目模板生成的整个 Grid
:
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="MY APPLICATION"
Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="{Binding Title}"
Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid Grid.Row="1" x:Name="ContentPanel">
<Grid.RowDefinitions>
<RowDefinition Height="0.13*" />
<RowDefinition Height="0.87*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.167*" />
<ColumnDefinition Width="0.667*" />
<ColumnDefinition Width="0.167*" />
</Grid.ColumnDefinitions>
<Maps:Map Grid.Row="1" Grid.ColumnSpan="3" x:Name="map"
CredentialsProvider="developer_api_here"
Center="{Binding MapCenter}"
ZoomLevel="{Binding ZoomLevel, Mode=TwoWay}"
LocalUI:BindingHelpers.TileSource="{Binding CurrentMap}">
<Maps:Map.Mode>
<MSPCMCore:MercatorMode />
</Maps:Map.Mode>
</Maps:Map>
<Button Content="<">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<MVVM:EventToCommand Command="{Binding PreviousMap}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<Button Content=">" Grid.Column="2">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<MVVM:EventToCommand Command="{Binding NextMap}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<TextBlock Grid.Column="1" TextWrapping="Wrap"
Text="{Binding CurrentMap.Name}" Margin="0,23,0,0" HorizontalAlignment="Center"/>
</Grid>
</Grid>
这可能看起来有点压倒性,但基本上页面上只有几个控件。中心是 Map
控件。然后是两个按钮(< 和 >),允许用户在不同的地图类型之间切换。最后一段代码是一个 TextBlock
,显示当前选择的地图,以便用户知道他/她正在查看哪种地图类型。
如果您现在尝试生成软件,它将失败,因为您需要在 XAML 页面的声明中设置一些引用:
xmlns:Maps="clr-namespace:Microsoft.Phone.Controls.Maps;assembly=Microsoft.Phone.Controls.Maps"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:MVVM="clr-namespace:Catel.MVVM;assembly=Catel.WP7"
xmlns:MSPCMCore="clr-namespace:Microsoft.Phone.Controls.Maps.Core;
assembly=Microsoft.Phone.Controls.Maps"
xmlns:LocalUI="clr-namespace:Catel.Examples.WP7.BingMaps.UI"
您现在可以生成并运行您的应用程序。它将显示设计的页面,但您仍然无法查看或切换地图。但是,您现在可以大致了解最终结果的外观。
5. 设置视图模型
本章将重点介绍在视图模型中实现实际的视图逻辑。首先,我们定义属性。然后,我们将创建方法来为属性添加逻辑和行为。在方法之后,我们将实现命令,以便视图可以与视图模型进行交互。最后但同样重要的是,我们将做一些很棒的事情,这些将在下一个 Mango 更新中可用:模拟地理位置。然而,使用 Catel,您不必等到 2011 年秋天,而是可以立即开始使用它。
5.1. 清理
本章实际上从清理视图模型开始。项目模板创建了默认属性和行为,以尽可能贴近默认的 Microsoft 模板。但是,在此项目中我们不需要它们,因此我们从清理开始。
项目模板已创建一个名为 Items
的属性,我们不会使用它;请删除它(不要忘记 RegisterProperty
行)。
接下来,也删除 Initialize
方法。它用于向后兼容,但在 Catel 2.0 版本中将被删除。我们不会使用验证,也不会保存任何数据,因此可以删除 ValidateFields
、ValidateBusinessRules
和 Save
方法。
本文不介绍为 Bing Maps 创建运行时视图模型。因此,要做的最后一件事是删除 DesignMainPageViewModel
类。
现在视图模型类看起来干净多了,不是吗?即使您不这么认为,我们也要继续本章的下一部分!
5.2. 属性
在使用 Bing Maps 时,有几个属性对 Map
控件非常重要。我再次想提一下代码的清晰度。我喜欢将代码组织到区域中。有些人似乎害怕区域,他们开始尖叫和四处奔跑。如果您不喜欢区域,只需扔掉它们。但为了在本章开发视图模型期间的清晰度,请暂时保留它们。本文使用的所有属性都将添加到 *Properties\View model* 区域(是的,嵌套区域,我喜欢它们:))。
5.2.1. AvailableMapSources
首先,虚构用户希望能够查看几种不同的地图。必须有一些列表包含这些地图。
步骤
- 使用 *vmprop* 代码片段
- 添加描述 *Gets or sets the available map sources*,然后按 *tab* 键
- 添加类型
ObservableCollection<BaseTileSource>
,然后按 *tab* 键 - 最后使用名称
AvailableMapSources
,再按一次 *tab* 键
结果
/// <summary>
/// Gets or sets the available map sources.
/// </summary>
public ObservableCollection<BaseTileSource> AvailableMapSources
{
get { return GetValue<ObservableCollection<BaseTileSource>>(
AvailableMapSourcesProperty); }
set { SetValue(AvailableMapSourcesProperty, value); }
}
/// <summary>
/// Register the AvailableMapSources property so it is known in the class.
/// </summary>
public static readonly PropertyData AvailableMapSourcesProperty =
RegisterProperty("AvailableMapSources",
typeof(ObservableCollection<BaseTileSource>));
5.2.2. CurrentMap
用户希望看到一张地图。为了知道用户当前正在查看哪张地图,需要添加一个 CurrentMap
属性。
步骤
- 使用 *vmprop* 代码片段
- 添加描述 *Gets or sets the current map*,然后按 *tab* 键
- 添加类型
BaseTileSource
,然后按 *tab* 键 - 最后使用名称
CurrentMap
,再按一次 *tab* 键
结果
/// <summary>
/// Gets or sets the current map.
/// </summary>
public BaseTileSource CurrentMap
{
get { return GetValue<BaseTileSource>(CurrentMapProperty); }
set { SetValue(CurrentMapProperty, value); }
}
/// <summary>
/// Register the CurrentMap property so it is known in the class.
/// </summary>
public static readonly PropertyData CurrentMapProperty =
RegisterProperty("CurrentMap", typeof(BaseTileSource));
5.2.3. MapCenter
默认情况下,Bing Maps 显示世界。但您希望将地图居中到特定位置。这由 MapCenter
属性表示。
步骤
- 使用 *vmprop* 代码片段
- 添加描述 *Gets or sets the map center*,然后按 *tab* 键
- 添加类型
GeoCoordinate
,然后按 *tab* 键 - 最后使用名称
MapCenter
,再按一次 *tab* 键 - 添加
using System.Device.Location
结果
/// <summary>
/// Gets or sets the map center.
/// </summary>
public GeoCoordinate MapCenter
{
get { return GetValue<GeoCoordinate>(MapCenterProperty); }
set { SetValue(MapCenterProperty, value); }
}
/// <summary>
/// Register the MapCenter property so it is known in the class.
/// </summary>
public static readonly PropertyData MapCenterProperty =
RegisterProperty("MapCenter", typeof(GeoCoordinate));
5.2.4. ZoomLevel
注册的最后一个属性是 ZoomLevel
属性。用户可能想放大到他/她自己的房子。为了跟踪用户当前缩放的级别,需要此属性。
步骤
- 使用 *vmprop* 代码片段
- 添加描述 *Gets or sets the zoom level*,然后按 *tab* 键
- 添加类型
double
,然后按 *tab* 键 - 最后使用名称
ZoomLevel
,再按一次 *tab* 键
结果
/// <summary>
/// Gets or sets the zoom level.
/// </summary>
public double ZoomLevel
{
get { return GetValue<double>(ZoomLevelProperty); }
set { SetValue(ZoomLevelProperty, value); }
}
/// <summary>
/// Register the ZoomLevel property so it is known in the class.
/// </summary>
public static readonly PropertyData ZoomLevelProperty =
RegisterProperty("ZoomLevel", typeof(double));
5.3. 方法
注册属性相当容易。现在我们必须实现的唯一方法是构造函数。它初始化 AvailableMapSources
等属性。下面是代码:
/// <summary>
/// Initializes a new instance of the <see cref="MainPageViewModel"/> class.
/// </summary>
public MainPageViewModel()
: base()
{
AvailableMapSources = new ObservableCollection<BaseTileSource>
{
new BingAerial{ Name = "Bing Aerial"},
new BingRoad {Name = "Bing Road"},
new Mapnik {Name = "OSM Mapnik"},
new OsmaRender {Name = "OsmaRender"},
new Google {Name = "Google Hybrid", MapType = GoogleType.Hybrid},
new Google {Name = "Google Street", MapType = GoogleType.Street},
};
if (AvailableMapSources.Count > 0)
{
CurrentMap = AvailableMapSources[0];
}
ZoomLevel = 1;
}
代码初始化了可用的地图源(直接实例化不同的类型)。可以使其更动态,但这会使此示例更复杂。当地图初始化时,它默认选择第一个,以便用户在应用程序启动时有地图可看。最后,将 ZoomLevel
设置为 1,提供对地球的完整概览。双击某个位置会放大。
5.4. 命令
让我们给用户切换不同类型地图的选项。正如您可能从上一章中记得的那样,我们在页面上创建了两个按钮。让我们实现这些命令。命令应位于 *Commands* 区域。
5.4.1. PreviousMap
顾名思义,用户可以转到前一种地图类型(在集合中向下移动一个)。
- 使用 *vmcommandwithcanexecute* 代码片段
- 添加名称
PreviousMap
,然后按 *tab* 键 - 将命令的实例化移动到构造函数开头
- 使用以下内容实现
CanExecute
: - 使用以下内容实现
Execute
:
return AvailableMapSources.Count > 1;
var newIdx = AvailableMapSources.IndexOf(CurrentMap) - 1;
CurrentMap = AvailableMapSources[newIdx < 0 ? AvailableMapSources.Count - 1 : newIdx];
5.4.2. NextMap
顾名思义,用户可以转到下一张地图类型(在集合中向上移动一个)。
- 使用 *vmcommandwithcanexecute* 代码片段
- 添加名称
NextMap
,然后按 *tab* 键 - 将命令的实例化移动到构造函数开头
- 使用以下内容实现
CanExecute
: - 使用以下内容实现
Execute
:
return AvailableMapSources.Count > 1;
var newIdx = AvailableMapSources.IndexOf(CurrentMap) + 1;
CurrentMap = AvailableMapSources[newIdx > AvailableMapSources.Count - 1 ? 0 : newIdx];
5.5. 获取地理位置
用户现在可以缩放和平移地图。太棒了,但用户默认希望看到他/她的当前位置。但在 MVVM 情况如何获取当前地理位置?没问题,Catel 来救援!
Catel 提供了一个 ILocationService
,它默认在 Windows Phone 7 上可用。在本文中,我们将使用此服务检索用户的当前位置并在必要时更新它。ILocationService
需要启动,以便提供实时更新。
在构造函数中,检索服务并启动它:
var locationService = GetService<ILocationService>();
locationService.LocationChanged += OnCurrentLocationChanged;
locationService.Start();
在 *Methods* 区域中,添加 OnCurrentLocationChanged
事件处理程序的实现:
/// <summary>
/// Called when the current location on the location service has changed.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="Catel.MVVM.Services.LocationChangedEventArgs"/>
/// instance containing the event data.</param>
private void OnCurrentLocationChanged(object sender, LocationChangedEventArgs e)
{
// Only update if there is a new location, otherwise assume that the
// user wants to see the last position
if (e.Location != null)
{
MapCenter = new GeoCoordinate(e.Location.Latitude,
e.Location.Longitude, e.Location.Altitude);
}
}
一旦 ILocationService
向视图模型提供新位置,当前 MapCenter
就会更新到该位置,为用户提供实时地图概览。
使用完毕后,需要关闭位置服务。这就是 Catel 的 ViewModelBase
的 Close
方法真正派上用场的地方。以如下方式重写 Close
方法:
protected override void Close()
{
var locationService = GetService<ILocationService>();
locationService.LocationChanged -= OnCurrentLocationChanged;
locationService.Stop();
base.Close();
}
调用 base.Close
非常重要,因为它将进一步处理页面的关闭。如您所见,再次检索 ILocationService
(相同的实例),视图模型将从此事件取消订阅,最后停止服务。
为了给用户提供出色的用户体验,让我们确保地图已经尽可能近地缩放到地球。为此,请在构造函数中将 ZoomLevel
的默认值更改为 19。
如何使地图中心更新生效?
运行应用程序时,您会注意到地图不会更新。这是因为 Map
控件未正确监听更改。这是 Microsoft 的一项非常糟糕的实现。幸运的是,Catel 可以处理此类情况并为一切提供解决方案。在 MainPage
的代码隐藏中,我们可以收到视图模型内更改的通知(这样视图模型仍然不知道任何 UI),应用程序可以提供实时更新。这与 MVVM 或 Catel **无关**,这只是 Map
控件的一个糟糕的实现。
为了完成实现,请添加一个名为 MapCenter
的依赖属性,如下面的代码示例所示(使用 *propdp* 代码片段定义):
public GeoCoordinate MapCenter
{
get { return (GeoCoordinate)GetValue(MapCenterProperty); }
set { SetValue(MapCenterProperty, value); }
}
// Using a DependencyProperty as the backing store for MapCenter.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty MapCenterProperty =
DependencyProperty.Register("MapCenter", typeof(GeoCoordinate),
typeof(MainPage), new PropertyMetadata(
null, (sender, e) => ((MainPage)sender).UpdateMapCenter()));
如您所见,它使用 MainPage
的 UpdateMapCenter
,因此这里是该方法的实现:
private void UpdateMapCenter()
{
map.SetView(ViewModel.MapCenter, ViewModel.ZoomLevel);
}
很棒,但现在我们需要确保我们“神奇地”将视图链接到视图模型。在 Catel 中,您可以通过简单的属性来完成此操作。使用 ControlToViewModelAttribute
装饰属性:
[ControlToViewModel(MappingType = ControlViewModelModelMappingType.ViewModelToControl)]
public GeoCoordinate MapCenter
PhoneApplicationPage
将会监视视图模型,并在发生更改时立即更新 MainPage
的 MapCenter
属性。
5.6. 模拟地理位置
太棒了,应用程序似乎已经完成了。但是您的测试人员使用的是模拟器而不是真实的 Windows Phone 7。没问题,Catel 完全支持 ILocationService
的测试实现,因此您可以在模拟器中模拟实时 GPS 更新。
为此,您需要配置 IoC 容器。下面是一个为您完成所有这些操作的方法,它还添加了一个穿越街道的路线:
/// <summary>
/// Initializes the demo route for test purposes.
/// <para />
/// Calling this method will register the test version
/// of the <see cref="ILocationService"/>.
/// </summary>
private void InitializeDemoRoute()
{
// This is a demo app, register test version of the service
// In normal situations, you would not directly cast
// a service to a specific type in your view-model,
// only in unit tests to set the expected locations.
// However, since we simply want to show the power
// of IoC in combination with the location service,
// we register the service here and directly retrieve
// it to simulate a user walking through a street
IoC.IoCProvider.Instance.RegisterType<ILocationService,
MVVM.Services.Test.LocationService>();
var testLocationService =
(MVVM.Services.Test.LocationService)GetService<ILocationService>();
TimeSpan timeSpan = new TimeSpan(0, 0, 0, 0, 500);
// First one is longer because maps need to initialize
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38772d, 5.56484d),
new TimeSpan(0, 0, 0, 5)));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38771d, 5.56484d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38770d, 5.56484d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38769d, 5.56483d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38768d, 5.56483d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38767d, 5.56483d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38766d, 5.56482d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38765d, 5.56482d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38764d, 5.56482d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38763d, 5.56481d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38762d, 5.56481d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38761d, 5.56481d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38760d, 5.56480d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38759d, 5.56480d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38758d, 5.56480d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38757d, 5.56479d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38756d, 5.56479d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38755d, 5.56479d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38754d, 5.56478d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38753d, 5.56478d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38752d, 5.56478d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38751d, 5.56477d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38750d, 5.56477d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38749d, 5.56477d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38748d, 5.56476d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38747d, 5.56476d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38746d, 5.56476d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38745d, 5.56475d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38744d, 5.56475d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38743d, 5.56475d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38742d, 5.56474d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38741d, 5.56474d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38740d, 5.56474d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38739d, 5.56473d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38738d, 5.56473d), timeSpan));
testLocationService.ExpectedLocations.Enqueue(
new LocationTestData(new Location(51.38737d, 5.56473d), timeSpan));
}
看起来代码量很大。当然有更智能的编写方式,但这样用户就不必理解大量的 for
循环,而是可以轻松理解正在发生的事情。我将把创建更智能的编写方式留给读者作为练习。
该方法首先在 IoC 容器中注册 ILocationService
的测试版本。然后,它将所有预期的位置排队到服务中,一旦服务启动(就像普通 GPS 设备一样),服务就会开始生成更改。
最后要做的事情是在构造函数中调用 InitializeDemoRoute
。在通过 GetService
检索服务之前这样做,因为此时,视图模型决定是使用服务的测试版本还是生产版本。
6. 结论
本文展示了使用 Catel 为 Windows Phone 7 编写 MVVM 应用程序是多么容易,即使您需要读取和模拟地理位置。
Catel 在 Silverlight 中提供的用户控件的泛型基类存在一些缺点。问题在于 Silverlight(因此也是 Windows Phone 7)不允许页面或控件直接派生自泛型类。项目和项模板会为您处理这个问题。
一切进展得太快,还是无法将各部分联系起来?别担心!我们在这里帮助您解决与 Catel 相关的问题。您可以在此文章下方的评论中发表评论,或者前往 CodePlex 的 讨论页面。
我们很想听听您的反馈,所以如果您有任何意见,请随时告诉我们!感谢您的阅读,希望在下一篇关于 Catel 的文章中再见!