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

探索云的机会:第 1 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.53/5 (3投票s)

2010年12月31日

CPOL

19分钟阅读

viewsIcon

26123

downloadIcon

202

一系列文章,探讨如何使用必应地图为 Windows Azure 开发应用程序。

引言

我最近读到,在短短几年内,超过三分之一的开发都将基于云。这是一个巨大的转变。因此,我开始研究一些技术以及开发云应用程序需要具备的条件。需要明确的是,我认为可能存在一些关于什么是云应用程序的误解。我不是指基于Web服务器的应用程序。我所谈论的开发是指针对 Azure 等平台的应用程序。

我所知道的唯一学习方法就是“去做”;经验绝对是我最好的老师。不幸的是,日常工作很少能提供足够多样的挑战或创造性的机会来探索所有新技术。所以对我而言,一种替代方法是永远像企业家一样思考。我关注新技术,并思考可以使用新技术启动的新业务或项目。在审视这种新范例时,我发现潜在的机会非常多。我之所以这么说,是因为我认为这种转变不仅仅是将现有应用程序迁移到新环境。相反,我认为技术、工具和基础设施都已到位,可以实现一大批以前不可行的全新解决方案。

我们可以争论,但我相信这些新解决方案中的关键参与者将是我们随身携带的设备——手机。实际上,我认为我们不应该再称它们为手机了。因为它们实际上是“无线个人电脑”,同时还能让你打电话。这些设备的强大功能和能力已经超越了几年前的笔记本电脑。考虑到宽带速度的可用性来支持这些设备,应用程序的功能几乎是无限的。我谈论的不是那些有趣的消遣小程序或游戏;我谈论的是真实的业务解决方案。

因此,这里的首要目标是学习开发 Azure 应用程序需要什么,但不仅仅是打包和部署应用程序的机械操作。具体来说,我想探索在桌面和手机上使用 Silverlight(作为一个单一解决方案)并部署在 Azure 上。但这个目标对于概念化特定应用程序来说仍然过于宽泛。为了缩小范围,我们将以必应地图作为探索云解决方案的中心主题。您会惊讶于除了典型的导航路线指引之外,还可以设计出多少其他用途。

由于必应地图将构成探索的核心,我们将在这篇文章中首先熟悉该服务以及相关的 Silverlight 控件。在下一篇文章中,我将描述一些围绕必应地图的云应用程序概念。同时,我们将扩展本文中提供的地图功能,以突出一些概念的功能。在第三篇文章中,我们将再次利用必应地图,介绍一个完整的云解决方案。最后说明一点,因为这对我来说是一项学习练习,所以行文更倾向于循序渐进的教程。学生实际上是我,但欢迎您跟随。

开始使用必应地图

为了能够访问微软提供的地图服务,您需要注册并获取一个帐户。您可以在 http://www.microsoft.com/maps/developers/web.aspx 注册一个开发者帐户。注册后,您将能够获得一个必应地图密钥,然后您可以使用该密钥免费访问该服务。您需要的所有信息都将在上述链接中提供。

我们还将大量使用 Silverlight 地图控件,因此您也需要下载它。您可以在此处找到 SDK 和相关说明:http://www.microsoft.com/downloads/en/details.aspx?displaylang=en&FamilyID=beb29d27-6f0c-494f-b028-1e0e3187e830

现在我们已经完成了外部先决条件,让我们看看如何应用它们。首先创建一个基本的 Silverlight 应用程序,命名为 _SLMapTest_,并接受默认的 ASP Web 托管。首先,由于我们要使用地图控件,您需要为项目添加 DLL 引用(见下文)。您应该可以在 _Program Files_ 文件夹下的 Bing Maps Silverlight Control-Libraries 中找到它们。

BingMapCtlReference.jpg

现在,将地图控件添加到您的页面,并包含适当的命名空间,如下所示。请注意,您必须在“Your Key”的位置插入您自己的必应地图凭据密钥才能访问服务。

 xmlns:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"
... 
    <Grid x:Name="LayoutRoot" Background="White">
        <m:Map x:Name="bingMap" CredentialsProvider="Your Key">
        </m:Map>
    </Grid>
...

编译并运行。您刚刚为用户提供了一个功能齐全的交互式地图。用户可以滚动世界地图,更改视图模式,并缩放到任何所需的区域。对我们来说,几乎没做什么就实现了这些,这算是不错了。虽然从用户的角度来看并不令人印象深刻(他们可以在必应上做到这一点),但这确实证明了我们已经正确连接了所有必要的组件。

现在让我们添加一些基本功能,允许用户输入所需的地点/地址,然后系统会显示该区域的放大视图(如果找到)。修改页面如下

<m:Map x:Name="bingMap" CredentialsProvider="Your Key">
    <TextBox Height="23" HorizontalAlignment="Right" Margin="0,26,50,0" 
       Name="textAddress" VerticalAlignment="Top" Width="120">
    </TextBox>
    <Button Content="Find" Height="23" HorizontalAlignment="Right" Margin="0,26,0,0" 
       Name="buttonFind" VerticalAlignment="Top" Width="44" Click="buttonFind_Click">
    </Button>
    <TextBlock Height="23" HorizontalAlignment="Right" 
       Name="textResult" VerticalAlignment="Top" Width="166"/>
</m:Map>

我们所做的就是添加一个用于用户输入的文本框,一个用于调用请求的按钮,以及一个 `TextBlock` 来显示任何异常结果。以下是连接好所有东西后,用户输入地点后的样子

SLMapTestView.png

您可以通过地图控件公开的方法和属性来对其进行编程控制(SDK 提供的文档中有完整说明)。我们感兴趣的方法是 `SetView`,它将地图居中于与用户输入的地点地址对应的坐标集。不幸的是,地图控件没有一个接受街道地址的方法,只有地理坐标(纬度/经度)。这意味着我们必须执行地点地址(字符串)和地理坐标之间的转换。幸运的是,微软提供了一个提供此功能的可用服务。

为了继续项目,我们需要设置一个必应地理编码服务的代理,该服务将提供我们所需的转换。右键单击解决方案资源管理器中的“引用”节点,然后选择“添加服务引用...” 。在地址栏输入以下内容:http://staging.dev.virtualearth.net/webservices/v1/geocodeservice/geocodeservice.svc?wsdl。单击“GO”以检索文件。找到并加载文件后,您应该会在左侧窗格中看到服务的契约定义。在点击 OK 让向导生成代理之前,请进行另外两项更改。首先,将默认命名空间更改为更具描述性的名称,例如“BingGeocodeService”或您喜欢的任何名称。默认生成的代理类会引入一些类型定义,这些定义将与地图控件中已定义的类型冲突。要解决此问题,请单击“高级”按钮,然后选择“在指定的引用程序集中重用类型”。然后选择两个地图程序集。这将从生成的代理命名空间中删除这些定义。最后单击 OK 生成代理。

为了让我们使用该服务,请在代码隐藏文件中为代理添加一个成员变量,如下所示(不要忘记命名空间)

#region private property
private BingGeocodeService.GeocodeServiceClient geocodeClient;
private BingGeocodeService.GeocodeServiceClient GeocodeClient
{
    get
    {
        if (null == geocodeClient)
        {
            BasicHttpBinding binding = 
              new BasicHttpBinding(BasicHttpSecurityMode.None);
            UriBuilder serviceUri = new UriBuilder(
              "http://dev.virtualearth.net/webservices/" + 
              "v1/GeocodeService/GeocodeService.svc");

            //Create the Service Client
            geocodeClient = new BingGeocodeService.GeocodeServiceClient(binding, 
                            new EndpointAddress(serviceUri.Uri));
            geocodeClient.GeocodeCompleted += new 
             EventHandler<binggeocodeservice.geocodecompletedeventargs>(
             GeocodeCompleted);
        }
        return geocodeClient;
    }
}

我们已准备好开始连接各个部分。为按钮的 `Click` 事件创建一个事件处理程序。当用户按下“查找”按钮时,将发生此操作。在该处理程序中,我们将添加一些代码,使用我们新创建的代理调用必应地理编码服务。契约指示有一个名为 `GeocodeAsync` 的方法,该方法可以接受一个地址字符串(作为查询),并通过回调返回该地点的纬度/经度值。然后,我们将把这些值传递给地图控件,以便将视图居中于该坐标。

由于对服务的调用是异步的,因此我们在实例化终结点时(参见上文)创建了一个事件处理程序(`GeocodeCompleted`)来处理响应。在按钮的点击事件处理程序中,我们获取用户输入的地址字符串,并将其传递给一个名为 `GeocodeAddress` 的帮助程序方法,在该方法中调用服务。以下代码显示了这三个方法

private void buttonFind_Click(object sender, RoutedEventArgs e)
{
    //Make sure there's something there
    if (textAddress.Text.Length > 0)
    {
        GeocodeAddress(textAddress.Text);
    }
}
...
private void GeocodeAddress(string address)
{
    //Pack up a request
    BingGeocodeService.GeocodeRequest request = 
                       new BingGeocodeService.GeocodeRequest();
    request.Query = address;    //what user entered
    // Don't raise exceptions.
    request.ExecutionOptions = new BingGeocodeService.ExecutionOptions();
    request.ExecutionOptions.SuppressFaults = true;

    // Only accept results with high confidence.
    request.Options = new BingGeocodeService.GeocodeOptions();

    //ObservableCollection is the default for Silverlight proxy generation.
    request.Options.Filters = 
       new ObservableCollection<BingGeocodeService.FilterBase>();
    BingGeocodeService.ConfidenceFilter filter = 
               new BingGeocodeService.ConfidenceFilter();
    filter.MinimumConfidence = BingGeocodeService.Confidence.High;
    request.Options.Filters.Add(filter);

    //Need to add your key here
    request.Credentials = new Credentials();
    request.Credentials.ApplicationId = 
       (string)App.Current.Resources["BingCredentialsKey"];

    //Make the call
    GeocodeClient.GeocodeAsync(request);
}
...
private void GeocodeCompleted(object sender, 
        BingGeocodeService.GeocodeCompletedEventArgs e)
{
    string callResult = "";
    try
    {
        //Was the service able to parse it? And did it return anything?
        if (e.Result.ResponseSummary.StatusCode != 
                BingGeocodeService.ResponseStatusCode.Success ||
            e.Result.Results.Count == 0)
        {
            callResult = "Could not find address.";
        }
        else
        {
            // Center and Zoom the map to the location of the item.
            bingMap.SetView(e.Result.Results[0].Locations[0], 18);
        }
    }
    catch
    {
        callResult = "Error processing request.";
    }

    textResult.Text = callResult;
}

试试看。编译并运行应用程序,确保您可以访问地理编码服务。是的,我知道,您只需访问必应地图就可以获得更多信息。但我们的计划不是提供_那种_服务,而是以新的方式利用现有资源。此时,我们仍在检查管道。但在我们开始探索可以使用地图控件创建的新功能之前,我想稍微“跑偏”一下,进入_MVVM 的世界_。主要是因为一旦我们开始添加更多代码,如果我们继续使用当前的方法,事情就会变得一团糟。其次是因为...

MVVM 的世界

关于 MVVM 有很多可说的。事实上,简单搜索一下就会让您应接不暇_所有_被讨论的内容。一个模式为什么会有如此多的意见和方法?模式的定义应该是相反的,即提供一个_简单易懂的方法_来解决问题。模式是_沟通_机制。在我看来,我们是“只见树木,不见森林”。

如果我们退一步,从另一个角度来看,也许会更清楚(或者不)。我认为,作为一个社区,我们已经认识到我们想要(并需要)什么,并将其命名为“总括”术语_MVVM_。MVVM 实际上是_技术_的集合(有些通过框架提供,有些是临时发明的),使我们能够实现良好设计的目标:分离表示;关注点分离;可测试性;等等。这实在太多的东西,不能被定义为一个_模式_。

Martin Fowler 在谈论 MVC 时(在此处)写道:“它经常被称为一个模式,但我认为将其视为一个模式并没有多大用处,因为它包含了相当多的不同想法。”MVVM 作为 MVC 的变体,也属于同样的批评范畴。而在(这篇关于 MVVM 的博客)中,John Grosman 写道:“我的团队已经狂热地使用了这个模式两年,而且我们_仍然_没有一个清晰的 ViewModel 定义”(注意,是我加重的部分)。“一个问题是(那就是)ViewModel 实际上结合了几个独立的、正交的概念:视图状态、值转换、用于在模型上执行操作的命令。”

我认为上面提到的每个“正交概念”_本身_都可以用其自己的模式来描述。它不仅是太多东西无法描述为一个简单的模式,而且实现这些“正交概念”的机制也一直不存在(它们是随着时间一点点加入的)。此外,已添加的支持在不同平台之间存在差异!这应该足以解释为什么 MVVM 周围存在如此多的困惑,但依我看,还有更多。

如果一个应用程序仅仅由一个视图及其相关的 ViewModel/Model 组成,那么事情可能会很好(在您弄清楚如何实现“正交概念”之后)。然而,事情很少如此简单。即使是最简单的应用程序,也会有多个视图。事实上,仅仅显示一个对话框就构成了一个单独的视图。通常,您会有多个视图,这些视图无疑会有一些相互关联的状态/数据。采纳关注点分离_要求_存在某种消息传递基础设施可供 ViewModels 使用。这是实现_MVVM_所需支持的另一个空白。幸运的是,可以通过使用各种社区工具包和库来填补这一空白。这种方法的困境是,您的应用程序的生命周期可能比社区的支持时间长。_而且_微软可能有其他计划,这些计划可能与您选择的库不完全一致。因此,希望消息传递机制将集成到 .NET Framework 的未来版本中(并且也希望有一个依赖注入设施)。

拆分很难

是的,我知道。大多数读者可能无法理解。总之,在我们示例应用程序的上下文中,拆分将通过使用 MVVM Light Toolkit 来实现,您可以在此处找到它。该工具包提供了几个“工具”来帮助实现_MVVM_,包括:将视图和 ViewModel 绑定在一起的机制;`ICommand` 的实现;以及一个很好的消息传递机制。它还与 Visual Studio 和 Blend 很好地集成。还有其他工具包和库可用,我只是碰巧为这个应用程序使用了这个。

安装 MVVM Light Toolkit 后,继续我们项目的首要任务是添加对 GalaSoft MvvmLight 库 DLL 的引用。不同平台有不同的库,所以对于这个项目,请拉入 Silverlight 4 的那些。然后,在项目中创建一个文件夹,称之为_ViewModel_,并在新文件夹中添加一个 _MvvmViewModel_(SL) 作为 MainViewModel,以及一个 _MvvmViewModelLocator_(SL) 作为 ViewModelLocator。这些模板是作为 MvvmLight 安装的一部分添加的。

MvvmLight 模板类提供了注释说明,指示了需要的内容。但我们将在下面介绍这些步骤。首先,将 ViewModelLocator 添加到应用程序资源(在_App.xml_中),如下所示

<Application 
    xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
       http://schemas.microsoft.com/winfx/2006/xaml/presentation</a>"
    xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml">
       http://schemas.microsoft.com/winfx/2006/xaml</a>"
    xmlns:d="<a href="http://schemas.microsoft.com/expression/blend/2008">
       http://schemas.microsoft.com/expression/blend/2008</a>"
    xmlns:mc="<a href="http://schemas.openxmlformats.org/markup-compatibility/2006">
      http://schemas.openxmlformats.org/markup-compatibility/2006</a>"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    x:Class="SLMapTest.App"
    xmlns:vm="clr-namespace:SLMapTest.ViewModel"
    mc:Ignorable="d">
    <Application.Resources>
        <!--Global View Model Locator-->
        <vm:ViewModelLocator x:Key="Locator"
        d:IsDataSource="True" />
    </Application.Resources>
</Application>

在_MVVM_中分离表示意味着视图及其关联的 ViewModel 可以独立设计。然而,在某个时候,它们需要被_介绍_给对方。ViewModelLocator 提供了实现这一目标的一种机制。它本质上是一个用于视图:ViewModel 关联的全局查找。所以,我们将继续在 ViewModelLocator 中为我们上面创建的新 ViewModel 类 `MainViewModel` 添加一个条目。您可以使用 MvvmLight 安装提供的代码片段来完成此操作,并在 ViewModelLocator 模板注释中进行描述。最终结果是 `MainViewModel` 被添加为类的可绑定属性。

现在我们可以通过修改 _MainPage.xml_ 来将 MainPage 视图绑定到关联的 `MainViewModel` 类,如下所示

...
    xmlns:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"

    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400"
    DataContext="{Binding Source={StaticResource Locator}, Path=ViewModelMain}">
...

如果您编译并运行应用程序,您可能会注意到实际上没有任何变化。但是,如果您在 `MainViewModel` 类的构造函数中设置一个断点,您将看到它确实被创建了,所以现在我们只需要利用它。

因此,_MVVM_的核心思想是使代码隐藏文件中的内容尽可能少(或没有),以便视图可以独立于业务逻辑(从 ViewModel 开始)进行设计,并且业务逻辑也可以独立于视图进行测试,从而无需用户交互。然而,我认为前者应该是一个目标,而不是强制规定。如果您必须将应用程序复杂化到极致,仅仅是为了从代码隐藏文件中删除某些内容,请停下来思考一下。结果是否值得付出的努力或造成的复杂性?

继续我们的项目,为了让 `MainViewModel` _支持_ MainPage(视图),我们需要添加一些绑定属性。具体来说,我们需要支持按钮和两个文本框。下面的代码显示了已准备好与视图绑定的类

public class MainViewModel : ViewModelBase
{
    RelayCommand findButton;
    string findString;
    string resultString;
    /// <summary>
    /// Initializes a new instance of the MainViewModel class.
    /// </summary>
    public MainViewModel()
    {
        findButton = new RelayCommand(FindButtonClick);
    }
    #region Commanding
    public ICommand FindButtonCommand
    {
        get { return findButton; }
    }
    void FindButtonClick()
    {
    }
    #endregion
    #region data context
    public string FindString
    {
        get { return findString; }
        set { findString = value; }
    }
    public string ResultString
    {
        get { return resultString; }
        set { resultString = value; }
    }
    #endregion
}

如果您打开控件的属性窗口,您将能够以交互方式完成所有绑定;这比输入要好。但请确保您先进行编译,否则可绑定属性将不会显示。以下是绑定定义方式

...
    <TextBox Height="23" HorizontalAlignment="Right" Margin="0,26,50,0" 
      Name="textAddress" VerticalAlignment="Top" Width="120" 
      Text="{Binding Path=FindString, Mode=TwoWay}">
    </TextBox>
    <Button Content="Find" Height="23" HorizontalAlignment="Right" 
      Margin="0,26,0,0" Name="buttonFind" VerticalAlignment="Top" 
      Width="44" Command="{Binding Path=FindButtonCommand}">
    </Button>
    <TextBlock Height="23" HorizontalAlignment="Right" Name="textResult" 
      VerticalAlignment="Top" Width="166" Text="{Binding Path=ResultString}" 
      Foreground="#FFEA1818" />
...

现在,我们可以将 MainPage 中剩余的代码从代码隐藏文件移到其 ViewModel。您可以在可下载的项目中查阅完整的代码。

此时只有一个小问题(会有很多其他问题)在于将代码移出代码隐藏文件。`MainViewModel` 类不知道地图控件。而且它也不应该知道。地图控件是通过方法调用来控制的,因此没有简单的绑定机制允许访问我们需要的所有功能。我搜索了一下,看看是否有快速的解决方案,但没有找到。我也知道我打算添加比普通地图查看器更多的交互,所以我不想一开始就把自己逼入绝境。如果以后我找到了将功能移到 ViewModel 并使其更清晰的方法,我到时候再做。目前,我们将部分交互保留在 MainPage 的代码隐藏文件中。

那么,`MainViewModel` 如何处理按钮点击消息,而视图又能对事件做出反应,而 `MainViewModel` 又与视图没有关联呢?答案是使用消息。MVVM Light Toolkit 包含了一个很好的通用消息传递基础设施。它本质上是一个发布服务,允许订阅者“注册”接收消息,发布者“发送”(发布)消息。它非常类似于 Prism 中的 EventAggregator。

在我们的情况下,我们可以让 MainPage(视图)注册以接收当 `MainViewModel` 处理按钮点击事件时的通知。以下代码显示了这两个类中的更改。首先,定义一个 `SetMapViewMsg` 消息

namespace SLMapTest.Messages
{
    public struct SetMapViewMsg
    {
        public Location CenterLocation;
    }
}

该消息包含翻译后的用户输入的地址的纬度/经度值,作为 `Location` 属性。MainPage(视图)订阅该消息,因为它知道如何处理这些值,即将其传递给地图控件。而 `MainViewModel` 发布消息,因为它能够访问执行翻译的服务。以下是订阅方式

public MainPage()
{
    InitializeComponent();
    //Subscribe to message, we know what to do with user input
    Messenger.Default.Register<SetMapViewMsg>(this, SetViewHandler);
            
}
#region message handler
void SetViewHandler(SetMapViewMsg msgData)
{
    bingMap.SetView(msgData.CenterLocation, 18);
}
#endregion

以下是 MainViewModel 中为实现发布而修改的 `GeocodeCompleted`

...
    else
    {
        //What are we getting back?
        Location geoLocation = new Location(
          e.Result.Results[0].Locations[0].Latitude, 
          e.Result.Results[0].Locations[0].Longitude);
        // Zoom the map to the desired location
        Messenger.Default.Send<SetMapViewMsg>(
          new SetMapViewMsg() { CenterLocation = geoLocation });
    }
...

现在,如果我们编译并运行应用程序,我们应该回到了原点,但有了一个更好的基础来添加即将到来的附加功能。但在开始之前,UI 中只有一个(对我来说)令人讨厌的部分需要解决。

细节决定成败

当用户在文本框中输入地址时,最自然的动作是用 Enter 键结束输入。但是,您可以看到,如果运行应用程序,什么也不会发生。用户需要点击按钮才能处理输入。这很烦人,所以我认为我们需要解决这个问题。我们想要的结果是,当键盘上的 Enter 键被按下(针对文本框)时,系统的行为与按下“查找”按钮时一样。这更直观。

为了做到这一点,我们需要检查正在输入的每个字符,以便知道 Enter 键何时被按下。通常,您可以通过实现 KeyUp 事件的处理程序来实现。但是,由于我们正在实现_MVVM_,我们可以使用行为来实现该功能,特别是通过 MVVM Light Toolkit 提供的_EventToCommand_行为。这里的酷特性在于它允许我们传递我们需要的事件参数。以下是 MainPage XAML 的更改。当在文本框上生成 KeyUp 事件时,它实际上会调用 `KeyUpCommand` 方法,并将事件参数传递给该方法。

...
<TextBox Height="23" HorizontalAlignment="Right" 
  Margin="0,26,50,0" Name="textAddress" VerticalAlignment="Top" 
  Width="120" KeyUp="textAddress_KeyUp" Text="{Binding Path=FindString, Mode=TwoWay}">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="KeyUp">
            <cmd:EventToCommand 
                Command="{Binding Path=KeyUpCommand, Mode=OneWay}" 
                PassEventArgsToCommand="True"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </TextBox>
...

如您所见,我们设置了一个 KeyUp 事件的触发器,该触发器将在每次生成 KeyUp 事件时调用 KeyUpCommand 定义的方法(委托),并将 Event Args 传递给该方法。因此,在 `MainViewModel` 中,我们必须创建 `RelayCommand`(实现 `ICommand`)属性,并定义为每个 KeyUp 事件调用的方法。以下是代码摘要(您可以在可下载的项目中看到完整的代码)

...
        RelayCommand<KeyEventArgs> keyUpCommand;
        /// <summary>
        /// Initializes a new instance of the MainViewModel class.
        /// </summary>
        public MainViewModel()
        {
            findButton = new RelayCommand(FindButtonClick);
            //We want to know when user presses ENTER
            //in the find address text field
            keyUpCommand = 
              new RelayCommand<KeyEventArgs>(TextFieldCharEntry);
        }

...
        public ICommand KeyUpCommand
        {
            get { return keyUpCommand; }
        }
        void TextFieldCharEntry(KeyEventArgs e)
        {
            //If it's ENTER key...
            if (e.Key.Equals(Key.Enter))
            {
                if (findAddress != null && findAddress.Length > 0)
                {
                    GeocodeAddress(findAddress);
                }
            }
        }
...

这一边相当直接,对吧?所以,继续编译并运行应用程序。在文本框中输入一个地址并按 Enter 键,以验证您不需要使用“查找”按钮。

几乎...但又不完全是

如果您一直在跟随,您会注意到什么都没有发生!所以,让我们看看我们是否至少能够接收到按键事件。在上面代码块所示的检查 Enter 键的代码块中放置一个断点。再次运行它。您会注意到字符确实被传递了,并且 Enter 键_被_检测到了。但是,如果您检查 `findAddress` 属性,您会发现它是 null!这就是为什么代码没有执行的原因。问题在于,文本框在失去(输入)焦点之前不会执行绑定更新。这是有道理的,但会给我们带来问题,正如您所见。这也是当按下按钮时(焦点转移到按钮)它起作用的原因。

由于我们在输入时获取了每个字符,因此我们可以在检查 Enter 键的同时更新本地变量(用于地址)。但还有另一种方法可能更合适、更通用,或者只是更优雅。这也证明了代码隐藏文件中可以包含完全合适且不损害_MVVM_的代码。我认为这不会影响 ViewModel 的可测试性。

解决方案是在 MainPage 的代码隐藏文件中添加一个 KeyUp 事件处理程序。然后,在处理程序中,我们可以在检测到 Enter 键时强制进行绑定更新。以下是处理程序的代码

...
        private void textAddress_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == System.Windows.Input.Key.Enter)
            {
                //We need to force the update manually...
                var binding = textAddress.GetBindingExpression(TextBox.TextProperty);
                binding.UpdateSource();
            }
            base.OnKeyUp(e);
        }
...

这样就完成了。现在编译并运行应用程序。当用户按下 Enter 键时,系统会以与按下“查找”按钮相同的方式响应。

目标

现在,我们已准备好开始向应用程序添加一些新的地图相关功能。我们已经打下了良好的基础。我们将着眼于地图控件,目标是了解它如何用于改进典型的业务流程,以及它如何用于创建新的和新颖的解决方案。然而,即使必应地图是视觉粘合剂,最终目标仍然是将 Silverlight、Azure 和 Phone 三者结合成一个完整的解决方案;这是我的终极学习目标。

因此,在第二部分中,我们将继续学习如何绘制地图,进行反向地理编码、路线规划和其他很酷的功能。为了实现其中的一些功能,我们将利用一些其他的必应地图服务。这就是我从这个角度看到的。我确信会有些我无法预见的惊喜和挑战,这没关系,因为这将增加学习的乐趣。

© . All rights reserved.