剥洋葱 - 从头开始的 Windows Phone 7 编程(第 1 部分)






4.95/5 (71投票s)
从头开始学习 Windows Phone 7 编程。
引言
在本文中,我们将创建第一个运行“芒果”更新的Windows Phone 7应用程序。我们将从在Visual Studio中创建应用程序开始,然后我们将分析构成它的代码,以便熟悉Silverlight和XAML。一旦我们掌握了代码,我们将看到如何快速修改生成的代码以显示不同的文本并对按钮单击做出反应。
背景
最近,微软宣布发布了Windows Phone 7的一个更新[^],代号为“芒果”,它为WP7开发人员带来了大量一流的功能。我想到,尽管CodeProject上有很多很棒的WP7文章,但却没有一个从零开始教授WP7开发的资源。为了解决这个问题,似乎我们需要一系列文章,以友好而简单的方式教授WP7。
这些文章不假设您编写过任何WP7、XAML(读作“ZAMEL”)、XNA,也不假设您知道Silverlight和XNA是什么,或者依赖项属性和数据绑定等是如何工作的。希望在本系列文章结束时,您将学到足够的知识,能够轻松自信地开发WP7应用程序。
有些文章将演示如何使用Expression Blend,但如果您没有副本,请不要担心——我只是用它来设置用户界面的部分样式,您可以随意复制我将生成的模板。我们将在这些文章中使用的主要工具是Visual Studio 2010。在使用Silverlight时,我们将确保遵循设计Metro应用程序的指南。Metro指的是WP7应用程序的外观和感觉,以及应用程序的响应方式;WP7应用程序的一个指导原则是,它必须与手机上运行的其他应用程序相匹配,并且对于习惯Metro应用程序但以前没有使用过您的应用程序的人来说,必须易于上手。
与其重新提及大量关于Windows Phone 7历史的内容,我建议您阅读这篇文章[^],如果您对WP7的历史感兴趣的话。对我们来说,有趣的点是,WP7的开发可以使用Silverlight的一个版本,该版本旨在利用手机的功能,以及XNA的一个版本(一个用于开发在手机、XBOX和Windows PC上运行的游戏的优秀API)来开发手机游戏,并且在本系列文章结束时,我们将同时使用两者。
有用链接
您可能会发现以下链接在阅读本文时很有用。
- 最新版本的WP7 SDK可以在此处[^]下载。它在Windows Vista (SP2) 或 Windows 7 上运行,需要Visual Studio 2010 并带有Service Pack 1[^] 或 Visual Studio Express for Windows Phone,后者可以在此处[.]下载。
- Windows Phone Metro主题信息可以在这里[^]找到。
- Metro UI 指南可以在这里[^]下载。
必备组件
我假设您熟悉标准的 .NET 和 Visual Studio 概念,例如命名空间、类和代码隐藏文件。本文不会教您如何使用 C# 编码(如果您选择 VB.NET 也可以)。
我们的第一个WP7应用程序
经典的入门应用程序是“Hello World”,这个应用程序也不例外。那么,让我们启动旧的Visual Studio,系好安全带,享受进入Windows Phone世界的旅程吧。
Visual Studio打开后,选择“文件 > 新建 > 项目”以显示“新建项目”对话框。在已安装的模板列表中,查找“Windows Phone 版 Silverlight”部分,然后选择“Windows Phone 应用程序”(我将使用 C#,因此模板安装在“Visual C#”节点下)。我们将其命名为MyFirstPhoneApplication
,然后单击“确定”创建应用程序。

如果您安装了任何以前版本的WP7 SDK,您现在会看到一个对话框,要求您选择要定位的平台。选择“Windows Phone 7.1”。

如果一切按计划进行,我们应该有一个看起来像这样的解决方案(如果您没有“所有”未保存的已编辑部分——它来自我在开发环境中安装的一个插件,请不要担心):

那些以.xaml结尾的奇怪文件是什么?
XAML代表XML应用程序标记语言。基本上,在WP7中,XAML允许您声明性地布局用户界面,这些文件就包含这些内容。好吧,这听起来很棒,但它到底意味着什么呢?
当你创建任何类型的用户界面时,都存在一种隐式的父子关系。通常,你会有一个顶层表单,它包含一组子控件。其中一些子控件可能包含一组子控件。XAML允许你使用XML来表示这种层次结构,以识别不同的控件及其所属关系,以及这些控件的一些属性。需要记住的重要一点是,你在XAML中可以做的任何事情,都可以使用普通的C#或VB.NET来完成(但使用XAML会更容易)。但是我们为什么需要它呢?嗯,XAML允许设计人员在不了解任何代码的情况下布局用户界面,一旦你习惯了它,以这种方式布局你的界面就会变得非常自然。
好吧,你已经告诉我这些文件是什么了,但它们到底有什么用呢?
App.xaml 和 App.xaml.cs
如您所知,为了运行应用程序,我们必须有一个入口点。在WP7中也是如此,入口点就是Application。Application类的默认位置在这两个文件中(只要您看到*.xaml.cs*,就表示这是一个*.xaml*文件的代码隐藏文件)。让我们第一次看看XAML,看看*App.xaml*文件里有什么。
<Application
x:Class="MyFirstPhoneApplication.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone">
<!--Application Resources-->
<Application.Resources>
</Application.Resources>
<Application.ApplicationLifetimeObjects>
<!--Required object that handles lifetime events for the application-->
<shell:PhoneApplicationService
Launching="Application_Launching" Closing="Application_Closing"
Activated="Application_Activated" Deactivated="Application_Deactivated"/>
</Application.ApplicationLifetimeObjects>
</Application>
“哇,皮特。这看起来有点吓人。”我听到你这么说。别怕,我来告诉你,这东西远没有看起来那么可怕。让我们把它分解开来,弄清楚这一切意味着什么。
<Application
x:Class="MyFirstPhoneApplication.App"
...
</Application>
还记得我说过这个文件是基于XML的吗?毫不奇怪,这个文件遵循XML的规则,因此起始标签必须有一个匹配的结束标签。在这个例子中,标签是Application
,它告诉编译器这是包含Application
定义的XAML。下一行只是简单地告诉编译器这个特定文件的命名空间和类名是什么。如果您熟悉ASP.NET,您应该会发现这与您的*.aspx*文件顶部的Page
指令相似,其中的Inherits
标签告诉编译器ASPX继承自什么。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone">
这些行允许您直接在XAML中使用.NET命名空间中存在的功能。有两种连接命名空间的方式:一种是指定一个URI,该URI将在相关程序集中作为XML命名空间定义发布;另一种是使用clr-namespace
格式(如果命名空间不在当前程序集中,程序集部分会告诉编译器命名空间在哪个DLL中定义)。我们将在后续文章中更深入地讨论命名空间定义,届时我们将研究添加新程序集以及如何在XAML中与它们交互)。
总而言之,如果我们需要与默认命名空间(由 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 覆盖)之外的某个命名空间中的内容进行交互,那么我们需要在该元素前面加上我们在此处设置的命名空间名称。默认命名空间中的一个元素示例是 Application
(这就是为什么它不需要任何前缀的原因)。
<!--Application Resources-->
<Application.Resources>
</Application.Resources>
资源是可以在XAML中重复使用的项目,例如画笔、模板和样式。我们将在后续文章中深入讨论资源,但任何我们希望在当前应用程序的任何XAML页面中可用的资源都将放置在此部分中。这使我们不必将相同的元素复制到不同的页面中——您几乎可以将其视为已包含在每个页面中的CSS文件。
<Application.ApplicationLifetimeObjects>
<!--Required object that handles lifetime events for the application-->
<shell:PhoneApplicationService
Launching="Application_Launching" Closing="Application_Closing"
Activated="Application_Activated" Deactivated="Application_Deactivated"/>
</Application.ApplicationLifetimeObjects>
这个部分实际上非常酷,因为ApplicationLifetimeObjects
为我们做了什么。我们不必通过继承Application
类来添加额外功能,而是可以使用此部分列出扩展Application
类的扩展(是的,它们是标准扩展方法)。WP7提供了一个名为PhoneApplicationService
的标准扩展(注意使用shell: 来告诉我们它在Microsoft.Phone.Shell
命名空间中)。那么,这个类给我们带来了什么呢?它提供了对与应用程序生命周期的各个方面相关的方法的访问,例如应用程序启动时。这里列出的四个属性(这是在XAML中向对象添加属性和事件的简单方法)与Launching、Closing、Activated和Deactivated事件有关,并且事件处理程序位于文件App.xaml.cs中。
好吧,我们已经看到有代码链接到App.xaml.cs文件,那么它长什么样呢?与其列出整个文件,不如在Visual Studio中打开代码,我将解释每个部分的作用。
呼,代码量不小,但它有什么作用呢?同样,如果我们把它分解成小块,就更容易理解了。这次,我们不会涵盖所有代码,因为我们确实不需要讨论using
语句、命名空间和类定义。好了,我们来看看RootFrame
属性。
public PhoneApplicationFrame RootFrame { get; private set; }
所有WP7页面都显示在一个框架内,并且可以通过此属性访问该框架。如果我们将此视为基于浏览器的应用程序,那么RootFrame
将等同于Web浏览器本身,而页面将是单独的HTML页面。
public App()
{
// Global handler for uncaught exceptions.
UnhandledException += Application_UnhandledException;
// Standard Silverlight initialization
InitializeComponent();
// Phone-specific initialization
InitializePhoneApplication();
// Show graphics profiling information while debugging.
if (System.Diagnostics.Debugger.IsAttached)
{
// Display the current frame rate counters.
Application.Current.Host.Settings.EnableFrameRateCounter = true;
// Show the areas of the app that are being redrawn in each frame.
//Application.Current.Host.Settings.EnableRedrawRegions = true;
// Enable non-production analysis visualization mode,
// which shows areas of a page that are handed off to GPU with a colored overlay.
//Application.Current.Host.Settings.EnableCacheVisualization = true;
// Disable the application idle detection by setting the
// UserIdleDetectionMode property of the
// application's PhoneApplicationService object to Disabled.
// Caution:- Use this under debug mode only.
// Application that disables user idle detection will continue to run
// and consume battery power when the user is not using the phone.
PhoneApplicationService.Current.UserIdleDetectionMode = IdleDetectionMode.Disabled;
}
}
这是我们App
类的构造函数,因此一旦类初始化就会调用它。在此阶段,尚未创建任何可视项,也没有任何可视项可供挂钩,因此重要的是不要在此处添加任何依赖于可视元素显示的内容。
UnhandledException
这一行通常在我们的应用程序中是一个很好的做法,因为它提供了一个顶级错误处理程序,除了最极端的情况外,它都能保证被调用。错误处理程序实际上是在Application_UnhandledException
方法中处理的。
如果我们查看类,我们将找不到InitializeComponent
方法的任何实现,但是当我们编译代码时,这里没有错误。那么这是什么呢?编译器中是否有某种神奇的设置,当它在代码中遇到InitializeComponent
方法时不会生成编译错误?毫不奇怪,情况并非如此——真正的原因要平淡得多,线索在于类定义,这是一个部分类。当应用程序编译时,幕后会在特殊的*.g.cs*文件中为我们创建代码,而InitializeComponent
就是在这个类中实现的。
下一行只是简单地调用了稍后在类中定义的InitializePhoneApplication
方法。我们稍后将介绍该方法。
下一节涵盖了当调试器连接时应用程序的行为。我将在这里简要概述这些属性的用途(包括已注释掉的属性),而不是逐行介绍它们。在此阶段,即使我们尚未完成程序的编写,也让我们构建并运行它。当我们运行应用程序时,它会打开Windows Phone模拟器(请注意工具栏中的Windows Phone Emulator选项)。

模拟器启动时,看起来像这样

应用程序加载完成后,看起来像这样

好的,这是一个有趣的小插曲(我希望如此),但您可能想知道我在承诺解释Debugger.IsAttached
部分中的内容时正在做什么。嗯,如果您仔细看上面的图片,您会看到一些看起来像干扰的东西。如果我旋转、调整大小并稍微裁剪一下图片,我们可能会发现这里还有更多的事情发生。

那么,这些数字是什么?嗯,它们是被Application.Current.Host.Settings.EnableFrameRateCounter = true;
这一行启用的帧率计数器。从左到右,这些数字是
- 渲染线程帧率。这告诉我们渲染线程每秒输出的帧数。如果我们的应用程序图形密集,我们应该力争达到每秒60帧的帧率(数字越低,UI的响应速度就越慢)。
- UI线程帧率。这是主UI线程执行的帧率,管理属性更改通知、数据绑定和未在渲染帧上处理的动画等。
- 纹理内存使用量。这告诉我们用于存储应用程序纹理的视频内存量。
- 表面计数器。这显示了传递给图形芯片的表面数量。
- 所使用的中间纹理总数。
- 屏幕填充率。这描述了每一帧中绘制的完整手机屏幕数量。
如果我们取消注释Application.Current.Host.Settings.EnableRedrawRegions = true;
,我们就能看到每帧被重新绘制的项目。如果某个项目是由GPU绘制的,我们在这里将看不到重新绘制;这是我们想要的理想情况——重新绘制由GPU处理。
Application.Current.Host.Settings.EnableCacheVisualization = true;
这一行很有趣。它通过给未由GPU重新绘制的项目着色来告诉我们哪些项目没有被GPU重新绘制。如果一个项目由GPU处理并缓存,它将不会被着色。
PhoneApplicationService.Current.UserIdleDetectionMode = IdleDetectionMode.Disabled;
这一行附带了一个重要的警告注释是有原因的。在标准的手机应用程序模式下,空闲检测允许手机节省资源,并在应用程序空闲一段时间后进入“休眠”状态。如果我们在发布的版本中关闭此功能,我们的应用程序将持续耗电,因为它不会进入空闲模式。
Windows Phone GPU——旁注
WP7与大多数智能手机一样,支持图形处理器单元(GPU),可用于提高图形应用程序的性能。通常,我们可以让Silverlight为我们处理将工作委托给GPU的任务,但必须遵循一些规则来支持此行为。在本系列文章中,我们将看到一些区域和规则,它们可以帮助我们确定某些内容是否在GPU上运行。
现在,让我们回到代码。
// Code to execute when the application is launching (eg, from Start)
// This code will not execute when the application is reactivated
private void Application_Launching(object sender, LaunchingEventArgs e)
{
}
// Code to execute when the application is activated (brought to foreground)
// This code will not execute when the application is first launched
private void Application_Activated(object sender, ActivatedEventArgs e)
{
}
// Code to execute when the application is deactivated (sent to background)
// This code will not execute when the application is closing
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
}
// Code to execute when the application is closing (eg, user hit Back)
// This code will not execute when the application is deactivated
private void Application_Closing(object sender, ClosingEventArgs e)
{
}
这些方法是在PhoneApplicationService
扩展中连接的。在未来的文章中,我们将了解如何以及为何需要使用这些方法。
private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
{
if (System.Diagnostics.Debugger.IsAttached)
{
// A navigation has failed; break into the debugger
System.Diagnostics.Debugger.Break();
}
}
此方法在导航失败时,如果调试器已连接,则会中断到调试器。
private void Application_UnhandledException
(object sender, ApplicationUnhandledExceptionEventArgs e)
{
if (System.Diagnostics.Debugger.IsAttached)
{
// An unhandled exception has occurred; break into the debugger
System.Diagnostics.Debugger.Break();
}
}
这是处理未处理异常的事件处理程序。在未来的文章中,我们将深入研究如何使其成为一个功能更丰富的方法,并探讨如何使异常处理真正发挥有意义的作用。总而言之,默认实现只会中断到已连接的调试器,这在部署场景中并不是很有用。
#region Phone application initialization
// Avoid double-initialization
private bool phoneApplicationInitialized = false;
// Do not add any additional code to this method
private void InitializePhoneApplication()
{
if (phoneApplicationInitialized)
return;
// Create the frame but don't set it as RootVisual yet; this allows the splash
// screen to remain active until the application is ready to render.
RootFrame = new PhoneApplicationFrame();
RootFrame.Navigated += CompleteInitializePhoneApplication;
// Handle navigation failures
RootFrame.NavigationFailed += RootFrame_NavigationFailed;
// Ensure we don't initialize again
phoneApplicationInitialized = true;
}
// Do not add any additional code to this method
private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e)
{
// Set the root visual to allow the application to render
if (RootVisual != RootFrame)
RootVisual = RootFrame;
// Remove this handler since it is no longer needed
RootFrame.Navigated -= CompleteInitializePhoneApplication;
}
#endregion
}
}
App.xaml.cs谜题的最后一部分在于这两个方法。使用字段phoneApplicationInitialized
是为了防止此方法被调用两次。RootFrame
属性被初始化,并且Navigated
和NavigationFailed
事件被挂接。最后,根视觉对象被设置为根框架,并且导航事件被解除引用。
App.xaml 文件几乎就这些了。还有一个难题需要解决,那就是应用程序如何知道 App
类是启动类?嗯,如果我们打开项目属性对话框并查看“应用程序”选项卡,我们可以看到启动对象设置为 MyFirstPhoneApplication.App
。现在是时候看看 MainWindow
功能了。
MainPage.xaml
让我们看看MainPage.xaml的内容。如果它现在看起来很神秘,请不要担心,因为我们将对其进行分解并讨论不同的部分,看看它们是如何协同工作的。
首先,我们来看看实际的代码。
<phone:PhoneApplicationPage
x:Class="MyFirstPhoneApplication.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">
<!--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="page name" Margin="9,-7,0,0"
Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"></Grid>
</Grid>
<!--Sample code showing usage of ApplicationBar-->
<!--<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
<shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Button 1"/>
<shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Button 2"/>
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text="MenuItem 1"/>
<shell:ApplicationBarMenuItem Text="MenuItem 2"/>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>-->
</phone:PhoneApplicationPage>
嗯,这看起来相当吓人,但您会很高兴知道它实际上相当容易理解。
<phone:PhoneApplicationPage
x:Class="MyFirstPhoneApplication.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
...
</phone:PhoneApplicationPage>
就像在App.xaml文件中一样,开头属性设置了XAML背后的类,并添加了适当的命名空间以供XAML使用。如我们在此处所见,手机页面继承自PhoneApplicationPage
类型,该类型相当于浏览器中的HTML页面,或WinForms应用程序中的Form
。
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
这些属性很有趣。d:
属性是Expression Blend标签,告诉设计窗口要应用于页面的宽度和高度。如果我们愿意,这些尺寸可以与页面的实际宽度和高度不同——它们只是为了帮助UI设计器布局屏幕。mc:Ignorable
标签告诉编译器忽略XAML中以d:
开头的任何命名空间。如果现在这不太明白,请不要担心;当我们开始更多地使用设计窗口时,它会变得更清楚。
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
好的,这需要一点解释,所以请原谅我们暂时转到资源领域。
正如您无疑很清楚的,Windows应用程序允许我们将资源嵌入到应用程序中,我们可以在使用中访问这些资源。WP7应用程序也不例外——我们可以嵌入资源并轻松访问各个资源。在Silverlight应用程序中,我们使用一个名为ResourceDictionary
的东西来管理我们的资源。现在,访问资源项的语法看起来有点奇怪,但我们必须习惯它,因为我们会经常使用它。基本上,StaticResource
标记告诉编译器它需要从ResourceDictionary
中查找某个东西并应用它。{}符号的使用告诉编译器它将不得不绑定一个值,而不是简单地应用一个string
字面量。
这里不同资源的值来自手机的标准资源,因此我们不需要自己添加这些值。这些资源的详细信息可以在这里[^]找到。
虽然我们可以使用FontFamily="Segoe UI"
将FontFamily
设置为Segoe UI,但我们使用资源字典的事实意味着,如果我们要将字体更改为Verdana,我们只需要更改字典并重新编译即可。我在这里也不是随意选择Segoe UI,这是资源字典中PhoneFontFamilyNormal
的当前值。在可能的情况下,使用资源字典是一个好习惯,因为它很容易更改正在重用的元素;在这方面,它们就像常量一样。
SupportedOrientations="Portrait" Orientation="Portrait"
这两个属性告诉手机在查看页面时如何显示(Orientation
),以及是否可以以横向或纵向显示(SupportedOrientation
)。
shell:SystemTray.IsVisible="True">
系统托盘是手机显示屏顶部的一个便捷项,用户可以点击它来获取信号强度和电池寿命等信息。通过将可见性设置为true,用户可以访问此功能。我们只有在确实确定您的应用程序不应显示系统托盘时才应更改此项,因为用户已经习惯了它的存在。
下图用红色突出显示了系统托盘
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
...
</Grid>
最后,我们开始添加可视项目了。当我们要添加显示控件时,我们需要将它们添加到一些东西中,这些东西会告诉应用程序如何布局组件。在这个特定的例子中,我们有一个Grid
,它的行为与HTML中的Table
控件类似,可以用来将控件布局在行和列中。属性x:Name
给网格一个名称,代码后台可以使用它来访问网格并在需要时操作它。最后,这里的Background
属性使网格透明。
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
与HTML中定义表格行需要使用<TR>
样式的语法不同,XAML允许我们预先定义行,这是一个非常强大的功能,我们将在本系列文章中进一步探讨,因为它允许我们只需更改控件上的单个值即可指定其显示位置。如果我们手动编辑XAML,这可以节省大量时间,因为我们不必剪切和粘贴项目将其移动到新行。我们将在修改应用程序时使用此功能。
Height
属性告诉应用程序将行的大小设置为多大,但那些奇怪的大小是什么?当高度设置为Auto
时,行的大小完全基于内容的大小。当高度被引用为*
时,这意味着大小是可用空间的一部分,您有时会听到这被称为星形尺寸。在这个特定的情况下,它意味着它使用了所有剩余空间。
注意:如果我们不提供此部分,则会向我们的网格添加一个隐式行定义,该定义将占据整个网格的大小。
<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="page name" Margin="9,-7,0,0"
Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
网格的第一行将包含另一个控件容器。这次,它包含一个名为StackPanel
的控件,它告诉应用程序将控件放置在单独的行上,或者全部放置在同一行上。通过嵌套容器,我们可以轻松创建灵活的布局。同样,我们将在本系列文章中进行更多此类操作,因此我们将充分练习使用不同类型的容器。Grid.Row
属性是一个有趣的属性。如果我们查阅StackPanel
的文档并整晚搜索,我们不会在其中找到任何对Grid.Row
的引用。这是微软的遗漏吗?嗯,虽然MSDN偶尔会是传说中的,但在这种情况下并非如此。Grid.Row
是一个很棒的东西,称为附加属性,我们可以将其视为可以附加到任何类型对象的全局属性,它有助于控制对象的行为。在这种情况下,它告诉网格将StackPanel
放置在第一行(请注意,这是基于零的)。
Margin
用于控制元素之间的空间。
现在我们终于看到了一些用户可以看到的元素。在XAML中,我们有两个TextBlock
控件,它们显示来自Text
属性的文本。
好的,我们已经相当深入地讨论了样板代码,您可能已经迫不及待地想亲自动手了。那么,别再挠痒痒了,我们现在要添加一些代码。
Hello World
第一步是在 Visual Studio 中打开 MainPage.xaml。我们将使用设计窗口而不是直接编辑 XAML。如果您从未用过设计器窗口,您可以通过“设计”选项卡(下图中红色突出显示的部分)访问它

首先,让我们将文本“MY APPLICATION”更改为“Hello World”。确保“属性”选项卡已打开,然后单击设计视图中的文本,包含该文本的TextBlock
将被选中。

“属性”选项卡应如下所示:

好的,将文本更改为“Hello World”。然后选择页面名称并从Text
属性中删除文本。此时,我们的手机应用程序在设计器中应该看起来像这样

现在我们将向应用程序添加一个按钮。打开“工具箱”,双击按钮控件。这会将按钮添加到手机设计器的左上角。

不太吸引人是吧?嗯,我们马上就要看到那个网格行属性到底有什么用了。在属性窗口中,向下滚动直到您能看到Grid.Row
属性。

将值更改为1
,然后惊奇地看到按钮移动到下一行。与其向您展示此阶段的屏幕截图,不如让我们设置另外几个属性。将HorizontalAlignment
和VerticalAlignment
属性都设置为Center
以移动按钮。完成此操作后,双击按钮以在MainPage.xaml.cs中创建一个事件处理程序,并在生成的代码中添加以下代码
PageTitle.Text = "BOO !!!!";
这行代码设置了要在名为PageTitle
的TextBlock
中显示的文本。
最后,编译并运行应用程序。它应该看起来像这样:

现在按下按钮,然后看“BOO !!!!”出现。
实现这种魔术的XAML看起来像这样:
<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="Hello World"
Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="" Margin="9,-7,0,0"
Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"></Grid>
<Button Content="Button"
Height="72"
HorizontalAlignment="Center"
Margin="10,10,0,0" Name="button1"
VerticalAlignment="Center" Width="160" Grid.Row="1"
Click="button1_Click" />
</Grid>
该特定代码中最有趣的部分在于Click="button1_Click"
这行。它将按钮的点击事件与MainPage.xaml.cs中的button1_Click
方法绑定。
就这样,我们使用XAML和代码隐藏创建了我们的第一个WP7应用程序,它与XAML中定义的控件挂钩。
如您所见,在设计器中添加和编辑控件可以是一个非常简单的任务;我们对应用程序所做的更改都很快完成,并且省力。一旦您熟悉了工具,创建XAML可以是一个相对轻松的过程,正如我们快速修改代码以实现我们想要的功能所证明的那样。
最后一个值得思考的问题。手机怎么知道MainPage
是要显示的页面呢?毕竟,在XAML或代码隐藏中并没有立即显而易见的东西告诉编译器标记这个页面,在App.xaml中也没有对它的引用。答案在于文件WMAppManifest.xml,它创建了一个指向MainPage
的任务。本文不会深入探讨这个文件,但我们会在以后的文章中回来讨论它,届时我们将了解它如何影响固定和动态磁贴。
我们学到了什么?
在本文中,我们使用Visual Studio创建了我们的第一个WP7应用程序,并对其进行了修改以显示“Hello World”。在此过程中,我们分析了XAML应用程序的基础知识,并开始了解代码隐藏和XAML如何协同工作。最后,我们更改了项目以显示“Hello World”并响应按钮单击。
在未来的文章中,我们将扩展我们在这里获得的知识,并真正开始深入了解WP7开发。
延伸阅读
以下书籍可能对学习WP7有所帮助
- Adam Nathan的《101 Windows Phone 7 Apps,第一卷》[^]
- Nick Randolph 和 Christopher Fairbairn 的《专业 Windows Phone 7 应用程序开发:使用 Visual Studio、Silverlight 和 XNA 构建应用程序和游戏》[^]
- Charles Petzold 的《Microsoft Silverlight Edition: Windows Phone 7 编程》[^]
- Rob Cameron 的《Pro Windows Phone 7 开发》[^]
- Charles Petzold 的 Windows Phone 7 编程(这本是免费的)[^]
谢谢
我谨感谢Hans Dietrich、Keith Barrow、DaveAuld、Tom Deketelaere、gavinon以及所有其他为本文的创作提供了宝贵帮助的CodeProject优秀会员。如果我遗漏了任何人,那完全是我的过失,我深表歉意。如果我遗漏了您,请告诉我,我将相应地更新列表以反映您的钻石级身份。
评论
请注意,如果您觉得本文未能满足您的需求,或者其中有您不理解或我解释不清的地方,请告诉我。您对本系列的投入是无价的。请不要担心您无法清楚地记住语法或概念,随着本系列的进展,我们将更深入地涵盖每个领域。
历史
- 2011年7月——初始版本