Windows Phone 7

您的首个 Windows Phone 应用程序

Jesse Liberty

编写首个 Windows Phone 应用程序的决窍是构建足够有意义且足以真正实现的简单内容。为此,我将指导您创建一个我日常使用的简单实用程序: NoteToMe。创建思路是,您可以输入一则消息,然后通过按一个按钮将其发送给自己,如图 1 所示。

The NoteToMe Interface
图 1 NoteToMe 界面

请注意,本文将涉及众多主题,我将在后续文章中非常详细地介绍其中每个主题。这些主题包括:

  • 创建应用程序布局
  • 在独立存储中存储和检索数据
  • 事件和事件处理
  • 创建和运行任务(启动器和选择器)

在开始之前,您将需要从 http://xbox.create.msdn.com 中下载相关工具。如果您已解锁您的手机,但尚未升级到 Windows Phone 7.5(“Mango”),那么现在是升级到该系统的时候了;Mango 在 Windows Phone 操作系统中引入了数百个新功能。

开始使用

与许多 Windows Phone 开发人员一样,我相信创建 Windows Phone 应用程序的最佳做法是结合使用 Visual Studio(用于编写代码)和 Expression Blend(用于完成其他工作)工具。因此,我们将首先打开 Expression Blend,然后基于 Windows Phone SDK 7.1 创建一个名为 NoteToMe 的新应用程序。

让我们从更改应用程序标题开始。单击该标题,然后在“属性”窗口中,找到该控件的“文本”属性。Metro 设计准则(Windows Phone 的设计准则)要求标题全部为大写字母,所以请将标题更改为 NOTE TO ME。

单击页面标题,然后点击“删除”将其删除。

为创建布局,您在页面顶部附近需要一小行。单击边缘,将显示一条可帮助您直观地选择该行的放置位置的参考线,如图 2 所示。

Placing the Top Row of the Layout
图 2 定位布局的顶端行

当然,您可以直接在 XAML 中手动设置行大小:

<Grid.RowDefinitions>
  <RowDefinition Height="1*"/>
  <RowDefinition Height="9*"/>
</Grid.RowDefinitions>

值后面的星号表示相对大小 - 在本例中为 1:9。即,第一行的大小将是第二行的 1/9。

向 StackPanel 中添加三个控件

顶端行中将有三个并排控件:

  • 一个充当标签的文本块
  • 一个用于容纳电子邮件地址的文本块
  • 一个用于发送消息的按钮

此设计如图 3 所示。

Three Controls in the Top Row
图 3 顶端行中的三个控件

如果未将三个控件放在某种类型的组织容器中,则不能将它们放在单行的单列中。我将使用其方向已设置为水平的 StackPanel - StackPanel 彼此堆叠或彼此相连。

若要创建 StackPanel,请单击工具栏上“布局”控件旁边的白色小箭头,如图 4 所示。

Adding a StackPanel
图 4 添加 StackPanel

单击 StackPanel 以选择控件。现在将一个 StackPanel 拖动到行中,并在“布局”窗口中将其水平和垂直对齐方式设置为“拉伸”,将其边距设置为零,如图 5 所示。

Placing the StackPanel
图 5 定位 StackPanel

添加文本块,并将其字号设置为 32,将其文本设置为 To(收件人)。现在将一个文本框拖动到 StackPanel 上。(注意用于显示文本的文本块和用于文本输入的文本框之间重要且细微的差别。) 将此文本框命名为 Address(地址)。最后,向 StackPanel 中添加一个按钮,将其命名为 Send(发送),并将其 Content(内容)设置为 Send(发送)。

图 6 显示此生成的 XAML。

图 6 使用 XAML 设计 StackPanel

<StackPanel
  Margin="0"
  Orientation="Horizontal">
  <TextBlock
    Margin="0,8"
    TextWrapping="Wrap"
    Text="To"
    Width="42"
    HorizontalAlignment="Left"
    VerticalAlignment="Center"
    FontSize="32" />
  <TextBox
    x:Name="Address"
    Margin="0,0,0,-7"
    TextWrapping="Wrap"
    Text="foo@bar.com"
    Width="293" />
  <Button
    x:Name="Send"
    Content="Send"
    Margin="0,4,0,0"
    Width="124"
    Click="Send_Click" />
</StackPanel>

请注意,Send(发送)按钮具有 Click=“Send_Click” 属性。 单击该按钮可创建此属性,接下来,在“属性”窗口中单击“事件”按钮,如图 7 所示。

The Events Button
图 7“事件”按钮

这将打开该按钮的所有事件。 找到 click 事件并双击。 会使用该事件更新按钮,并且您会转到该事件处理程序的代码编辑器(在 Blend 或 Visual Studio 中,具体取决于您设置 Blend 的方式)。 您暂时可将此事件处理程序保持原样:

private void Send_Click( object sender, RoutedEventArgs e )
{
}

添加消息控件

单击工具栏中的文本框控件,然后将文本框拖出,以填充剩余页面的一半位置(我们将另一半位置留给键盘,当需要在文本框中输入内容时,会显示键盘。) 将“水平对齐方式”设置为“拉伸”,将“垂直对齐方式”设置为“顶端对齐”,将边距设置为 0。 将宽度设置为“自动”,将高度设置为 244。 可在调整文本框的大小时通过目测完成所有这些操作,也可在适当的位置粗略绘制文本框,并在“属性”窗口中设置相应的属性,如图 8 所示。

Adding the TextBox
图 8 添加文本框

编写代码

控件就绪后,您就可以处理程序的逻辑了。 您应在左上角看到一个名为“项目”的选项卡。 保存所有更改后,单击“项目”选项卡,然后右键单击 MainPage.xaml.cs 并选择“在 Visual Studio 中编辑”,如图 9 所示。

Getting Ready to Write the Code
图 9 准备编写代码

规范

我的(自愿设定的)规范指出,您应该不必在每次使用程序时都填写“To”(收件人)字段;“To”(收件人)字段中应预先填充了上次使用“To”(收件人)字段时填充的内容。

而且,当您单击“Send”(发送)时,应该已为您的电子邮件程序准备了一封新的电子邮件,其所有字段均已预先填充,这样您便只需按“Send”(发送),也可以选择编辑该邮件,然后按“Send”(发送)。

使用独立存储

若要在使用应用程序时保留“To”(收件人)字段的内容,您需要在手机的某个位置存储这些内容。 此处是独立存储的用途: 在关闭应用程序时保留数据。 正如其名称所示,独立存储允许您的应用程序独立存储数据,并防止其他应用程序存储数据。 使用独立存储非常简单。

首先,添加 using 语句:

using System.IO.IsolatedStorage;

声明一个类型为 IsolatedStorageSettings 的成员变量和一个充当独立存储词典中的关键字的常量字符串,以便可以存储和检索电子邮件地址:

private IsolatedStorageSettings _isoSettings;
const string IsoKey = "EmailAddress";
Initialize the _isoSettings member in the constructor:
 _isoSettings = IsolatedStorageSettings.ApplicationSettings;

存储和检索电子邮件地址

与独立存储相关的两个任务是存储和检索字符串。 最好在您离开页面时存储它。 当您离开任何 Windows Phone 页面时,都会调用 OnNavigatedFrom 方法。 您可以随时重写该方法,而这样做的一个充分理由是需要在独立存储中存储数据,如下所示:

protected override void OnNavigatedFrom(
  System.Windows.Navigation.NavigationEventArgs e )
{
  _isoSettings[IsoKey] = Address.Text;
  base.OnNavigatedFrom( e );
}

您现在已将电子邮件地址存储在 _isoSettings 词典中的 IsoKey 关键字下。 当您返回到该页面时,可还原此设置。 我通过从构造函数中调用专用 Helper 方法 RestoreEmailAddress 来实现这一目的:

private void RestoreEmailAddress()
  {
    if (_isoSettings.Contains( IsoKey ))
      Address.Text = _isoSettings[IsoKey].ToString();
  }

请注意,我在尝试还原此设置之前,执行了该关键字在独立存储中是否存在的测试操作 - 这可防止首次运行该程序时出现 KeyNotFound 异常。 请记住,在您首次运行程序时,尚未在独立存储中存储任何内容。

首次启动该程序时,“Address”(地址)字段中没有任何内容。 用户在地址字段中输入电子邮件地址后,该地址会存储在独立存储中,并在下次运行该程序时还原。 如果用户更改地址,该新地址即为还原后的地址。

任务

Windows Phone 7.5 支持大量可与内置手机应用程序(邮件、联系人列表、相机等)交互的任务。 有两种类型的任务: 启动器和选择器。 选择器用于选择信息并将其返回到程序(例如,为了从联系人列表中获取电子邮件地址)。 启动器用于启动不返回数据的程序。

在本例中,您已完成发送邮件所需的全部工作,所以可调用电子邮件启动器并提供所需的字段。 在电子邮件启动器上调用“Show”(显示)时,将使用您的数据启动电子邮件应用程序,但您不会获得任何数据(这样很好;您不需要任何数据)。

发送电子邮件后,如果您需要发送其他邮件,将返回到该程序。

创建启动器的所有工作都封装在“Send”(发送)按钮的 click 事件处理程序中。 让我们先创建 EmailComposeTask 的一个实例(启动器)。 填充相关字段并调用“Show”(显示)。 就这么简单:

private void Send_Click( object sender, RoutedEventArgs e )
{
  EmailComposeTask emailComposeTask = new EmailComposeTask();
  emailComposeTask.Subject = "Send To Me";
  emailComposeTask.To = Address.Text;
  emailComposeTask.Body = Message.Text;
  Message.Text = String.Empty;
  emailComposeTask.Show();
}

调用“Show”(显示)时,邮件的主题、地址和正文会传递到您的电子邮件应用程序中。 如果您有多个电子邮件应用程序,会询问您要使用哪一个。 将创建一封地址和格式都正确的电子邮件,便于您准备发送。

应用程序生命周期

如果确信用户在发送邮件前从不会中断其对应用程序的使用,则您已经大功告成。 但在现实中,用户会在撰写邮件的过程中停下来,并启动其他应用程序。 当他们返回时,如果他们所做的工作丢失,他们会感到不高兴。

若要了解如何防止出现这种情况,您需要了解一些关于应用程序生命周期的信息,以及如何在保持相应状态的同时仍支持 Mango 的一项最强大的功能: 快速应用程序切换。

当启动应用程序(假定从“开始”菜单启动)时,会引发 Application Launching 事件。 应用程序启动后,当用户每次导航到您的页面时,都会调用 OnNavigatedTo 方法,随后页面会进入“正在运行”状态。 如果用户启动新应用程序,您的应用程序会收到 Application Deactivated 事件,并进入休眠状态。 如果手机的内存不足,则您的应用程序可能处于逻辑删除状态。

在逻辑删除或休眠状态下,可终止或还原您的应用程序。 目前我们关心的是还原应用程序时所发生的情况。

如果您的应用程序处于休眠状态,那么在还原时,您不必执行任何操作,而且也不需要执行任何操作;当应用程序处于休眠状态时,会保持该状态,随时待命。

但如果应用程序处于逻辑删除状态,那么当应用程序返回时,您确实需要还原页面状态,以便向用户表明,该应用程序在切换出来时处于运行(或至少处于休眠)状态。

因此,您需要完成两个任务:

  1. 在调用页面的 OnNavigatedFrom 方法时保存相应状态。
  2. 在调用页面的 OnNavigatedTo 方法时可能需要还原相应状态,即,如果应用程序处于逻辑删除状态,则还原状态;如果处于休眠状态,则不需要还原。

在离开页面时保存相应状态

因为您无法知道,页面什么时候会收到 OnNavigatedFrom、还原时页面可能处于何种状态,所以您必须存储状态,以备不时之需。 这很容易实现: 您可以使用其语法与独立存储词典非常类似的 State 词典,但您必须知道编写 State 词典的目的不是为了永久存储,并且在退出程序或关闭手机时,实际上会损坏该词典。

让我们先创建一个常量字符串 StateKey,您将在 State 词典中将其用作偏移量:

const string StateKey = "MessageState";

在 OnNavigatedFrom 方法中,您将在 State 词典中存储状态(在本例中,为消息框中的内容):

protected override void OnNavigatedFrom(
  System.Windows.Navigation.NavigationEventArgs e )
{
  _isoSettings[IsoKey] = Address.Text;
  State[StateKey] = Message.Text;
  base.OnNavigatedFrom( e );
}

在创建页面时还原相应状态

当调用 OnNavigatedTo 方法时,如果应用程序处于休眠状态,那么您不需要执行任何操作来还原状态,但如果应用程序处于逻辑删除状态,则确实需要执行一些操作。

您可以通过在构造函数中将某标记设置为 false,然后将其设置为 true 来区分休眠或逻辑删除状态。 如果应用程序处于休眠状态,则不会调用构造函数;如果它处于逻辑删除状态,则会调用构造函数(因为将首次构造该程序),如下所示:

bool isNew = false;
  public MainPage()
  {
    InitializeComponent();
    isNew = true;

可在 OnNavigatedTo 中检查该标记:

protected override void OnNavigatedTo(
  System.Windows.Navigation.NavigationEventArgs e )
{
  if (isNew)
  {
    if (State.ContainsKey( StateKey ))
    {
      Message.Text = State[StateKey].ToString();
    }
  }
  isNew = false;
  base.OnNavigatedTo( e );
}

此测试会节省从 State 词典中还原值所需的时间。可通过先正常运行程序(在这种情况下,当您切换到其他应用程序时,您的程序会进入休眠状态),然后强制该程序进入逻辑删除状态来执行这一测试。可通过如下方法强制您的程序进入逻辑删除状态:右键单击项目,选择“属性”,选择“调试”选项卡,并选中“Tombstone upon deactivation while debugging”(调试期间在停用后进入逻辑删除状态)复选框。

在选中此复选框的情形下运行程序时,您会在返回页面时注意到明显的暂停,这是因为必须还原该状态。

最后概述

在这篇简短的文章中,我简要介绍了如何编写您的第一个重要的 Windows Phone 应用程序。我先在 Expression Blend 中创建该应用程序,在此过程中我创建了一个行,并使用 StackPanel 来设计控件布局。

然后,我切换到 Visual Studio,以编写按钮的事件处理程序的逻辑,并使用独立存储来保存电子邮件地址。我使用了 State 内存,以确保应用程序能够在进入逻辑删除状态后正常地重新启动。

正如前面所说,对于其中每个主题,还有许多相关内容要介绍,我将在后续文章中详细介绍它们。

Jesse Liberty 是 Windows Phone 团队的高级开发人员-社区推广专家。 Liberty 主办了受欢迎的 Yet Another 播客 (jesseliberty.com/podcast),并且他的博客 (jesseliberty.com/) 也值得一读。 他是许多热销书的作者,其中包括“Programming Reactive Extensions and LINQ”(Apress,2011 年)和“Migrating to Windows Phone”(Apress,2011 年)。 请在 Twitter 上关注 Liberty:twitter.com/JesseLiberty

衷心感谢以下技术专家对本文的审阅:Drew BatchelorCheryl Simmons