第4章控件

控件

         尽管使用绘图语法对于设计一个令人兴奋的和动态的手机应用程序是非常有效,但是用户期望应用程序的大部分功能都与之交互。这就是控件的由来。控件支持与用户直接交互。交互的类型取决于控件本身。例如,按钮和滚动条使用触摸界面;文本框使用键盘(屏幕或硬件)。在你的手机应用程序中使用的控件,你需要从不同的角度思考如何构建应用程序。如果你只是简单的将你在Web上或桌面应用程序上的经验应用到手机上,你的应用程序将不容易使用。使用控件时考虑小屏幕和触摸界面,将帮助你构建引人注目的应用程序。

Silverlight中的控件

         控件与到其他目前为止的看到的任何XAML元素没有什么不同。例如,下面的TextBox控件:

         <Grid>

                   <TextBox Text="Hello World"

                                     Height="75"/>

         </Grid>

         这个TextBox控件将像绘图元素一样显示,但是它将通过触摸形式支持用户与控件交互(有光标为证,如4.1所示)。

                       

图4.1TextBox控件列子

         Silverlight for Windows Phone SDK 7.1提供了以下开箱即用的控件:

l  Button

l  CheckBox

l  HyperlinkButton

l  ListBox

l  PasswordBox

l  ProgressBar

l  RadioButton

l  RichTextBox

l  Slider

l  WebBrowser

l  TextBox

         这些控件代表了与用户交互的主要形式。当然这张列表有些缩减,这些控件是专门用来支持Windows Phone触摸界面的。这些控件中大多数创建的比你想象的中的要大(和更大的间距),来容纳用户触碰他们。

         在Silverlight中大多数的控件分为三个类别,这应该有助于你理解控件是如何工作的:

l  Simple controls 简单控件

l  Content controls 内容控件

l  List controls 列表控件

Silverlight控件

         如果你用现有的Silverlight知识来看这本书,你可能会惊讶于原生支持的控件列表的内容缩减。

         虽然许多Silverlight 4(和Silverlight   Toolkit for Silverlight 4)的控件可以在Windows   Phone中使用,微软并没有重新设计这些控件来方便地在手机上使用。如果你需要这些其他控件,它们并不是被禁止的;这取决于你是否需要改变他们的外观来符合Metro设计语言,以及他们能够在手机上充分的与触摸模式进行工作。

         微软选择的控件与手机的触摸界面进行了特殊的集成。当你开始查看其他控件时(例如,提示,日历,等等),你会看到在一个触摸环境中对于这些控件要找到正确的功能并不简单。因此,你可能会坚持使用内置的控件,直到你有一个好的感觉,用户是如何通过触摸方式与控件进行交互的。

简单控件

         简单控件包括文本框,密码框,滑块和进度条。这些控件都有一个简单的API,他们做特定的工作和一个直观的形象。总之,他们很简单:

         <StackPanel>

                   <TextBox Text="Hello" />

                   <PasswordBox PasswordChar="*" />

                   <Slider Value="5" />

                   <ProgressBar IsIndeterminate="True" />

         </StackPanel>

使用键盘

         TextBox 和 PasswordBox支持用户输入文本。对于有物理键盘的设备这很简单,但是对多数设备(并没有物理键盘)你必须使用软件输入面板(SIP)。当这些控件收到焦点时SIP就会被显示出来。你可以看到默认的SIP在图4.2(包括横向和纵向版本)。

 

图4.2软件输入面板(SIP)

         SIP试图把最常用的键直接放在键盘上,但也支持显示其余字符的方式。图4.3向你展示了这些特殊键。这些包括Shift键(在图中#1),&123键(#2),和特殊long-hold键(例如句点键,#3)。这个long-hold键提供了一种方法来弹出常用的键,使整个键盘看起来没有那么拥挤。例如,在图4.4中当用户按下句号键超过两秒,你可以看到标准的SIP句号键。

 

图4.3 SIP特殊键

 

图4.4Long-hold键

         这个默认的SIP外观只是手机上的支持的众多键盘布局中的一种。当你正在构建的应用程序需要文本输入时,你需要告诉控件使用何种SIP布局。基于控件的特性显示不同软键盘布局是很重要的。用户输入数据越快,他们会感觉越愉快。

         通过调用输入范围(input scope)就可以很简单的更改SIP的外观。例如,你可以指定使用聊天输入范围来让你有一些聊天功能特性(比如一个笑脸按钮)。就像这样:

         <TextBox InputScope="Chat" />

         对于简单聊天这种改变是非常友好的,如图4.5显示。

 

图4.5 聊天输入范围

         聊天输入范围添加了一个表情符号按钮,以及一个自动改正面板,以帮助用户更快速地输入他们想说的话。long-hold键中的按键也是可以针对输入范围进行特殊指定。例如,当用户在使用聊天输入范围的SIP上进行输入,惊叹号字就会位于这个句号键的long-hold列表中。但是如果用户输入一个URL,冒号和斜杠字符会出现在long-hold键中。

         虽然有大量的输入范围可供你使用,对于大多数应用程序的输入范围值列在表4.1中,这将帮助你选择一个正确的输入范围供你使用。

表4.1常见的InputScope值

输入范围

布局

使用场景

Default

标准的传统键盘

当输入词典中无法找到的单词例如用户名

Text

标准的传统键盘

在输入文本时,可以帮助自动校正和大写字母(包括拼错单词的可视指示器)

Chat

标准的传统键盘

当编辑聊天信息时(缩写是更重要的)如Twitter的或短信

URL

标准的传统键盘

当输入一个互联网网址

EmailSmtpAddress

标准的传统键盘

当输入一个邮件地址

TelephoneNumber

12键

当输入手机号码

Search

标准的传统键盘

当用户需要输入搜索短语(包括拼错单词的可视指示器)

NameOrPhoneNumber

标准的传统键盘

当输入名字(例如SMS短信)但是快速访问12键手机号码布局也是需要的

Date

标准的传统键盘

当输入日期;两个数字和字符日期(例如,12/12/2011和2011年12月12日)

Maps

标准的传统键盘

当输入地址;简化的输入,默认为数字入口

RichTextBox控件

         Windows Phone SDK 7.1包含一个特殊的控件,用于显示格式化的文本。这个控件叫做RichTextBox。使用RichTextBox控件你可以格式化文本通过使用简单的格式标记包括段落、粗体、斜体、和超链接标记。

         当并不需要HTML堆栈全部的功能(或复杂的功能),格式化意味着提供一些像HTML文本部分级别的的支持。例如:

<RichTextBox>

<Paragraph>You can use inline tags to format

         <Bold>bold</Bold> text and even add

         <Italic>italics</Italic>.

</Paragraph>

         <Paragraph>Using this Markup you can add

                   <LineBreak /> line breaks and even add

                   <Hyperlink NavigateUri="/SomePage.xaml">hyperlinks</Hyperlink>!

         </Paragraph>

         <Paragraph>Also arbitrary XAML:

                   <InlineUIContainer>

                            <StackPanel>

                                     <Ellipse Fill="Red"

                                                        Width="25"

                                                        Height="25" />

                                     <TextBox />

                            </StackPanel>

                  </InlineUIContainer>

         </Paragraph>

</RichTextBox>

         RichTextBox控件支持一系列的标签,见表4.2。

Silverlight开发者

         对于你们中那些使用Silverlight for the desktop的开发人员,在手机上的RichTextBox文本框支持只读模式。

表4.2RichTextBox标签

标签

描述

Paragraph

文本信息的主要代表容器。RichTextBox通常包含一组Paragraph标签

Bold

用粗体格式化标签内部的文本

Italic

用斜体格式化标签内部的文本

Underline

用下划线格式化标签内部的文本

Run

包含用于格式化的未格式化的文本

Span

包含任何元素来添加格式。在当前的Windows Phone版本不能包含Paragraph,InlineUIContainer,或超链接标记。

LineBreak

在格式化文本中强行换行

Hyperlink

用于创建任意的文本,单击时将打开新的一页。

InlineUIContainer

允许任意XAML内联插入到RichTextBox的文本中。

内容控件

         内容控件允许你在他们内部包含任意的XAML。这些控件中最常见的是Button控件。例如,你可以通过设置Content属性简单的在一个按钮上显示一段文本消息,就像这样:

<StackPanel>

         <Button Content="Click Me" />

</StackPanel>

         你可以看到控件的内容 (“点击我”)现在显示在这个按钮内,如图4.6所示。

 

图4.6简单的按钮及内容

         但其内容可以是任意XAML内容:

         <StackPanel>

                   <Button>

                            <Button.Content>

                                     <StackPanel>

                                               <Image Source="headshot.png"

                                                                 Width="100"/>

                                               <TextBlock>Hello</TextBlock>

                                     </StackPanel>

                            </Button.Content>

                   </Button>

</StackPanel>

         这样做的结果XAML内容将包含在一个按钮中,如图4.7所示。

 

图4.7包含XMAL的按钮

         注意,内容是在按钮里面的,而不是取代组成该按钮的XAML。设定Content属性允许你指定在按钮里面是什么(或者在大多数内容控件内部,控件里面包含什么)。一个内容控件是任何继承自ContentControl类的控件。这些控件包括Button,CheckBox,RadioButton和HyperlinkButton。

列表控件

         列表控件支持显示任意的列表项。他们通过一个叫做ItemsSource的属性来完成。这个属性接收任何实现了IEnumerable 或 IList接口的集合。这意味着列表控件支持任何类型的集合(从简单到复杂的泛型集合数组)。在XAML视图中列表框的定义是相当标准:

         <StackPanel>

                   <ListBox x:Name="theList" />

         </StackPanel>

         真正的诀窍在于当你给ItemsSource属性设置一些集合:

         public partial class MainPage : PhoneApplicationPage

         {

                   // Constructor

                   public MainPage()

                   {

                            InitializeComponent();

                            theList.ItemsSource = new string[] { "One", "Two", "Three" };

                   }

         }

         设置ItemsSource将显示集合,并允许选择单个条目,如图4.8所示。

 

图4.8List box

         其他列表控件将会遵循这个相同的接口来设置集合(设置的集合到ItemsSource)。通过使用这些简单的控件集合,你应该能够为你的用户创建良好的体验。

手机特有控件

         到目前为止你看过的所有控件都存在于Silverlight的先前版本。但是一些控件是专门为了在手机上使用设计的。两个最明显的控件是Pivot和Panorama控件,他们允许你以有凝聚力的方式创建多面板控件。首先我们将讨论全景控件。

Panorama控件

         全景控件创建了一个由若干面板组成的虚拟画布,用户可以滚动到她想要的地方。Panorama控件允许你的建立这些虚拟画布在一个或多个面板外。你可以在图4.9中看到一个全景图应用程序的示例。

         Panorama控件需要你引用Microsoft.Phone.Controls 程序集。同样的你也必须在XAML文档中引用新的命名空间。如下所示:

         <phone:PhoneApplicationPage

                   x:Class="MyFirstPanorama.MainPage"

                   xmlns:ctrls="clr-namespace:Microsoft.Phone.Controls;

                                     assembly=Microsoft.Phone.Controls"

                   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

 

图4.9全景应用程式

         一旦你引用了新的命名空间你就可以使用Panorama和PanoramaItem元素来创建全景视图:

         <Grid x:Name="LayoutRoot"

                            Background="Transparent">

                   <ctrls:Panorama Title="my panorama">

                            <ctrls:PanoramaItem Header="first">

                                     <Grid>

                                               <ListBox />

                                     </Grid>

                            </ctrls:PanoramaItem>

                            <ctrls:PanoramaItem Header="second">

                                     <Grid>

                                               <ListBox Width="500" />

                                     </Grid>

                            </ctrls:PanoramaItem>

                   </ctrls:Panorama>

         </Grid>

         全景元素支持标题属性,它用于穿过整个全景控件来显示的文本。在全景控件内你可以使用一个或更多的PanoramaItem元素。每个PanoramaItem元素代表全景的一半。PivotItem的标题属性控制在每个PanoramaItem节上面显示的内容。在图4.10上你可以看到全景图。

         Panorama元素的标题属性在图4.10上以#1标签显示。你可以在所有的窗口中看到标题显示,所以它从来没有全部显示过。在设计时两个Panorama 项显示在单独的窗口中。标签#2区域显示了第一个PanoramaItem元素和标签# 3区域展示了第二PanoramaItem。注意,下一个全景项示意在屏幕的右边。

 

图4.10全景视图解释

         全景通常有幻灯片背景图像,随着用户移动到下一个全景项。为了得到这种行为,你可以设置全景的背景元素使用一个ImageBrush。例如,你可以使用下面的代码,用一个在.xap文件中图像绘制背景:

         <ctrls:Panorama Title="my panorama">

                   <ctrls:Panorama.Background>

                            <ImageBrush ImageSource="/back.jpg"

                                                        Opacity=".2" />

                   </ctrls:Panorama.Background>

         ...

         虽然全景项(例如PanoramaItem元素)意味着占据单个手机屏幕大部分,但其他部分可以大于一个屏幕。在图4.11中你可以看到标签# 1部分大于一个屏幕,而标签# 2部分是为单个页面大小。这与手机的设计范式是一致的。

 

图4.11Landscape节

         为了使用landscape节,你必须做出一些改动。默认情况下,一个全景节将几乎占据单页。为了获得更大的窗口你们必须设置PanoramaItem的内容为必要的大小,如有必要(使用宽度属性)以及设置PanoramaItem的方向为landscape,如接下来的代码中第2个Panorama Item所示:

         <ctrls:Panorama Title="my panorama">

                   <ctrls:Panorama.Background>

                            <ImageBrush ImageSource="/back.jpg"

                                                        Opacity=".2" />

                   </ctrls:Panorama.Background>

                   <ctrls:PanoramaItem Header="first">

                            <Grid>

                                     <ListBox />

                            </Grid>

                   </ctrls:PanoramaItem>

                   <ctrls:PanoramaItem Header="second"

                                                        Orientation="Horizontal">

                            <Grid Width="750">

                                     <ListBox />

                            </Grid>

                   </ctrls:PanoramaItem>

                   <ctrls:PanoramaItem Header="third">

                            <Grid>

                                     <ListBox Width="750" />

                            </Grid>

                   </ctrls:PanoramaItem>

         </ctrls:Panorama>

         设计指南规定,你在全景控件中的节应该有不超过四个或五个;如果你正在使用landscape节,那么整个全景控件中的节应该更少。一般的原则是整个全景控件少于2000像素宽,规模越较小,它就越容易被用户理解意图。

Pivot控件

         除了Panorama控件之外,另外还有一个叫做Pivot的手机特有控件。Pivot控件也是用来显示多个节,但是Pivot控件处理比Panorama控件更多的大量条目。主要原因是Pivot的节占据整个页面而没有重叠的节。例如,手机搜索页面使用了一个Pivot控件,如图4.12所示。

 

图4.12 Pivot控件

         在Pivot控件中,在页面的顶部显示了许多标签,显示了当前选择的节(在图4.12中#1)和当前没有选中的其他页面(#2)。当前选择的节的标签经常显示为白色,而其他章节显示为灰色以示区别。Pivot控件是始终在左上角显示选中章节的标题并将剩余的标签卷起显示在右侧(典型的在屏幕之外)。用户可以通过两种方式来切换章节,通过向左、向右滑动或点击节的顶部自动转到该选定章节。当章节更改后,内容区域(#3)也被更改来相应变化。

         Pivot控件和Panorama控件在相同的assembly和命名空间中(Microsoft.Phone.Controls)。所以需要引用的assembly和XML命名空间与之前的Panorama控件一致。在XAML中构建一个Pivot控件与Panorama控件相似,Pivot控件可以包含一个或多个PivotItem元素,像这样:

         <ctrls:Pivot>

                   <ctrls:PivotItem Header="first">

                            <ListBox />

                   </ctrls:PivotItem>

                   <ctrls:PivotItem Header="second">

                            <ListBox />

                   </ctrls:PivotItem>

                   <ctrls:PivotItem Header="third">

                            <ListBox />

                   </ctrls:PivotItem>

                   <ctrls:PivotItem Header="fourth">

                            <ListBox />

                   </ctrls:PivotItem>

         </ctrls:Pivot>

         像PanoramaItem元素一样,PivotItem使用Header属性来指定在Pivot控件顶部的标签。注意,Pivot控件本身并不支持Title属性(attribute)因为Pivot通常不包含一个在整个页面之上的标题。之前的XAML在页面上的结果显示在图4.13上。

         当用户点击顶部或滑动时,她可以前往到其他章节。与Panorama控件一样,章节是循环的,所以当用户位于最后一节时,第一节已经等待在最后一节的右侧了,如图4.14所示。

 

图4.13活动中的Pivot控件

 

图4.14在Pivot的章节循环

数据绑定

         编写手机应用程序或多或少的涉及到数据。Silverlight支持数据绑定,来帮助你以更有效的方式编写你的应用程序,但是准确的说什么是数据绑定那?数据绑定简而言之是将数据从一个源(例如一个对象的属性值)取出并显示在XAML元素中。如果XAML内容是一个控件,它也支持将变更内容推回数据源。当然这是非常基本的解释,这个解释是正确的。数据绑定非常简单,正因为它简单,所以它非常有效。

简单数据绑定

         在最基础的层面,一个绑定是一个连接器,从一个数据源获取数据并把它放到元素的属性中去。在图4.15中你可以看到一个数据绑定是在数据和元素(控件)中间的。

 

图4.15简单数据绑定

         绑定被定义为扩展标记,例如,以下是一个TextBox绑定Name属性:

         <TextBox Text="{Binding Name, Source={StaticResource myData}}" />

         绑定标记扩展首先采取路径到需要绑定的属性,然后是一些可选的元素。正如在这个例子中你所看到的,绑定是从称为myData源对象获取数据。当数据绑定发生,它采用数据绑定的源和使用属性路径(在这种情况下是Name)导航到数据,数据将被放到TextBox的文本中。通往属性的路径必须是公共的属性,因为它使用反射调用公共属性的getter方法,从数据源访问数据。

         指定绑定的来源的做法是相对罕见,不过,因为当源对象发生改变时你将不得不在一些地方改变它。例如,这段XAML将显示一个简单的数据编辑功能:

         <StackPanel>

                   <TextBlock>Name</TextBlock>

                   <TextBox Text="{Binding Name, Source={StaticResource myData}}" />

                   <TextBlock>Phone Number</TextBlock>

                   <TextBox Text="{Binding Phone, Source={StaticResource myData}}" />

                   <TextBlock>BirthDate</TextBlock>

                   <TextBox Text="{Binding BirthDay, Source={StaticResource myData}}"/>

         </StackPanel>

          想象一下如果源改变,所有的文本框的需要重新绑定。相反的,数据绑定使用一个属性叫做DataContext,简单的允许数据源沿着XAML的层次进行数据绑定。例如,如果DataContext被设定在StackPanel上,所有的控件尝试在StackPanel中数据绑定,从DataContext得到它们的数据,而不需要指定来源:

         <StackPanel DataContext="{StaticResource myData}">

                   <TextBlock>Name</TextBlock>

                   <TextBox Text="{Binding Name}" />

                   <TextBlock>Phone Number</TextBlock>

                   <TextBox Text="{Binding Phone}" />

                   <TextBlock>BirthDate</TextBlock>

                   <TextBox Text="{Binding BirthDay}" />

         </StackPanel>

         绑定发生主动拉动他们的数据时,并且当他们没有找到时他们将寻找数据源,他们会在层次结构中寻找第一个非空的数据源。在这种情况下,他们会在StackPanel级别发现并使用非空的数据源进行数据绑定。寻找DataContext将会持续下去,直到找到一个有效的DataContext为止。这种搜索XAML树行为并不局限于当前的XAML文档。如果数据绑定发生在一个控件内部,并且这个控件使用在另一个XAML文件中,它将通过所有的父子关系继续搜索,直到它耗尽整个对象树。

         数据绑定支持三种模式,如表4.3中描述。

类型

描述

例子

一次性

从数据源获取一次性数据

<TextBox Text="{Binding Name,

                                    Mode=OneTime}" />

单向

(默认)从数据源获取数据,当数据发送变化时,能够将变更从数据源拉到控件。

<TextBox Text="{Binding Name}"   />

双向

从源拉数据并将随着数据的变化将数据推回数据源(经常发生在控件失去焦点)。

<TextBox Text="{Binding Name,

                                    Mode=TwoWay}" />

         绑定变化的推动和拉取是通过反射进行的,所以所有的绑定模式都可以和任何的.NET类一起工作。没有要求这类来处理数据绑定。一个例外是,如果你要改变源对象本身并通过数据绑定反映到控件中。要做到这一点,你的源类必须支持一个称为INotifyPropertyChanged的简单接口。当源的数据发生变化,它通知绑定数据已经改变,这将导致绑定重新读取数据并改变控件中的数据,如图4.16所示。

 

图4.16 数据源里的变化

使用数据模板

         正如你在在这一章前面看到的,列表控件可以显示任何支持IList或IEnumerable接口的列表,但这只是故事的一部分。列表控件还支持使用DataTemplates来定制列表中单个条目的外观。你可以使用一个DataTemplate来指定ItemTemplate,DataTemplate可以使用任意的XAML内容来定义包含在一个ListBox中的内容。例如:

         <ListBox ItemsSource="{StaticResource theData}">

                   <ListBox.ItemTemplate>

                            <DataTemplate>

                                     <StackPanel>

                                               <TextBlock Text="{Binding Name}" />

                                               <Image Source="{Binding ImageUrl}" />

                                     </StackPanel>

                            </DataTemplate>

                   </ListBox.ItemTemplate>

         </ListBox>

         像这个列表在创建每个条目时,他将使用DataTemplate作为工厂在它内部来创建XAML。对于创建的XAML,DataContext成为了单独的条目被显示在ListBox内部,因此在DataTemplate内部的数据绑定继续工作。

改善滚动性能

         当绑定集合到手机上时,你必须意识到大量的数据是如何影响列表控件滚动的性能(如ListBox,ScrollViewer,Items-Source等)。有两个小的调整可以帮助你改善性能:图像创建和滚动处理。

         对于图像创建,你可以决定如何真正加载图像并解码。在图像的来源背后是一个叫做BitmapImage的构造对象。BitmapImage具有一个称为CreationOptions的属性,你可以在加载图像时指定它。默认情况下,CreationOptions指定图片应该是延迟加载的。这意味着图像直到他们可以看到才加载到页面上。除了延迟加载的图像,你还可以指定一个图像被后台线程加载。完成这两件事情可以总体改善一个集合中的图像性能。指定这些你会需要打开Image.Source属性,在控件模板中设置一个BitmapImage,像这样:

<ListBox ItemsSource="{StaticResource theData}">

         <ListBox.ItemTemplate>

                   <DataTemplate>

                            <StackPanel>

                                     <TextBlock Text="{Binding Name}" />

                                     <Image>

                                               <Image.Source>

                                                        <BitmapImage UriSource="{Binding ImageUrl}"

                                                                 CreateOptions="DelayCreation,BackgroundCreation" />

                                               </Image.Source>

                                     </Image>

                            </StackPanel>

                   </DataTemplate>

         </ListBox.ItemTemplate>

</ListBox>       

         你可以在这个例子中看到,图像是使用冗长的XAML语法来设置BitmapImage对象的Source。Uri源与前面的示例中使用的源采用了相同的绑定。最后,CreateOptions被设置到DelayCreation和BackgroundCreation两处来提高在用户界面中载入图像的性能。

         此外,你可以提高滚动的性能,通过允许对象负责滚动列表(ScrollViewer类)来决定是由操作系统(默认)还是由控件来处理滚动。根据滚动区域的大小,让控件负责滚动,可以提高用户的体验。为了改变这种情况, 你可以为任何列表控件(如ListBox,ItemsControl,ScrollViewer等)的ScrollViewer.ManipulationMode附加属性指定控件值,如下所示:

<ListBox ItemsSource="{StaticResource theData}"

                   ScrollViewer.ManipulationMode="Control">

<ListBox.ItemTemplate>

<DataTemplate>

<StackPanel>

<TextBlock Text="{Binding Name}" />

<Image>

     <Image.Source>

              <BitmapImage UriSource="{Binding ImageUrl}"

                       CreateOptions="DelayCreation,BackgroundCreation" />

     </Image.Source>

</Image>

</StackPanel>

</DataTemplate>

</ListBox.ItemTemplate>

绑定格式化

         在整个数据绑定过程中,你有机会在绑定中直接格式化数据。几个绑定属性允许你指定在绑定期间会发生什么。这些属性包括StringFormat,FallbackValue,TargetNullValue。这些属性可以影响用户在数据绑定过程中看到什么。例如, 在绑定中你可以包含这些属性作为额外的属性,如下所示:

         <TextBox Text="{Binding ReleaseDate,

                                                        StringFormat=d,

                                                        FallbackValue='n/a',

                                                        TargetNullValue='n/a'}" />

         StringFormat属性用于指定一个.NET中的格式化字符串在绑定的过程中使用。StringFormat可以是任何.NET格式字符串相匹配的类型。如果.NET格式字符串包含空格,你应该用单引号包围它。StringFormat既可以用于格式化数据时将其推送给控件,也可以解析数据返回到源。

         FallbackValue用于绑定失败的时候显示一个值。如果未找到一个源(例如,源或数据上下文为空)或当源中没有一个有效的属性用于绑定,绑定可能会失败。

         最后,TargetNullValue用来表明绑定成功了,但是绑定结果值是null。

元素绑定

         你还可以一种创建绑定,允许你创建在两个XAML元素之间的一个链接。这种绑定被称为元素绑定。使用元素绑定,你可以指定ElementName作为绑定语法,就像这样:

         <Slider Minimum="10"

                     Maximum="36"

                     x:Name="fontSizeSlider" />

         <TextBox FontSize="{Binding Value, ElementName=fontSizeSlider}"

                            Text="Make It Grow" />

         文本框中字体的大小被设置为基于滑块(名为fontSizeSlider)控件的值。通过这种方法,你可以使用XAML中的元素为其他XAML元素提供数据。一个更常见的使用元素绑定的场景是基于所选择控件的值到设置容器的数据上下文,就像这样:

         <ListBox ItemsSource="{Binding Games}"

                            x:Name="theList" />

         <StackPanel DataContext="{Binding SelectedItem, ElementName=theList}">

                   <TextBlock>Name</TextBlock>

                   <TextBox Text="{Binding Name}" />

                   <TextBlock>Phone Number</TextBlock>

                   <TextBox Text="{Binding Phone}" />

         </StackPanel>

         在这个例子中, StackPanel设置其DataContext为列表框中选择的任意项目。通过这种方式,你可以在StackPanel中根据列表框选择的条目显示和/或编辑数据。

转换器

         数据绑定使用对象的属性并移动他们到控件的属性中。有时,属性的类型可能不匹配或需要某种级别的处理才能工作。这就是转换器的由来。转换器是无状态的类,它可以在绑定过程中执行特定的转换。为了成为一个转换器,一个类必须实现IValueConverter接口。这个接口有两个方法, Convert和ConvertBack,以允许在绑定期间在两个方向进行转换。例如,一个简单的转换器使日期显示为短日期字符串看起来像这样:

         public class DateConverter : IValueConverter

    {

        public object Convert(object value,

        Type targetType,

        object parameter,

        CultureInfo culture)

        {

            if (targetType == typeof(string) &&

            value.GetType() == typeof(DateTime))

            {

                return ((DateTime)value).ToShortDateString();

            }

            // No Conversion

            return value;

        }

        public object ConvertBack(object value,

        Type targetType,

        object parameter,

        CultureInfo culture)

        {

            if (targetType == typeof(DateTime) &&

            value.GetType() == typeof(string))

            {

                DateTime newDate;

                if (DateTime.TryParse((string)value, out newDate))

                {

                    return newDate;

                }

            }

            // No Conversion

            return value;

        }

    }

         转换器都是作为资源创建(通常是在应用程序级别上)就像这样:

         <Application x:Class="PhoneControls.App"

                                     xmlns="..."

                                     xmlns:x="..."

                                     xmlns:phone="..."

                                     xmlns:shell="..."

                                     xmlns:my="clr-namespace:PhoneControls">

                   <Application.Resources>

                            <my:DateConverter x:Key="dateConverter" />

                   </Application.Resources>

                   ...

         </Application>

         通过在应用程序级别创建转换器,你可以在整个应用程序中使用它。最后,现在我们可以直接在我们的数据绑定中使用转换器,就像这样:

<StackPanel DataContext="{Binding SelectedItem, ElementName=theList}">

         <TextBlock>Name</TextBlock>

         <TextBox Text="{Binding Name, Mode=TwoWay}" />

         <TextBlock>Phone Number</TextBlock>

         <TextBox Text="{Binding PhoneNumber, Mode=TwoWay}" />

         <TextBlock>Phone Number</TextBlock>

         <TextBox Text="{Binding ReleaseDate,

                                               Mode=TwoWay,

                                               Converter={StaticResource dateConverter}}"

/>

</StackPanel>

         底层的数据转换(在这种情况下一个DateTime)使用了DateConverter类。当数据从源移动到控件时, Convert方法被调用;当数据被返回给源时,ConvertBack方法被调用。在这个例子中,转换器通常使用只是为了格式化并不是真正的转换。

数据绑定错误 

         在设计时,数据绑定错误不会导致异常。这种行为是值得的,因为数据绑定的源经常可以输入一个有效和无效状态在应用程序的生命周期中。让我们看一下我们之前看到的例子,我们有一系列的控件绑定到一个列表框的选中项上。当列表框没有选中项时数据绑定失败。这种情况下抛出一个异常是错误的做法。所以,作为一名开发人员,你将需要一种方法来实际看到数据绑定失败的。幸运的是你可以在Visual Studio输出窗口非常明显的看到它们。当运行你的应用程序,你可以使用视图菜单来显示输出窗口,如图4.17所示。

 

图4.17输出窗口

         当数据绑定失败, Visual Studio添加了一条调试信息输出到窗口。例如,如果你在绑定中使用了错误的路径(例如Title而不是Name)你可以在输出窗口看到这些,如图4.18所示。

 

图4.18绑定错误显示在输出窗口

         显示在输出窗口中的不仅仅只是错误的方式,错误的转换也显示在其中(真的,任何异常)。例如,如果用户试图进入一个错误的日期(例如,2010年2月31日)到日期字段,在数据绑定期间,输出窗口也会显示该错误,(如如图4.19所示)。

 

图4.19在输出窗口中的转换错误

控件模板

         尽管基于属性的样式非常强大,但是它可能不会让你以动态的方式改变控件的外观。这就是控件模板的由来。Silverlight中每个控件都是由XAML来定义如何绘制一个控件。例如, 谦卑的按钮使用这段XAML来绘制自身:

         <Grid Background="Transparent">

                   ...

                   <Border x:Name="ButtonBackground"

                                     BorderBrush="{TemplateBinding BorderBrush}"

                                     BorderThickness="{TemplateBinding BorderThickness}"

                                     Background="{TemplateBinding Background}"

                                     CornerRadius="0"

                                     Margin="{StaticResource PhoneTouchTargetOverhang}">

                   <ContentControl x:Name="ContentContainer"

                                               ContentTemplate="{TemplateBinding

                                                                                                                         ContentTemplate}"

                                               Content="{TemplateBinding Content}"

                                               Foreground="{TemplateBinding Foreground}"

                                               HorizontalAlignment="{TemplateBinding

                                                                                             HorizontalContentAlignment}"

                                               Padding="{TemplateBinding Padding}"

                                               VerticalAlignment="{TemplateBinding

                                                                                             VerticalContentAlignment}"

                   />

                   </Border>

         </Grid>

         即使你觉得控件是原子元素,但在控件内部还有XAML来定义控件的外观和感觉。控件模板被用来为任何控件重新定义这些XAML。

         控件模板是应用到一个控件部分的样式。ControlTemplate是任何控件的Template属性的值。例如:

         <Style x:Key="ButtonStyle1"

                            TargetType="Button">

                   <Setter Property="Template">

                            <Setter.Value>

                                     <ControlTemplate TargetType="Button">

                                               <Grid Background="Transparent">

                                                        ...

                                               </Grid>

                                     </ControlTemplate>

                            </Setter.Value>

                   </Setter>

         </Style>

         ControlTemplate的内容包括特殊控件使用的XAML(例如,这种情况下是Button)。你定义XAML,构成一个特定控件的外观,同时你可以使用一个扩展标记叫做模板绑定来将属性的值放入到你的XAML中。例如:

         <ControlTemplate TargetType="Button">

                   <Grid Background="Transparent">

                            <Border x:Name="ButtonBackground"

                                               BorderBrush="{TemplateBinding BorderBrush}"

                                               Background="{TemplateBinding Background}">

                            ...

                            </Border>

                   </Grid>

         </ControlTemplate>

         通过使用模板绑定,属性的原始值将被用于控件模板内部的值。这个值可能来自控件中默认值或来自一个样式的设置,或者它可能被专门设置到一个控件的实例上。模板绑定允许你使用任何能够在控件内部显示的属性值。

         对于一些控件,XAML必须包含特定的元素才能使控件继续工作。例如,WebBrowser控件需要XAML一部分必须是一个Border元素命名为PresentationContainer。这样,控件了解在哪里显示网页内容。你与控件作者之间的契约叫做template part(很多控件并不包含任何的template part)。需要模板部件的控件,需要在它自己的attributes中进行声明的(如图4.20所示)。

 

图4.20 TemplatePart属性

         在控件模板中的XMAL结构描绘了控件的外观,但是同时你也可以指定一个应用程序的感觉。一个应用程序的感觉是控件与用户交互的方式。例如,当被按下时按钮类改变其外观来响应用户,好像她真正的按下了按钮。这就是一个应用程序感觉是如何工作的。

         你可以使用称为视觉状态管理器的结构来创建一个应用程序的感觉。用视觉状态管理器可以定义动画来代表控件的状态。控件使用视觉管理器来进入特定的状态作为他们与用户的交互。作为应用程序设计人员,你可以定义这些状态来改变控件与用户交互的方式。这些状态可以被拆分到不同的组中因此一个单独的控件可以有多于一个的状态。例如,文本框有两个视觉状态管理器组,CommonStates(表示状态例如禁用和只读)和FocusStates(表示是否被选中或者不是)。视觉状态管理器组定义了一系列状态,但一次只能激活一个状态。例如,你可以让你的控件被禁用同时被选中,但是不能禁用同时只读。组中的状态是互相排斥的。

         为给视觉状态管理器定义组和状态,XAML可以包含一个VisualStateManager.VisualStateGroups属性(作为一个附加属性):

         <ControlTemplate TargetType="Button">

                   <Grid Background="Transparent">

                            <VisualStateManager.VisualStateGroups>

                                     <VisualStateGroup x:Name="CommonStates">

                                               <VisualState x:Name="Normal" />

                                               <VisualState x:Name="MouseOver" />

                                               <VisualState x:Name="Pressed">

                                                        <Storyboard>

                                                                 <ObjectAnimationUsingKeyFrames

                                                                           Storyboard.TargetProperty="Foreground"

                                                                           Storyboard.TargetName="ContentContainer">

                                                                 <DiscreteObjectKeyFrame KeyTime="0"

                                                                                                                         Value="{StaticResource

                                                                                                                         PhoneBackgroundBrush}" />

                                                                 </ObjectAnimationUsingKeyFrames>

                                                                 ...

                                                        </Storyboard>

                                               </VisualState>

                                               <VisualState x:Name="Disabled">

                                                        <Storyboard>

                                                                 <ObjectAnimationUsingKeyFrames

                                                                           Storyboard.TargetProperty="Foreground"

                                                                           Storyboard.TargetName="ContentContainer">

                                                                 <DiscreteObjectKeyFrame KeyTime="0"

                                                                                                                         Value="{StaticResource

                                                                                                                                   PhoneDisabledBrush}" />

                                                                  </ObjectAnimationUsingKeyFrames>

                                                                 ...

                                                        </Storyboard>

                                               </VisualState>

                                     </VisualStateGroup>

                            </VisualStateManager.VisualStateGroups>

                            ...

                   </Grid>

         </ControlTemplate>

         如这个例子所示,VisualStateManager.VisualStateGroups附加属性包含一个(或多个)VisualStateGroup对象。在组内是VisualState对象的列表,代表了一个故事板,展示了如何进入一个特定状态。为空的VisualState对象意味着状态应该看起来应该与对象原始状态一致。

         一个控件支持的视觉状态和组也需要指定为控件类的attributes,如图4.21所示。

         在创建自己的控件模板时,因为控件作者和你之间的契约,你需要注意模板部件和模板的视觉状态。你必须实现这些视觉状态和你认为控件能够继续正确执行的模板部件。

 

图4.21TemplateVisualState标签

Silverlight For Windows Phone工具包

         作为Windows Phone SDK 7.1中控件部分的补充,还有另一个名为Silverlight Toolkit可以下载。这个版本的手机工具包包括一组为手机定制的控件,来帮助你创建引人注目的应用程序。你应该安装Silverlight工具包添加这些控件(和其他特性你将在后面几章了解)到你的应用程序。你可以直接从CodePlex站点在http://silverlight.codeplex.com,下载Silverlight for Windows Phone工具包。你可以下载安装程序,或者如果你对这些控件是如何创建的感兴趣,你可以选择下载整个源代码。

         Silverlight for Windows Phone工具包包括了以下控件:

l  AutoCompleteBox

l  ContextMenu

l  DatePicker

l  TimePicker

l  ListPicker

l  LongListSelector

l  PerformanceProgressBar

l  ToggleSwitch

l  ExpanderView

l  PhoneTextBox

l  WrapPanel

         我们将在接下来的子章节中讨论这些控件:

AutoCompleteBox控件

         首先是AutoCompleteBox。这个控件的目的是允许你随着用户在控件中的输入给出建议。控件的风格看上去就像文本框,但是随着用户输入,控件可以显示一列可能的选项。例如,在图4.22中,当用户输入“S”控件显示了一个所有选项都是以字母S开始的弹出菜单。

 

图4.22 AutoCompleteBox列子

         这个控件是一个列表控件,所以你可以将一个任意的列表赋值给ItemsSource属性:

         theBox.ItemsSource = new string[]

{

"Shawn",

"Steve",

"Sally",

"Bob",

"Kevin"

};

         使用XAML属性可以有很多方式来定制这个控件,包括指定文本自动完成是否被启用,并且支持什么类型的过滤(StartsWith是默认值,但你也可以有基于包含或等于的建议):

         <toolkit:AutoCompleteBox x:Name="theBox"

                                                                 IsTextCompletionEnabled="True"

                                                                 FilterMode="Contains" />

         一种很常见的方法使用AutoCompleteBox是支持一列值,这些值在开发时是不可知的。这就在Web上Bing和谷歌搜索框是怎样工作的。你可以完成这些通过处理TextChanged事件,然后填充ItemsSource作为文本变化:

         // Constructor

         public MainPage()

         {

                   InitializeComponent();

                   theBox.TextChanged += new RoutedEventHandler(theBox_TextChanged);

         }

         void theBox_TextChanged(object sender, RoutedEventArgs e)

         {

                   // Go retrieve a list of items from a service

         }

ContextMenu控件

         ContextMenu控件的目的是允许用户在应用程序的某个部分“长按”来获取一组选项。默认情况下,控件本身显示的足够大,对用户来说很明显。你可以看到快捷菜单控件的动作有三个菜单项和一个分隔符,如图4.23。

 

图4.23 ContextMenu例子

         ContextMenu控件的结构是由一个ContextMenu元素组成的,这个元素在ContextMenu内使用一个或多个项的集合。这里只有一个级别的菜单项,所以没有子菜单支持。这两种类型的内容是菜单项元素和分隔符元素:

         <toolkit:ContextMenu>

                   <toolkit:MenuItem Header="Add" />

                   <toolkit:MenuItem Header="Remove" />

                   <toolkit:Separator />

                   <toolkit:MenuItem Header="Cancel" />

         </toolkit:ContextMenu>

         为了添加一个上下文菜单到XAML元素中,你可以使用上下文菜单的附加属性将它应用到你的设计中,就像这样:

         <Grid>

                   <toolkit:ContextMenuService.ContextMenu>

                            <toolkit:ContextMenu>

                                     <toolkit:MenuItem Header="Add" />

                                     <toolkit:MenuItem Header="Remove" />

                                     <toolkit:Separator />

                                     <toolkit:MenuItem Header="Cancel" />

                            </toolkit:ContextMenu>

                   </toolkit:ContextMenuService.ContextMenu>

                   ...

         </Grid>

         一旦菜单附加到元素上,用户按住(touch-hold)将引发要显示的菜单。每个MenuItem元素都可以启动代码,可以通过一个事件或通过一个命令绑定:

         ...

<toolkit:MenuItem Header="Add"

                                     Click="MenuItem_Click" />

<toolkit:MenuItem Header="Remove"

                                     Command="{Binding RemoveCommand}" />

...

注:命令绑定并没有在这本书中所提到,但命令绑定也是一种很有用的技巧来从代码中分离XAML。请参见Silverlight说明文档中ICommand接口来了解更多信息。

DatePicker和TimePicker控件

         如果你以前设计过桌面或Web应用程序,你可能习惯于用日历控件来允许用户选择日期。手机上的日历控件的问题是界面是不易于触摸的。相反,手机支持一个用于选择日期的控件:   DatePicker。使用DatePicker像使用XAML元素一样简单:

         ...

         <TextBlock>Pick Date</TextBlock>

         <toolkit:DatePicker />

         ...

         DatePicker看起来像一个简单的文本框,它接受一个日期。不同的是,当用户轻触控件时它启动了一个全屏的日期选择用户界面,如图4.24所示。

         日期选择用户界面允许用户按住移动和轻轻滑动来选择日期。这个界面与触摸界面工作的非常好。你将注意到在该界面底部显示了一个应用程序栏,但是图标是缺失的。为了使用这种控件,你需要手动添加图标到你的项目。图像通过使用相对路径获取图标,所以你的.xap文件必须包含这些图标。你首先需要在你的应用程序中创建一个目录,称为Toolkit.Content。

一旦你创建了这个目录,你需要从工具包目录的图标文件夹中添加图标。Icons文件夹存储在工具包目录中:

         %PROGFILES%\Microsoft SDKs\Windows Phone\v7.1\Toolkit\{Version}\Bin\Icons

         例如:

         %PROGFILES%\Microsoft SDKs\Windows Phone\v7.1\Toolkit\Aug11\Bin\Icons

在添加这些图标后,你应该确保这些文件被添加为“Content”,如图4.25所示。

         一旦这些文件成为项目的一部分,图标如预期的显示了。DatePicker控件默认值是DateTime.Now,这就是为什么显示了当前日期。你可以使用Value属性来指定一个日期:

         <toolkit:DatePicker Value="04/24/1969" />

        

 

 

图4.24 日期选择控件用户界面

 

图4.25 设置图标为“Content”

         TimePicker的工作方式与DatePicker的方式完全相同,但它使用DateTime结构的时间部分:

         ...

         <TextBlock>Pick Time</TextBlock>

         <toolkit:TimePicker Value="12:34 PM" />

         …

         选择时间的用户界面与DatePicker相识,但是他允许你指定时间来代替默认时间,如图4.26所示:

 

图4.26时间选择用户界面

ListPicker控件

         我知道开发人员喜欢的列表框。对于手机来说,有时ListBox是错误的工具。对于非常短的选项列表,列表框占据太多屏幕空间。作为替代,工具箱给了你ListPicker控件。

         当你有一个短列表,用户必须从中选择一个项目,ListPicker控件是一个很好的解决方案。事实上,ListPicker也可以用来取代单选按钮。ListPicker将当前选中的项显示在一个框里就像一个文本框,如图4.27所示。

 

图4.27 ListPicker例子(关闭状态)

         当用户触摸ListPicker的,它会以两种方式之一打开。如果列表很短(五项或更少),它展开控件来显示选项,如图4.28所示。

 

图4.28 ListPicker例子(展开状态)

         如果列表有多于5个选项,它会弹出一个全屏的列表选项可供选择,如图4.29所示。

 

图4.29 ListPicker例子(全屏状态)

         创建ListPicker,你就像创建一个简单的XAML元素一样来创建它,就像这样:

         <TextBlock Style="{StaticResource PhoneTextLargeStyle}"

         Text="Pick a Color" / >

         <toolkit:ListPicker x:Name="thePicker" />

         <Button Content="This is a Button" />

         因为ListPicker是个列表控件,你可以使用ItemsSource来指定列表:

         ...

thePicker.ItemsSource = new string[]

{

"Blue",

"Green",

"Red",

"Orange",

"Purple",

"Cyan",

"Brown",

"Gray",

"Light Green"

};

...

LongListSelector控件

         作为使用一个ListBox来显示一个长列表的替换方案,工具包还给你提供了一个控件,来允许用户查看大量的选项。当你从你的地址簿中选择一个电话号码时,手机使用了这个控件。因为人员列表可能会很长,他使用人们的姓氏或名字的首字母将人们进行分组。虽然它支持三种不同的模式,但真正的精华是能弹出一个组列表来帮助用户找到他们寻找的事物。尽管这种类型的控件常用作通讯录,该应用程序使用的第一个字母进行分组,但这完全取决于,你是如何决定LongListSelector中对象的组织形式。例如,图4.30显示了一个按风格分组的游戏列表。

 

图4.30 LongListSelector使用组

         这为用户显示了组并让她点击组弹出折叠组内容后的组来帮助她有效的导航大型列表,如图4.31所示。

         为了使用这种控件,你不得不依靠数据绑定来设置你的控件的三个元素。

l  ItemTemplate:这是每个LongListSelector选项的数据内容(例如,前面例子中的一个“Game”)

l  GroupHeaderTemplate:这是在被分组对象列表上方的项(例如,前一个例子中的“Genre”),显示在LongListSelector的主UI上

l  GroupItemTemplate:这是弹出时一个组的显示形式。

 

图4.31 LongListSelector的弹出组

         为组中的条目和组的头部使用同样的模板是非常普遍的。这个例子中使用XAML来创建LongListSelector:

 

         <Grid x:Name="LayoutRoot"

                            Background="Transparent">

                   <Grid.Resources>

                            <DataTemplate x:Key="letterTemplate">

                                     <Border Background="{StaticResource PhoneAccentBrush}"

                                                        Margin="4">

                                               <TextBlock Text="{Binding GroupName}"

                                                                 VerticalAlignment="Center"

                                                                 HorizontalAlignment="Center"

                                                                 Style="{StaticResource PhoneTextGroupHeaderStyle}" />

                                     </Border>

                            </DataTemplate>

                   </Grid.Resources>

                   <toolkit:LongListSelector x:Name="theSelector"

                                               GroupHeaderTemplate="{StaticResource letterTemplate}"

                                               GroupItemTemplate="{StaticResource letterTemplate}">

                            <toolkit:LongListSelector.ItemTemplate>

                                     <DataTemplate>

                                               <StackPanel Orientation="Horizontal"

                                                                           Background="Transparent">

                                                        <Image Height="75"

                                                                           Source="{Binding ImageUrl}" />

                                                        <TextBlock Text="{Binding Name}"

                                                                           Style="{StaticResource PhoneTextNormalStyle}"/>

                                               </StackPanel>

                                     </DataTemplate>

                            </toolkit:LongListSelector.ItemTemplate>

                   </toolkit:LongListSelector>

         </Grid>

         第一点值得注意的是在XAML的资源中存储了一个供组使用的DataTemplate。这样做的目的是我们可以给GroupHeaderTemplate和GroupItemTemplate使用相同的模板。接下来,ItemTemplate被指定为内联(inline)。它的工作方式很像本章前面的ListBox例子。分组才是真正改变这个控件原始的工作方式的原因。

         当你应用数据到这个控件时,你可以和其他列表控件一样指定ItemsSource。问题是要使控件工作,你的数据需要以一个非常特殊的格式。这个格式是一个组的集合。一个组就是一个集合,经常有一些来描述它(例如风格的名字,在我们的例子中)。Silverlight中已经包含了一些组,就像被称为IGrouping<T,T>的接口。你可以尝试仅使用LINQ的分组语法来完成这些,就像这样:

         // THIS DOES NOT WORK

         // Use LINQ to Group

         var games = new GameList();

         var qry = from g in games

                                     orderby g.Genre, g.Name

                                     group g by g.Genre into genres

                                     select genres;

         // Bind the collection of Groups into the control

         var result = qry.ToList();

         theSelector.ItemsSource = result;

         LINQ查询将游戏按照风格和游戏的名字进行排序,然后使用游戏风格进行分组将结果放入集合中。这看起来非常像我们需要对控件做的事情。不幸的是,处理分组底层类不支持数据绑定因为分组的名称不是公有的。要解决这种情况下,你可以使用一个简单的包装器用于分组,就像这样:

         public class Group<T> : List<T>

         {

                   public Group(IGrouping<string, T> group)

                   {

                            GroupName = group.Key;

                            this.AddRange(group);

                   }

                   public string GroupName { get; set; }

         }

         注:正如在数据绑定部分介绍的那样,数据绑定使用反射并且Silverlight不支持非公有反射。

         这个类只添加了一个公共属性,组的名称,并构造它形成LINQ使用的IGrouping < T,T >对 (尽管它假设一个基于字符串的键)。记住,这么做不仅使组拥有一个GroupName属性能够让用户识别组,这个类也是(is)底层对象需要的集合。这么做是因为这个控件需要数据格式化,这也是为什么我们需要这个类。这样,你可以修改LINQ查询来构建这些而不是返回原始IGrouping < T,T > 接口:

         // Use LINQ to Group

         var qry = from g in games

                            orderby g.Genre, g.Name

                            group g by g.Genre into genres

                            select new Group<Game>(genres);

         这很有效,因为我们使用我们指定的Group<T>类的GroupName来分组数据模板,来完成数据绑定:

         <DataTemplate x:Key="letterTemplate">

                   <Border Background="{StaticResource PhoneAccentBrush}"

                                     Margin="4">

                            <TextBlock Text="{Binding GroupName}"

                                               VerticalAlignment="Center"

                                               HorizontalAlignment="Center"

                                               Style="{StaticResource PhoneTextGroupHeaderStyle}" />

                   </Border>

         </DataTemplate>

         LongListSelector支持其他的模板和属性,来控制你展示这个控件这给用户的方式,但是理解如何使一个简单版本的控件工作的基本原理,将有助于你开始使用这个控件。

PerformanceProgressBar控件

         内置的ProgressBar控件有一些已知的性能问题,其中一个事实是,它呈现在错误的线程(使它显得神经质),和事实即使在控件停止时动画还在继续。这意味着如果你想在应用程序中用一个进度条,你应该使用在工具包中的PerformanceProgressBar控件来代替。这种控件之所以存在,是因为toolkit有更短的发布周期而且推送一种新进度条到所有手机将需要一次操作系统更新。发布这个工具包中意味着用户可以获得性能提升而无需等待下一个手机更新。

         你可以简单地使用这种控件作为你的进度条,而不是内置的那个,将ProgressBar放置在任何你期望的位置上,就像这样:

         <ProgressBar IsIndeterminate="{BindingIsBusy}" />

         使用工具包的版本替代这个,就像这样:

         <toolkit:PerformanceProgressBar IsIndeterminate="{BindingIsBusy}" />

         注:这个控件仍然存在于Silverlight for Windows Phone Toolkit中,但内置的控件也进行了同样的改变,因此让这个控件已经不必要了,除了向后兼容Windows Phone 7.0应用程序。

ToggleSwitch控件

         虽然手机包含一个复选框控件,点击一个框来启用一些事物未必是好的触摸式隐喻。取而代之的是一个工具包控件称为ToggleSwitch,如图4.32所示。

         ToggleSwitch允许用户要么按住滑动来改变它的值或真实的滑到它到右边来启用选项或到左边来禁用它。ToggleSwitch由三个部分,如图4.33所示。

 

图4.32 ToggleSwitch例子

 

图4.33ToggleSwitch组件

         标题属性控件标签# 1部分显示的内容如图4.33所示。内容属性控件标签# 2部分显示的内容。开关本身的部分显示在#3标签部分。控件的内容通常会随着控件状态的改变而改变 (例如,默认是On表示选中和Off表示未选中)。       你可以在以下XAML中看到一个ToggleSwitch的创建过程:

         <toolkit:ToggleSwitch Header="This one is enabled"

                                               Content="On"

                                               IsChecked="true"/>

         <toolkit:ToggleSwitch Header="This one is disabled"

                                               Content="On" />

ExpanderView控件

         在手机上显示的规模有限,意味着你可能想在屏幕上尽可能节约空间。ExpanderView控件将有助于节省空间。这个控件允许你设置内容是隐藏的,只有当用户单击标题时才显示隐藏的内容。控件包括一个标题和内容,内容可以在控件被点击时才显示(如图4.34)。

 

图4.34 活动中的ExpanderView

         使用这个控件,你需要一个ExpanderView的实例在你的XAML中。对于这个控件有两部分:标题和项目。标题是控件总是显示的部分,和包括内容的项目,一旦用户点击标题才显示。例如:

         <toolkit:ExpanderView Header="Click here to Expand">

                   <TextBlock>This is hidden by default</TextBlock>

         </toolkit:ExpanderView>

         标题属性可以是文本(如图所示),或者也可能是一个更复杂的控件使用扩展XAML语法:

         <toolkit:ExpanderView x:Name="theExpander">

                   <toolkit:ExpanderView.Header>

                            <TextBlock FontWeight="Bold"

                                                        Margin="4">Click here to expand</TextBlock>

                   </toolkit:ExpanderView.Header>

         </toolkit:ExpanderView>

         对这些内容,你可以只包括一列控件来显示简单的内容或者你可以用ItemsSource指定集合(很像一个列表框的方式工作方式):

 

         public partial class MainPage : PhoneApplicationPage

         {

                   // Constructor

                   public MainPage()

                   {

                            InitializeComponent();

                            expander.ItemsSource = new string[]

                                     { "Blue", "Red", "Green", "Orange" };

                   }

         }

         像ItemsSource属性一样,你还可以使用数据模板(像ItemTemplate)控制在ExpanderView中每个项目的外观。你应该认为ExpanderView类似于其他列表控件(本章早些时候描述的)。

PhoneTextBox控件

         内置的文本框是非常有用的,但是将它用于更实用的一些场景时,它缺少一些功能。如果文本框支持提示(例如,在它包含文本之前,显示的文本水印在一个文本框上),长度指示,按回车键以创建多个行文本和通过图标在文本框上执行行为,文本框会更好。PhoneTextBox填补了这些需求。图4.35显示了PhoneTextBox当它没有焦点时。注意水印(“Enter Tweet”)和操作图标在右边,你可以连接事件。

         指定这些,你可以简单的实例化控件并指定Hint属性和ActionIcon属性为必要的:

         <toolkit:PhoneTextBox x:Name="tweetText"

                   ActionIcon="Toolkit.Content/ApplicationBar.Cancel.png"

                   Hint="Enter Tweet" />

 

图4.35显示Hint和ActionIcon的PhoneTextBox

         PhoneTextBox包括一个事件当ActionIcon被点击时使用。这个事件被称为ActionIconTapped:

         public partial class MainPage : PhoneApplicationPage

         {

                   // Constructor

                   public MainPage()

                   {

                            InitializeComponent();

                            tweetText.ActionIconTapped += new

                                     EventHandler(tweetText_ActionIconTapped);

                   }

                   private void tweetText_ActionIconTapped(object sender, EventArgs e)

                   {

                            tweetText.Text = "";

                   }

         }

         这个例子展示了当用户点击取消图标时清空了PhoneTextBox。这是一种常见的对标准文本框的可用性改进。

         PhoneTextBox还支持向用户展示她输入了多少字符的能力。随着用户在控件中输入,它可以显示字符的数量(以及允许的最大字符数),如图4.36所示。

 

图4.36 PhoneTextBox支持长度提示

         长度指标是通过设置LengthIndicatorVisible属性为真来支持的。LengthIndicatorThreshold属性是用来表明为了长度指标显示之前必须输入多少个字符。这允许你直到用户正在接近的最大字符数才显示长度指标。最后,DisplayedMaxLength属性是用来表示的最大数字的指标。注意,这并不限制字段的长度,但是只是简单显示在指标里的最大值(这就是为什么它被称作DisplayedMaxLength)。你可以在如下XAML中看到这些指定:

         <toolkit:PhoneTextBox x:Name="tweetText"

                                                                 DisplayedMaxLength="140"

                                                                 LengthIndicatorVisible="True"

                                                                 LengthIndicatorTheshold="20" />

         最后,你也可以指出控件应该支持按下回车键来扩展文本框来包含多行文本。当用户使用这个功能,在键盘上按Enter(或SIP)将扩大PhoneTextBox来包括多个行 (如图4.37)。

 

图4.37 PhoneTextBox的AcceptReturn功能

         你通过使用AcceptsReturn属性指定这种情况,如下所示:

         <toolkit:PhoneTextBox x:Name="tweetText"

                                                                 AcceptsReturn="True" />

WrapPanel 布局容器

         最后包含在工具包中的XAML元素包是一个新的布局容器称为WrapPanel。这个元素不是一个控件而是布局容器(如,Grid,StackPanel)。WrapPanel的目的是指定元素从左到右,当他们不适合横向扩展时连接到一个新行。例如,如果你在一个StackPanel放置九个按钮就像这样:

<StackPanel>

<Button Content="1" />

<Button Content="2" />

<Button Content="3" />

<Button Content="4" />

<Button Content="5" />

<Button Content="6" />

<Button Content="7" />

<Button Content="8" />

<Button Content="9" />

</StackPanel>

         StackPanel将简单的将它们垂直地堆积,如图4.38所示。

 

图4.38在StackPanel的按钮

         但是如果你改变XAML用WrapPanel取代StackPanel,就像这样:

<toolkit:WrapPanel>

<Button Content="1" />

<Button Content="2" />

<Button Content="3" />

<Button Content="4" />

<Button Content="5" />

<Button Content="6" />

<Button Content="7" />

<Button Content="8" />

<Button Content="9" />

</toolkit:WrapPanel>

         WrapPanel将水平排列项目,当内容已不再适合时,则包连接到“新一行”,如图4.39所示。

 

图4.39在WrapPanel中的按钮

         你也可以改变Orientation属性为Vertical使控件垂直排列:

<toolkit:WrapPanel Orientation="Vertical">

<Button Content="1" />

<Button Content="2" />

<Button Content="3" />

<Button Content="4" />

<Button Content="5" />

<Button Content="6" />

<Button Content="7" />

<Button Content="8" />

<Button Content="9" />

</toolkit:WrapPanel>

         这些内容显示在图4.40中。

 

4.40在垂直的 WrapPanel中的按钮

我们在哪里?

         Windows Phone控件集是非常广泛的。结合包括SDK控件,微软作为Silverlight for Windows Phone工具包发布的一部分控件,将会给你一个引人注目的方式来构建你的应用程序。这里你要记住最重要的课程是,你是为手机构建应用程序。触摸是被放到第一位的。这意味着你不得不放弃你关于在什么地方使用什么控件的旧观念,并尝试了解在触摸和不动产(real estate)之间的差异。大多数开发人员严重依赖的一个控件,某种形式的data grid,是没有提供的。原因就是网格和手机无法很好地协同工作。混合列表、控件,和屏幕导航而不是依靠一块满是数据的大画布,将帮助用户更恰当地使用你的应用程序,并提高你创造一个伟大的、受欢迎的应用程序的机会!       

posted @ 2012-08-22 15:24  newetmscontact  阅读(562)  评论(0编辑  收藏  举报