MVVM

在 Windows 8 中使用 MVVM 模式

Laurent Bugnion

下载代码示例

对任何基于 XAML 的框架已具有经验的任何编程人员可能至少已听说过 Model-View-ViewModel (MVVM) 模式。一些编程人员一直在其所有 Windows Presentation Foundation (WPF)、Silverlight 或 Windows Phone 应用程序中广泛使用此模式。其他一些编程人员一直避免使用此模式,原因在于他们误解了此模式的确切功能,或者他们不希望将其所见内容作为一种新的复杂性级别添加到其应用程序中。

您不必使用 MVVM 模式构建基于 XAML 的应用程序。使用传统模式(如基于事件的交互)来创建具有吸引力的应用程序是完全可能的。然而,分离的模式(如 MVVM)可带来相当多的优势。值得注意的是,MVVM 模式可以极大提升 Expression Blend 中的体验,并加快设计人员-­开发人员工作流。

最近(尤其是将 XAML 扩展到新平台,如 Windows Phone 和 Windows 8 之后),MVVM 的使用情况已提升到了一个全新的级别,从只有几个酷爱钻研技术的编码员使用的小环境模式,发展到 Microsoft 鼓励采用的主流做法。

前置条件

本文演示了如何将 MVVM 模式与 MVVM Light Toolkit(一种有助于基于 XAML 的应用程序中常见操作的开放源代码工具包)结合使用。若要全程遵循示例操作,需要 Visual Studio 2012 以及最新版本的 MVVM Light Toolkit。请注意,您可以使用免费提供的 Visual Studio 2012 Express 版本。当然,也可以使用其他任何版本。

可从 bit.ly/vs12rc 下载 Visual Studio 2012(Ultimate、Premium 或 Professional 版本)。免费的 Express 版本可以从 bit.ly/vs12express 下载。

可以从 mvvmlight.codeplex.com 的“下载”部分下载 MVVM Light Toolkit 安装程序。安装 MSI 之后,会打开一个“自述文件”页,您可以为您使用的 Visual Studio 版本选择安装项目和项模板。

Windows 8 中的 MVVM 模式

Windows 8 依赖于称为 Windows 运行时 (WinRT) 的一组新的 API。有三种可能的方式(有时称为“投影”)对 Windows Runtime 上运行的应用程序编程。

对于具备基于 XAML 的其他框架(如 WPF、Silverlight 或 Windows Phone)技能的人员而言,第一种方式可能是最直观的。它使用 XAML 作为前端,使用 C# 或 Visual Basic 作为其逻辑。尽管 Silverlight 与 WinRT XAML/C#/Visual Basic 投影之间存在一些差异,但技能和多数概念都是相同的。实际上,MVVM 是最适用于此投影的模式,原因与在那些早期框架中是相同的。

第二种方式:熟悉 HTML 和 JavaScript 的开发人员还可以使用基于 HTML 的投影对 Windows 应用商店应用程序编码。随着 XAML 中 MVVM 的逐渐普及,HTML 开发人员也已经想要在 HTML/JavaScript 中使用数据绑定。但由于缺乏数据绑定机制,必须从头开始重新创建此功能。这就是 knockout.js (knockoutjs.com) 之类的框架的用途所在。这类 MVVM 框架还可用于开发 Windows 应用商店应用程序。

还可以使用名为 WinJS 的 Microsoft 专用框架。WinJS 中的控件(如 GridView、SemanticZoom 等)与 XAML 中的控件相似,也支持数据绑定。有关 WinJS 的详细信息,请参阅 bit.ly/winjsbindingbit.ly/jsbinding 中详细说明了用于 JavaScript 的更多数据绑定框架。

项目 Hilo(C++ 和 XAML)

第三个 WinRT 投影使用 XAML 作为前端,使用非托管 C++(不具备 Microsoft .NET Framework)作为其逻辑。Hilo (bit.ly/hilocode) 是 Microsoft 模式和实施方案中的一个示例项目,目的在于演示使用 C++ 和 XAML 创建 Windows 应用商店应用程序的各个方面。

由于可使用 XAML 和数据绑定,您可以将 MVVM 模式与非托管 C++ 配合使用。Hilo 演示了如何在 C++ 中使用 MVVM,此外还介绍了其他主题,如:C++ 中的异步编程、使用图块、页面导航、使用触控、处理激活、暂停和恢复、创建高性能体验、测试应用程序和认证。

Hilo 附带完整的源代码和完整文档。对于要使用现代开发模式开始 Windows 8 入门的传统 C++ 开发人员以及想要使用 C++ 堆栈的额外性能的 C#/Visual Basic 开发人员而言,Hilo 都是理想的案例研究。

MVVM 提示

MVVM 模式是另一个为人熟知的分离模式 Model-View-Controller(简称 MVC)的变体。MVC 模式用在众多框架中,特别是广泛使用的 Ruby on Rails Web 应用程序框架以及 Microsoft 的 ASP.NET MVC。MVC 不仅用在 Web 应用程序中,还广泛应用于从桌面应用程序到移动应用程序(例如在 iOS 中)在内的多种应用程序中。

分离模式的主要优势是它向每一层分配明确界定的职责。模型是数据来源;视图向用户显示内容并接受用户的输入。对于控制器,它有些类似乐队指挥,负责协调应用程序的操作和响应。例如,控制器经常负责协调根据用户输入显示数据、启动和停止动画播放等操作。

明确界定职责之后,开发小组成员可以专注于各自负责的部分而不会相互干扰。同样,交互设计人员也不必担心数据创建或持久性等问题。

关注点分离后的一个优点是:分离的应用程序也更容易测试了。例如,可以对模型的各个类进行单元测试,而不必考虑视图。由于模型与视图之间的交互已明确界定并且由控制器协调,就有可能对需要创建一致测试条件的那些交互进行模拟。这就是经常将可测试性作为在基于 XAML 的应用程序中使用 MVVM 模式的优势之一的原因。

从 MVC 到 MVVM

尽管 MVC 模式在许多框架中都有明显的优势,但由于数据绑定系统的原因,它并不是最适用于基于 XAML 的框架的模式。这种功能强大的基础结构可以绑定不同对象的属性并使其保持同步。这听起来好像不值一提,但其意义颇为深远: 通过让数据绑定系统负责这种同步,开发人员可以专注于计算数据对象属性的值而不必担心如何更新 UI。

此外,绑定只是松散耦合。该绑定只会按需评估,而且即便绑定的结果无效,应用程序也不会崩溃。这种“松散性”有时可能让开发人员困惑不已,因为很难找到无法正确评估某些绑定的原因。但是,为了更便于操作 UI,不与数据结构进行紧密耦合反而是一个优点。松散绑定后,很容易移动视图上的 UI 元素,甚至可以在不更改基础层的情况下完全更改应用程序的外观。

为了便于数据绑定以及避免非常大的对象,控制器细分为称为 ViewModel 或表示模型的更精细的小型对象。常见的用法是:将某个视图与某个 ViewModel 配对,只需将该 ViewModel 设置为该视图的 DataContext 即可。但实际情况并非始终如此;多个视图与给定 ViewModel 关联或是复杂视图拆分为多个 ViewModel 的情况也并不少见。

由于可以使用 XAML 来构建 UI,可以采用声明的方式直接在 XAML 文档正文中表示数据绑定。这会产生一个分离过程,此时开发人员专注于模型和 ViewModel,而在此期间甚至在这之后,交互设计人员可接管用户体验的创建。

最后,由于 XAML 基于 XML,它能与 Expression Blend 之类的可视化工具很好地配合使用。正确配置后,MVVM 支持将设计时数据显示到屏幕上,使设计人员能够处理用户体验而不必运行应用程序,如图 1 所示。

用于包含设计时数据的 Windows 应用商店应用程序的 Expression Blend
图 1:用于包含设计时数据的 Windows 应用商店应用程序的 Expression Blend

MVVM Light Toolkit

MVVM 模式的一个缺点是某些所需代码是有时称为“样板代码”的代码,即不直接执行函数但却是执行内部“探测”所需的基础结构代码。可能最好的样本代码示例就是使用 INotifyPropertyChanged 接口实现及其 PropertyChanged 事件使某个属性可观察所需的代码,如图 2 所示。此代码显示无法使用自动属性(有 getter 和 setter 但无正文的属性)。但该属性具有一个支持字段。在 setter 中进行了检查,以防止过多引发 PropertyChanged 事件。之后,如果属性值确实更改,则引发 PropertyChanged 事件。

图 2:可观察属性

private string _firstName;
public string FirstName
{
  get
  {
    return _firstName;
  }
  set
  {
    if (_firstName == value)
    {
      return;
    }
    _firstName = value;
    var handler = PropertyChanged;
    if (handler != null)
    {
      handler(this, new PropertyChangedEventArgs("FirstName"));
    }
  }
}

当然这是最糟糕的情况,此时对象的每个属性都需要大约 20 行代码。 此外,“魔幻字符串”用于标识属性名称,如果有拼写错误则可能造成问题。 为解决此问题,MVVM Light 提出了一些解决方法:

  • 引发 PropertyChanged 事件所需的逻辑可以存储在每个 ViewModel 继承自的 ViewModelBase 类中。
  • 可以通过 lambda 表达式而不是字符串来标识属性。 这样可以在属性名称更改时防止拼写或其他错误。
  • 其余行可使用 Visual Studio 中的代码段自动执行。

MVVM Light 提供很多有助于更便捷地创建分离应用程序的组件,您将在下文中提供的示例应用程序中看到这些组件。

Windows 8 还引入了一些新类(如 BindableBase 类和 CallerMember­Name 属性)来帮助解决此问题。 但遗憾的是,这些新类还不能用于其他基于 XAML 的框架,并且不能用在共享代码中。

在 Windows 8 中创建新的应用程序

最简单的创建新应用程序的方法是启动 Visual Studio 12 并选择 MvvmLight (Win8) 项目模板。 创建该应用程序后,通过按 Ctrl+F5 可立即启动该应用程序。 主页极为简洁,仅显示“欢迎使用 MVVM Light”,如图 3 所示。

创建新的 MVVM Light 应用程序
图 3:创建新的 MVVM Light 应用程序

更有趣的是在 Visual Studio 设计器中打开同一应用程序: 在解决方案资源管理器中,右键单击 MainPage.xaml,然后选择“视图设计器”。 首次加载可视化对象需要一些时间,但一旦加载完毕,屏幕会显示“欢迎使用 MVVM Light [设计]”。在 Expression Blend 中打开该应用程序也会显示相同的结果: 右键单击同一文件并选择“在 Blend 中打开”以便在窗口中查看设计时文本。 请注意,用于 Windows 应用商店应用程序的 Expression Blend 随 Visual Studio 2012(甚至 Express 版本)一同安装。 和以往在 Windows Phone 上使用一样,现在可以免费使用 Expression Blend。

刚才发生的情况可以说明几个有意思的问题: 可视化设计器运行应用程序的某些部分,可以通过某种方式来确定该应用程序在设计器中是否运行。 在可视化设计器中看到与运行时不同的视图对于交互设计期间良好的体验而言是一项至关重要的要求。 这样允许绕过在设计时不工作的 Web 服务调用或数据库连接。 同时还允许创建熟知数据,如很长的文本,以验证在不同方向或分辨率情况下的布局外观,而不必运行应用程序。 这样既可以缩短交互设计阶段所需的时间,也省去了很多麻烦。

这里要开发的应用程序是用户友人的查看器,该查看器连接到来自 Facebook 的一项 Web 服务(但已经大幅精简)。 该 Web 服务返回 JSON 格式字符串,其中含有关于用户友人的信息,比如名字、姓氏、出生日期、指向个人资料图片的 URI 等。 这些信息存储在于模型(MVVM 中的第一个“M”)层中创建的类型为 Friend 的实例中。 Friend 类继承 MVVM Light 库(由新应用程序引用)提供的 ObservableObject 类。 这样 Friend 类就很容易引发 PropertyChanged 事件。 请注意,即使 Friend 属于模型层这也是有意义的,因为这样做允许 UI 与 Friend 的属性进行数据绑定,而不必在 ViewModel 层中复制这些属性。

如前所述,实现可观察的属性需要一些样本代码,该问题尽管麻烦但已由 MVVM Light 缓解: 使用代码段添加一个 INPC 属性(代表 INotifyPropertyChanged,即定义 PropertyChanged 事件的接口)。 只需键入 mvvminpcset,然后按跳格键展开该代码段。 使用跳格键在字段之间切换: 输入属性的名称 (FirstName)、属性类型 (string)、支持字段的名称 (_firstName),最后还有默认值 (string.Empty)。 然后键入 Esc 退出代码段编辑模式。 此外,该代码段还添加一个包含属性名称的常量。 将来在注册到 PropertyChanged 事件时会用到该常量。

所有 MVVM Light 代码段都以 mvvm 开头,这样很方便在 IntelliSense 中找到它们。 针对该属性实现的变体还另有一些 mvvminpc 代码段。 使用 mvvminpcset,您还可以实现一个名为 LastName、类型为字符串的可观察属性。

FirstName 和 LastName 之类的属性是非常直观的。 但有时应用程序从 Web 服务获得的数据格式不是最佳的,需要进行转换。 在基于 XAML 的常规开发中,开发人员有时会使用转换器(IValueConverter 的实现)。 但在 MVVM 中,多数转换器都可由简单属性替代。 例如,我们考虑一下出生日期。 JSON 字段的格式为“MM/DD/YYYY”,这是美国采用的 日期表示格式。 但是,应用程序可能在任何区域设置中运行,所以需要转换。

首先,我们需要使用以前用过的同一代码段 mvvminpcset 添加一个类型为字符串、名为 DateOfBirthString 的可观察属性。 然后,按如下所示添加另一个属性(此属性将 DateOfBirthString 用作支持字段,负责将值转换为正确的 DateTime):

public DateTime DateOfBirth
{
  get
  {
    if (string.IsNullOrEmpty(_dateOfBirthString))
    {
      return DateTime.MinValue;
    }
    return DateTime.ParseExact(DateOfBirthString, "d",
      CultureInfo.InvariantCulture);
  }
  set
  {
    _dateOfBirthString = value.ToString("d",
      CultureInfo.InvariantCulture);
  }
}

但是还需要一样东西: 每当 DateOfBirthString 更改,还需要为 DateOfBirth 引发 PropertyChanged 事件。 这样,数据绑定将重新查询该值,并强制再次转换。 为此需要修改 DateOfBirthString 属性 setter,如下面的代码所示:

set
{
  if (Set(DateOfBirthStringPropertyName, 
    ref _dateOfBirthString, value))
  {
    RaisePropertyChanged(() => DateOfBirth);
  }
}

如果值更改,MVVM Light ObservableObject Set 方法将返回 true。 这也是为 DateOfBirth 属性引发 PropertyChanged 事件的暗示! 这种转换机制十分方便。 示例应用程序在若干位置使用该转换机制,例如将个人资料图片 URL(另存为一个字符串)转换为一个 URI。

实现和模拟数据服务

该应用程序已经有一个简单的 DataService,可以重复使用它来连接实际数据服务。 Web 服务是一个可返回 JSON 格式文件的简单的 .NET HTTP 处理程序。 由于可以使用 .NET Framework 4.5(async/await 关键字)中引入的异步编程模式,连接 Web 服务非常简单,如图 4 所示(与 IDataService 接口一同显示)。 连接是异步执行的,从 SendAsync 方法名称中即可看出。 但是,由于使用了 await 关键字,该方法不需要回调,从而避免使代码复杂化。

图 4:连接 Web 服务并反序列化 JSON 结果

public interface IDataService
{
  Task<IList<Friend>> GetFriends();
}
public class DataService : IDataService
{
  private const string ServiceUrl
    = "http://www.galasoft.ch/labs/json/JsonDemo.ashx";
  private readonly HttpClient _client;
  public DataService()
  {
    _client = new HttpClient();
  }
  public async Task<IList<Friend>> GetFriends()
  {
    var request = new HttpRequestMessage(
      HttpMethod.Post,
      new Uri(ServiceUrl));
    var response = await _client.SendAsync(request);
    var result = await response.Content.ReadAsStringAsync();
    var serializer = new JsonSerializer();
    var deserialized = serializer.Deserialize<FacebookResult>(result);
    return deserialized.Friends;
  }
}

最后,检索到 JSON 字符串之后,将使用 JSON 序列化程序将该字符串反序列化为类型为 FacebookResult 的 .NET 对象。 这个类只是一个帮助程序,在方法返回后即丢弃。

MVVM Light 还包含一个称为 SimpleIoc 的简单的控制反转 (IOC) 容器。 IOC 容器是一个不仅在 MVVM 应用程序中有用,而且在任何分离体系结构中都有用的组件。 IOC 容器充当实例缓存,可以按需创建,并且在应用程序中的多个位置解析。

DataService 实现 IDataService 接口。 有了 IOC 容器,就很容易模拟数据服务并创建设计器中使用的设计时数据。 这个类被称为 DesignDataService,如图 5 所示。

图 5:设计时数据服务

public class DesignDataService : IDataService
{
  public async Task<IList<Friend>> GetFriends()
  {
    var result = new List<Friend>();
    for (var index = 0; index < 42; index++)
    {
      result.Add(
        new Friend
        {
          DateOfBirth = (DateTime.Now - TimeSpan.FromDays(index)),
          FirstName = "FirstName" + index,
          LastName = "LastName" + index,
          ImageUrl = "http://www.galasoft.ch/images/el20110730009_150x150.jpg"
        });
    }
    return result;
  }
}

注册和调用服务

服务既已实现,现在就该实例化这些服务并创建 ViewModel。 这是 ViewModelLocator 类任务。 这个类在 MVVM Light 所启用的应用程序结构中非常重要。 它作为 XAML 资源在文件 App.xaml 中创建。 这将在 XAML 标记和源代码之间创建重要链接,允许可视化设计器创建 ViewModel 并运行设计时代码。 该注册显示在图 6 中。

图 6:注册服务和 ViewModel

static ViewModelLocator()
{
  ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
  if (ViewModelBase.IsInDesignModeStatic)
  {
    SimpleIoc.Default.Register<IDataService, 
      Design.DesignDataService>();
    SimpleIoc.Default.Register<INavigationService,
      Design.DesignNavigationService>();
  }
  else
  {
    SimpleIoc.Default.Register<IDataService, DataService>();
    SimpleIoc.Default.Register<INavigationService>(() => 
      new NavigationService());
  }
  SimpleIoc.Default.Register<MainViewModel>();
}

由于 MainViewModel 构造函数将一个 IDataService 和一个 INavigationService 作为参数,所以 SimpleIoc 容器能够自动创建所有对象。 MainViewModel 公开为 ViewModelLocator 的属性,并在 XAML 中进行了数据绑定,指向 MainPage 的 DataContext。 此绑定已在 MVVM Light 项目模板中创建。 在 Expression Blend 中打开该页时,将解析该数据绑定,创建 MainViewModel 实例(如果需要),并且使用正确的 DataService 实例(运行时或设计时)运行构造函数。

调用数据服务的操作使用 Relay­Command 来公开,如图 7 所示。 这个类是 MVVM Light Toolkit 的组件,用来实现 ICommand 接口,并提供简单的方法将 UI 元素(如按钮)的 Command 属性绑定到在 ViewModel 上实现的方法。

图 7:RelayCommand 类

private RelayCommand _refreshCommand;
public RelayCommand RefreshCommand
{
  get
  {
    return _refreshCommand
      ?? (_refreshCommand = 
      new RelayCommand(ExecuteRefreshCommand));
  }
}
private async void ExecuteRefreshCommand()
{
  var friends = await _dataService.GetFriends();
  if (friends != null)
  {
    Friends.Clear();
    foreach (var friend in friends)
    {
      Friends.Add(new FriendViewModel(friend, _schema));
    }
  }
}

友人添加到的 Friends 集合属于类型 ObservableCollection<FriendViewModel>。 此集合类型经常用在基于 XAML 的框架中,因为与此类集合进行的任何列表控件数据绑定会在其内容更改时自动更新其视图。 模型的 Friend 类实例不会直接存储到该集合中,而是封装在 ViewModel 层的另一个称为 FriendViewModel 的类中。 这允许添加不会在模型中保留的视图特定的属性。 例如,FriendViewModel 类公开名为 FullName 的属性。 根据应用程序的设置,它可以返回“FirstName, LastName”或“LastName, FirstName”格式的字符串。此类逻辑在 ViewModel 类中很典型,不应将其存储在应用程序的较低层中。

为了以正确格式返回 FullName 属性,FriendViewModel 侦听类型为 ChangeFullName­SchemaMessage 的消息。 该功能是通过 MVVM 的 Messenger 组件实现的,这是一个松散耦合的事件总线,用于连接发送方和一系列接收方,而不在二者间创建紧密连接。 请注意,这个 Messenger 可以发送任意消息类型,从整数之类的简单值到包含附加信息的复杂对象都可以发送。

接收到此类消息时,将为 FullName 属性引发 PropertyChanged 事件。 图 8 显示了 FriendViewModel 类。

图 8:FriendViewModel 类

public class FriendViewModel : ViewModelBase
{
  private FullNameSchema _schema = FullNameSchema.FirstLast;
  public Friend Model
  {
    get;
    private set;
  }
  public FriendViewModel(Friend model, FullNameSchema schema)
  {
    Model = model;
    Model.PropertyChanged += (s, e) =>
    {
      if (e.PropertyName == Friend.FirstNamePropertyName
          || e.PropertyName == Friend.LastNamePropertyName)
      {
        RaisePropertyChanged(() => FullName);
        return;
      }
      if (e.PropertyName == Friend.DateOfBirthPropertyName)
      {
        RaisePropertyChanged(() => DateOfBirthFormatted);
      }
    };
    Messenger.Default.Register<ChangeFullNameSchemaMessage>(
      this,
      msg =>
      {
        _schema = msg.Schema;
        RaisePropertyChanged(() => FullName);
      });
  }
  public string DateOfBirthFormatted
  {
    get
    {
      return Model.DateOfBirth.ToString("d");
    }
  }
  public string FullName
  {
    get
    {
      switch (_schema)
      {
        case FullNameSchema.LastFirstComma:
          return string.Format(
            "{0}, {1}",
            Model.LastName, Model.FirstName);
        default:
          return string.Format(
            "{0} {1}",
            Model.FirstName, Model.LastName);
      }
    }
  }
}

从“FirstName, LastName”切换为“LastName, FirstName”的指令是由 MainViewModel 发送的,如下所示:

private void SetSchema(FullNameSchema schema)
{
  _schema = schema;
  Messenger.Default.Send(new 
    ChangeFullNameSchemaMessage(_schema));
}

由于 Messenger 类采用分离方式操作,所以很容易将其从 MainViewModel 移入后面的 Settings 类之类的位置。

设置导航

得益于每个页面所公开的 Navigation­Service 属性,从视图的代码隐藏发起导航时,很容易在 Windows 8 中的页面之间进行导航。 但要从 ViewModel 发起导航,需要进行少许设置。 设置很简单,因为负责导航的框架以静态方式公开为 ((Frame)Window.Current.Content)。 该应用程序可以公开独立的导航服务:

public class NavigationService : INavigationService
{
  public void Navigate(Type sourcePageType)
  {
    ((Frame)Window.Current.Content).Navigate(sourcePageType);
  }
  public void Navigate(Type sourcePageType, object parameter)
  {
    ((Frame)Window.Current.Content).Navigate(sourcePageType, parameter);
  }
  public void GoBack()
  {
    ((Frame)Window.Current.Content).GoBack();
  }
}

图 8 中的代码显示了导航服务如何向 SimpleIoc 容器注册。 它作为一个参数注入到 MainViewModel 构造函数中,可用于直接从 ViewModel 层方便地开始 UI 导航。

在 MainViewModel 中,名为 SelectedFriend 的属性一更改即发起导航。 此属性与之前设置的其他属性一样是一个可观察的属性。 此属性将在 UI 中进行数据绑定,所以将由用户操作发起导航。 要显示友人,需要使用 GridView,它与 MainViewModel 上的 Friends 集合进行了数据绑定。 由于此 ViewModel 设置为 MainPage DataContext,所以很容易创建该绑定,如下所示:

<GridView
  ItemsSource="{Binding Friends}"
  ItemTemplate="{StaticResource FriendTemplate}"
  SelectedItem="{Binding SelectedFriend, Mode=TwoWay}"/>

在 GridView SelectedItem 属性与 MainViewModel SelectedFriend 属性(列在图 9 中)之间另外创建了一个 TwoWay 绑定。

图 9:MainViewModel SelectedFriend 属性

public const string 
  SelectedFriendPropertyName = "SelectedFriend";
private FriendViewModel _selectedFriend;
public FriendViewModel SelectedFriend
{
  get
  {
    return _selectedFriend;
  }
  set
  {
    if (Set(SelectedFriendPropertyName, 
      ref _selectedFriend, value)
        && value != null)
    {
      _navigationService.Navigate(typeof (FriendView));
    }
  }
}

最后,导航将用户引导到所选友人的详细信息页。 在此页中,DataContext 绑定到 MainViewModel SelectedFriend 属性。 这同样确保了良好的设计时体验。 当然,在设计时,构造 MainViewModel 时会执行 RefreshCommand,并设置 SelectedFriend 属性:

#if DEBUG
private void CreateDesignTimeData()
{
  if (IsInDesignMode)
  {
    RefreshCommand.Execute(null);
    SelectedFriend = Friends[0];
  }
}
#endif

此时,可以在 Expression Blend 中设计用户体验。 由于运行了设计时代码,Blend 在添加到 MainPage 的 GridView 中显示所有设计时友人,如图 1 所示。 创建数据模板肯定需要一些时间和技巧,但可以通过可视化方式轻松完成创建,而不必运行应用程序。

总结

以上过程对于熟悉 WPF、Silverlight 或 Windows Phone 中的 MVVM 模式的开发人员而言可能似曾相识, 这是因为一切都与 Windows 运行时相似。 这些以往框架中需要的技巧很容易转移到 Windows 应用商店应用程序的开发中。 当然,某些概念(如使用 async/await 进行异步编程)是全新的,并且需要执行一些操作将代码转换为 Windows 运行时。 但使用 MVVM 模式和 MVVM Light Toolkit 之类的帮助程序,开发人员将具备良好的开端,可以尽享分离应用程序模式的优势。

Laurent Bugnion 是 IdentityMine Inc. 的高级主管,目前在瑞士苏黎世工作。该公司是从事 Windows Presentation Foundation、Silverlight、Surface、Windows 8、Windows Phone 和用户体验等技术开发工作的 Microsoft 合作伙伴公司。

衷心感谢以下技术专家对本文的审阅: Karl Erickson 和 Rohit Sharma