65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (9投票s)

2011年5月25日

CPOL

18分钟阅读

viewsIcon

43798

downloadIcon

1532

将 Catel 用作 Windows Phone 7 上的 MVVM 框架。

Catel 是一个全新的框架(或企业库,您喜欢怎么称呼它都可以),它集成了数据处理、诊断、日志记录、WPF 控件和 MVVM 框架。因此,Catel 不仅仅是另一个 MVVM 框架或一些好用的扩展方法。它更像是一个您希望在未来开发的所有(WPF)应用程序中包含的库。

本文解释了如何将 Catel 用作 Windows Phone 7 上的 MVVM 框架。

文章浏览器

目录

  1. 引言
  2. 创建项目
  3. 理解项目
  4. 添加 Bing Maps
  5. 设置视图模型
  6. 结论

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 中,

  1. 启用隐藏文件,并
  2. 像下图所示一样将项目项包含进来

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 版本中将被删除。我们不会使用验证,也不会保存任何数据,因此可以删除 ValidateFieldsValidateBusinessRulesSave 方法。

本文不介绍为 Bing Maps 创建运行时视图模型。因此,要做的最后一件事是删除 DesignMainPageViewModel 类。

现在视图模型类看起来干净多了,不是吗?即使您不这么认为,我们也要继续本章的下一部分!

5.2. 属性

在使用 Bing Maps 时,有几个属性对 Map 控件非常重要。我再次想提一下代码的清晰度。我喜欢将代码组织到区域中。有些人似乎害怕区域,他们开始尖叫和四处奔跑。如果您不喜欢区域,只需扔掉它们。但为了在本章开发视图模型期间的清晰度,请暂时保留它们。本文使用的所有属性都将添加到 *Properties\View model* 区域(是的,嵌套区域,我喜欢它们:))。

5.2.1. AvailableMapSources

首先,虚构用户希望能够查看几种不同的地图。必须有一些列表包含这些地图。

步骤
  1. 使用 *vmprop* 代码片段
  2. 添加描述 *Gets or sets the available map sources*,然后按 *tab* 键
  3. 添加类型 ObservableCollection<BaseTileSource>,然后按 *tab* 键
  4. 最后使用名称 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 属性。

步骤
  1. 使用 *vmprop* 代码片段
  2. 添加描述 *Gets or sets the current map*,然后按 *tab* 键
  3. 添加类型 BaseTileSource,然后按 *tab* 键
  4. 最后使用名称 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 属性表示。

步骤
  1. 使用 *vmprop* 代码片段
  2. 添加描述 *Gets or sets the map center*,然后按 *tab* 键
  3. 添加类型 GeoCoordinate,然后按 *tab* 键
  4. 最后使用名称 MapCenter,再按一次 *tab* 键
  5. 添加 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 属性。用户可能想放大到他/她自己的房子。为了跟踪用户当前缩放的级别,需要此属性。

步骤
  1. 使用 *vmprop* 代码片段
  2. 添加描述 *Gets or sets the zoom level*,然后按 *tab* 键
  3. 添加类型 double,然后按 *tab* 键
  4. 最后使用名称 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

顾名思义,用户可以转到前一种地图类型(在集合中向下移动一个)。

  1. 使用 *vmcommandwithcanexecute* 代码片段
  2. 添加名称 PreviousMap,然后按 *tab* 键
  3. 将命令的实例化移动到构造函数开头
  4. 使用以下内容实现 CanExecute
  5. return AvailableMapSources.Count > 1;
  6. 使用以下内容实现 Execute
  7. var newIdx = AvailableMapSources.IndexOf(CurrentMap) - 1;
    CurrentMap = AvailableMapSources[newIdx < 0 ? AvailableMapSources.Count - 1 : newIdx];

5.4.2. NextMap

顾名思义,用户可以转到下一张地图类型(在集合中向上移动一个)。

  1. 使用 *vmcommandwithcanexecute* 代码片段
  2. 添加名称 NextMap,然后按 *tab* 键
  3. 将命令的实例化移动到构造函数开头
  4. 使用以下内容实现 CanExecute
  5. return AvailableMapSources.Count > 1;
  6. 使用以下内容实现 Execute
  7. 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 的 ViewModelBaseClose 方法真正派上用场的地方。以如下方式重写 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()));

如您所见,它使用 MainPageUpdateMapCenter,因此这里是该方法的实现:

private void UpdateMapCenter()
{
    map.SetView(ViewModel.MapCenter, ViewModel.ZoomLevel);
}

很棒,但现在我们需要确保我们“神奇地”将视图链接到视图模型。在 Catel 中,您可以通过简单的属性来完成此操作。使用 ControlToViewModelAttribute 装饰属性:

[ControlToViewModel(MappingType = ControlViewModelModelMappingType.ViewModelToControl)]
public GeoCoordinate MapCenter

PhoneApplicationPage 将会监视视图模型,并在发生更改时立即更新 MainPageMapCenter 属性。

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 的文章中再见!

© . All rights reserved.