3 XMAL概述
手机曾经是以非常实用为主导的(比如,我可以用它来拨打电话),但在过去几年里事情却发生了戏剧性的变化。尽管,你可以尝试创建应用程序专注于功能,但是他们可能不会吸引大量的用户,除非它们有非常出色的用户界面。这就是一个应用程序吸引人的地方。这意味着你的工作是要找出人们想要什么,和是什么使得应用程序易于使用和学习。幸运的是Silverlight自带了设计界面,还有很多控制应用程序外观和感觉的方法。创建让用户兴奋的应用程序会比以往更容易。本章将向你展示如何来实现这一切。
什么是XMAL
什么是可扩展的应用程序标记语言(或XAML)?在Silverlight中XAML是用来设计用户界面的(包括应用程序的外观和感觉),但它的确提供了更多的功能。在这里学到的主要概念是:XAML可以被认为是一个序列化格式,可以很好的被工具处理。它允许我们声明一个结构化的用户界面。以这种方式声明接口方便工具来创建用户界面和应用程序在运行时使用该文件。
我在这里说的序列化格式意味着什么哪?XAML非常简单,让我们来看一个非常基本的XAML:
<UserControl x:Class="WinningTheLottery.Sample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<TextBlock Text="Hello" />
<Rectangle Width="100"
Height="100"
Fill="Blue" />
</Grid>
</UserControl>
XAML是一个XML文件,它遵循XML基本的规则(例如,单一的顶层容器,区分大小写)。在这个文件中我们声明一个UserControl根元素并包含一个Grid控件,Grid元素中还包含了两个元素(一个TextBlock和一个Rectangle)。这是这个简单的用户界面的基本层次。在解析时,这个XAML文件被用来在内存中创建相同的层次结构。从字面上看,元素的名称关联自身到类的名称。所以当XAML被解析时, UserControl元素提示系统创建一个新的UserControl实例。所有用在这里的类都必须允许空构造函数(.NET要求),以便UserControl类可以被创建。在UserControl自身被创建后,它查看子元素(Grid)同时在UserControl内部创建一个子元素。最后,它将创建TextBlock和Rectangle并将它们作为Grid的子元素。当TextBlock被创建,它检查到了attribute(Text),并调用新TextBlock的property的设置方法,用attribute的内容进行赋值。它也使用同样的方式处理Rectangle的众多attributes。它通过这种方式遵循XAML中相同的层次结构,建造一个内存中的对象图谱。能够理解你正在使用的XAML是你运行时设计的基础,这对于了解XAML是如何工作的是非常重要的。
Silverlight 5开发者 对于那些使用Silverlight 5进行桌面程序开发的开发者来说,手机中的Silverlight 版本是基于Silverlight 4。这意味着你可能已经使用了它缺少的功能,包括隐式数据模板和相对绑定等等。 |
XMAL对象属性
你在XAML视图中设置的大多数对象的属性是简单的和基于字符串的:
<Rectangle Fill="Blue" />
然而,并非所有属性都可以设置成用简单的、基于字符串的语法。在后台,许多属性(在XAML解析时)正试图将一个字符串attribute转换成属性值(使用一种称为TypeConverter的特殊类型)。例如,Fill属性它接受的是一个笔刷值,而不是一个颜色。在Fill="Blue"作为XAML解析时,转换是在一个字符串(例如“Blue”)和一个叫做SolidColorBrush笔刷之间完成的。对于更复杂的值类型(如笔刷),有一个更详细的语法来设置属性值:
<Rectangle>
<Rectangle.Fill>
<SolidColorBrush Color="Blue" />
</Rectangle.Fill>
</Rectangle>
这个冗长的语法在运行时与先前的例子完全相同。通过在矩形内部添加一个元素,元素的名称就是对象的名称,一个点和属性的名称(例如,Rectangle.Fill),我们可以用XAML为属性定义值来代替仅使用字符串。因为并不是所有复杂的属性值都可以以这种方式来定义,一个转换可以被执行,这个冗长的语法允许设置属性值时是一个复杂类型,用一个单独的字符串来描述将很困难或者不可能。例如,让我们把SolidColorBrush替换为LinearGradientBrush:
<Rectangle>
<Rectangle.Fill>
<LinearGradientBrush>
<GradientStop Color="Blue" Offset="0" />
<GradientStop Color="White" Offset="0.5" />
<GradientStop Color="Blue" Offset="1" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
在这个例子中我们可以看到,定义一个Fill并指定了颜色和补偿,仅仅使用一个简单的字符串不但很难实现,而且会使XAML更加难以理解。通过这种方式,XAML允许你设置非常复杂的属性而无需实现转换。随着我们的深入,你将会在XMAL中许多不同的地方看到这是如何使用的。
理解XAML命名空间
在XAML文件根元素中是两个命名空间。在XAML中的命名空间就是XML的命名空间。默认命名空间(xmlns)声明:这是一个XAML文件。第二个命名空间(xmlns:x)引入的一些元素和属性类型都是以x别名为前缀的。所以当你看到x:Class,这是一个转换意味着这个类是定义在第二个命名空间里的。
你可以想象命名空间别名作用类似于在.NET中的命名空间。当你添加一个命名空间,它将带来一些新类型,它们可以在XAML视图中进行描述。不同于. NET,尽管,你必须使用一个别名(因为所有其他命名空间不是“默认”的命名空间),然后在任何你想要使用该命名空间中的信息时使用这个别名。在x别名的实例中,在这里的别名“x”只是一个约定,XAML倾向于使用它。在XML命名空间意义上,你可以将别名改变为任何你想要的名字,但你必须在它的每个引用位置改变它。例如:
<UserControl foo:Class="WinningTheLottery.Sample"
xmlns="..."
xmlns:foo="...">
别名仅仅是:一个代替,因此解析器可以确定你的元素或属性的命名空间是来自那里。当我们改变了代替的名称,它是在文档的其余部分你可以使用别名来代替命名空间。
虽然这些命名空间代表基本XAML命名空间,你可以通过使用命名空间扩展XAML带来任意.NET类型。如果你定义一个命名空间它指向了一个.NET命名空间和程序集,这些类型也可以在XAML中使用:
<UserControl x:Class="WinningTheLottery.Sample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Grid>
<TextBlock>
<TextBlock.Text>
<sys:String>Hello</sys:String>
</TextBlock.Text>
</TextBlock>
</Grid>
</UserControl>
在这个例子中,XAML“引入”在mscorlib.dll程序集里的系统命名空间。一旦.NET的命名空间被引入,命名空间中的所有的类型在XAML里都可以被创建。在XAML中创建任何类型必须符合下列规定:
l 有一个空的,公共的(public)构造函数
l 有公共(public)属性
任何遵守这些规则的.NET对象都可以在XAML中创建(并成为你初始对象图谱中的一部分)。在这一章随着我们继续深入,你将看到这是如何使用的。
XAML中的命名
不同于其他平台,XAML不需要每个在XAML文件中的对象都需要特殊的命名。事实上,在XAML文件中为每个对象命名可能是个坏主意。当你需要引用一个已命名对象时(例如从代码或通过数据绑定),在XAML中命名对象才变得重要。在XAML中命名对象是以一个attribute形式出现的,可以适用于大多数XAML元素:x:Name。例如:
<UserControl x:Class="WinningTheLottery.Sample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Name="LayoutRoot">
<TextBlock Text="Hello"
x:Name="theTextBlock" />
<Rectangle Width="100"
Height="100"
Fill="Blue"
x:Name="theRectangle" />
</Grid>
</UserControl>
你将会注意到命名属性以x:前缀(或别名)。在关于命名空间中的讨论中解释过的,这意味着,通过包含在每一个XAML文件顶部(默认情况下)的 x命名空间使得命名属性是可用的。一旦这些对象被命名,XAML中的其他元素,后台代码通过名字就可以访问到他们 (这两种情况你将在这一章中都能看到)。这里使用的名称必须是惟一的。每个名称在单一的XAML文件仅能出现一次。这简化了命名策略,但也意味着命名范围不再有意义(在HTML命名范围有意义)。
可视化容器
如果你思考的上面显示例子会发现,你可能忽略了XAML容器的重要性。其中最明显的可以在Grid元素中发现:
<UserControl x:Class="WinningTheLottery.Sample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<TextBlock Text="Hello" />
</Grid>
</UserControl>
这些容器的目的是允许其他元素以特定的方式放置在Silverlight可视化界面上。尽管这些容器本身通常没有任何用户界面,但是他们用来确定如何将不同的XAML元素排列在屏幕上。一些布局容器对于在XAML中进行设计是非常重要的。每个布局容器可以包含一个或多个子元素,并把它们以特色的形式进行布局。在表3.1中,你可以看到常见的视觉容器。
布局容器 | 描述 | 是否支持多子元素? |
Grid | 类似于表格的行列布局;容易进行对齐和调整间距的设计 | 是 |
StackPanel | 水平或垂直堆叠单独的元素 | 是 |
Canvas | 基于位置的布局(通过顶部和左侧的位置) | 是 |
ScrollViewer | 虚拟的容器,内容可以大于容器,允许用户通过容器进行滚动 | 否 |
Border | 在一个元素附近创建一个简单的边界 | 否 |
这些容器很重要,因为它们被用于确定如何布局元素。其中最重要的是Grid容器,它将你最常使用的容器。Grid是一个支持动态布局,类似于表格的行和列的布局容器。在定义行和列时,你设置了Grid的ColumnDefinitions和/或RowDefinitions属性。这些属性使用一个或多个ColumnDefinition或RowDefinition元素,如下所示的代码:
<UserControl x:Class="WinningTheLottery.Sample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="Hello" />
</Grid>
</UserControl>
你使用ColumnDefinitions和RowDefinitions属性创建新行和列(如上)。这允许你通过Grid.Column或Grid.Row附加属性单独的指定元素在一个特定的行或列(请参阅侧栏,什么是附加属性?):
<UserControl x:Class="WinningTheLottery.Sample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="Hello"
Grid.Column="1" /> <!-- The Second Column -->
</Grid>
</UserControl>
TextBlock通过使用附加属性,表明TextBlock属于第二列(注意,行和列的数字是以0为开始的)。通过这种方式,Grid通过在前面指定行或列的数量主动的创建列或行。乍一看通过这种方式来创建行和/或列定义似乎罗嗦,但是这是重要的,因为这种定义可以用来设置包含其他的重要信息。
什么是附加属性? 有些属性是不相关的,直到它们存在于一些特定的范围。例如,当一个对象在一个网格里的时候,能够告诉XAML它在那列或那行就变得很重要了。但是,同样的元素在StackPanel里行或列就没有概念了。附加属性是特定类型的属性,这些属性只是在特定的情况下有效。附加属性的定义是:拥有类型名称和附加属性的名称(例如,Grid.Row)。通过自己公开他们通常在那里会使用,附加属性的信息在类中才是可用的。尽管在XAML中附加属性通常是这样使用的,但是附加属性在一个全局范围内是可用的属性。所以你可以定义一个属性,可以应用于任何XAML对象。容器例如Grid和Canvas,通过公开附加属性来显式地让他们处理布局,对于大多数新的XAML开发人员来说,它们很可能是第一个真正使用的附加属性。 例如,在Grid类中,随着Grid对象在它内部进行布局,它通过查询附加属性来确定布局一个元素在那行和/或列。字面上的属性是在运行时附加上的,所以底层元素不需要具有没必要的属性(例如行和列)。 |
当创建行和列时,你可以使用三个方式(分别)定义高度或宽度,显示于表格3.2。
表3.2Grid行和列尺寸
类型 | 描述 | 例子 |
Auto | 内容基于行或列的大小。尺寸将由 在相应的行或列中最大的对象确定。 | <RowDefinition Height="Auto" /> |
Pixel | 设置行或列为一个特点的尺寸,以Pixel为单位,大对象将被截断。 | <RowDefinition Height="100" /> |
Star | 基于权值成比例来设置行或列的大小。 | <RowDefinition Height="*" /> <RowDefinition Height="25*" /> <RowDefinition Height="0.147*" /> |
Auto和Pixel形式的尺寸设置不言自明,但Star形式的尺寸设置需要一些解释。Star尺寸设置适当的设置行或列的大小基础在高度或宽度值。例如:
<UserControl x:Class="WinningTheLottery.Sample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="33*" />
<ColumnDefinition Width="66*" />
</Grid.ColumnDefinitions>
</Grid>
</UserControl>
宽度值作为整个大小的加权比例。虽然这看上去类似于百分比(像你可能在Web应用程序使用的),这些数字并不是任意一个100%的比例。例如,更改值为“1 *”和“2 *”产生相同的2-to-1比率,等同于“33 *”和“66 *”。如果使用单独一颗星(如。“*”),它等同于“1 *”。通过混合使用Grid元素的auto,pixel和star 尺寸设置形式,你可以创建富有弹性的布局(使用star尺寸来灵活的设置元素大小,使用pixel/auto sizing 方式来设计更多的静态部分)。
你已经看到了,你可以使用附加属性来设置一个特定元素在Gird内的行和/或列。Gird类也支持指定RowSpan和ColumnSpan属性,这意味一个特定的元素可以跨多行和/或列。这将给你更多的灵活性来创建你的表格布局设计,就像这样:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="1" />
<TextBlock Text="1"
Grid.Row="1" />
<TextBlock Text="1"
Grid.Column="1" />
<TextBlock Text="Across All 3 Columns"
Grid.ColumnSpan="3" />
<TextBlock Text="Across Both Rows"
Grid.RowSpan="2" />
</Grid>
尽管在某些情况下你可以使用其他布局容器,但你应该适应Grid容器,它会是你最常用的容器。
视觉语法
Silverlight让你能够在手机本身的界面上绘制形状和颜色。当然你可能不会过多想象真正的进行绘图,但重要的是你要理解如何利用基本的绘图元素来进行设计,这对绘制整个XAML故事来说是非常重要的。当你开始使用这些控件时,你会发现这些控件都是由更原始的元素组成,当你想改变控件和其他元素的外观时,你不得不了解绘图的堆栈机制。
形状
最基本的绘图元素是Shape。Shape元素是一些被用来进行基础绘图的形状的基类。下面列出了基本绘图形状:
l Line
l Rectangle
l Ellipse
l Polygon
l Polyline
l Path
每种形状有基本的属性,例如Height, Width, Fill和Stroke:
<Grid>
<Rectangle Width="100"
Height="100"
Fill="Blue" />
<Ellipse Width="200"
Height="50"
Stroke="Black" />
</Grid>
如果你不能通过上述的5种形状来构成你想要的图形,那你只能选择Path图形了。Path是一个强大的形状,它给了你设计任意形状的权力。Path形状可以让你创建打开,关闭,或复合形状。Path形状有一个叫做Data的属性,该属性用于指定元素的形状,例如,你可以指定一个包含对象曲线图的路径,就像这样:
<Path Stroke="Black">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="0,50">
<BezierSegment Point1="50,0"
Point2="50,100"
Point3="100,50"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
通过将Data属性赋值给PathGeometry元素,指定了一个包含BezierSegment的Path对象(从0,50开始到100,50,包含转折点50,0和50,100的曲线),你可以画一条曲线,如图3.1所示。
图3.1路径解释
Data属性包含一个简化符号来简化和缩短XAML的大小。简化符号是同样类型的信息,但是存储在一个单独的字符串。例如,在图3.1的曲线可以简化为:
<Path Stroke="Black" Data="M 0,50 C 50,0 50,100 100,50" />
这里包含了相同的信息,但是是一个缩写形式:移动到0,50位置和使用三个点做Bezier曲线。通常路径是通过工具创建的(例如Expression Blend),制作过程可以得到简化,但是这个过程确实允许绘制非常复杂的路径。
笔刷
到目前为止在许多实例中,你已经看到了如何在XAML中使用的颜色名称(例如,black, red),来表示一个对象是用什么颜色展示的。事实上,这些颜色是用于创建一个名为笔刷对象的快捷方式。笔刷通常用来绘制界面(例如,使用fill,stroke和background笔刷)。在表格3.3中一些类型的笔刷已经提供给你使用了。
类型 | 描述 | 例子 |
SolidColorBrush | 绘制一个纯色 | <Ellipse Fill="Blue" /> |
LinearGradientBrush | 绘制一个线性渐变 | <Ellipse> <Ellipse.Fill> <LinearGradientBrush> <GradientStop Color="Blue" Offset="0" /> <GradientStop Color="Red" Offset="1" /> </LinearGradientBrush> </Ellipse.Fill> </Ellipse> |
RadialGradientBrush | 绘制一个径向渐变,焦点定义渐变的开始,而圆定义渐变的终点 | <Ellipse> <Ellipse.Fill> <RadialGradientBrush> <GradientStop Color="Blue" Offset="0" /> <GradientStop Color="Red" Offset="1" /> </RadialGradientBrush> </Ellipse.Fill> </Ellipse> |
ImageBrush | 绘制一个图形 | <Ellipse> <Ellipse.Fill> <ImageBrush ImageSource="/foo.jpg" /> </Ellipse.Fill> </Ellipse> |
VideoBrush | 绘制一个媒体元素 | <Ellipse> <Ellipse.Fill> < VideoBrush SourceName="theVideo" /> </Ellipse.Fill> </Ellipse> |
XAML元素的每个属性,如果可以接收brush对象,就可以使用不同类型的笔刷。
颜色
XAML包含一组内置的颜色。你可以使用这141种命名颜色来指定独特的颜色,就像这样:
<Grid>
<Rectangle Fill="Blue"
Stroke="Pink" />
</Grid>
虽然,在大多数情况下,已命名的颜色最终不足以处理全部的基础颜色。由于数以百万计的颜色可供选择,XAML需要以更有效地的方式来指定一种颜色。XAML支持HTML中将十六进制字符串转换为RGB颜色的方式,像这样:
<Grid>
<Rectangle Fill="#0000FF"
Stroke="#FF0000" />
</Grid>
在这种格式中,英镑符号(#)后接着一组十六进制数字,代表红色、绿色、蓝色被使用数量。六位和三位数字的格式都支持 (例如# FF0000等同于# F00)。此外,XAML扩展了HTML语法以包括一个八位数的版本。在八位数的版本中,前两个字符代表一个十六进制数字,标明阿尔法通道(或不透明程度):
<Grid>
<Rectangle Fill="#800000FF"
Stroke="#C0FF0000" />
</Grid>
在这个例子中,Fill大约50%都是透明的,同时Stroke有大约75%是不透明的(或25%透明)。
文本
对于绘制基本的文本,TextBlock类是正确的工具。TextBlock是一个绘制文本的简单容器。它支持属性选择,例如基本字体大小、字体、粗细、前景颜色、对齐等等。
<Grid>
<TextBlock Text="Hello World"
Foreground="White"
FontFamily="Segoe WP"
FontSize="18"
FontWeight="Bold"
FontStyle="Italic"
TextWrapping="Wrap"
TextAlignment="Center" />
</Grid>
除了简单的文本,TextBlock类还支持简单的使用简单内嵌格式使用LineBreak和Run结构。
<Grid>
<TextBlock>
Hello World. <LineBreak />This
is the second line. The breaking of
the lines in the XAML are
<Run Foreground="Red">not significant</Run>.
</TextBlock>
</Grid>
一个LineBreak标明文本会换行,忽略TextWrapping属性。一个Run是用于包装一段文本,需要进行不同于TextBlock其他地区的格式化。Run支持TextBlock允许的基本属性,但是只将它们应用到Run元素中的文本上,如上所示。TextBlock不是一个处理任何类型的富文本或者处理HTML级别的文的控件,但在大多数情况下的文本操作已经足够了。
图像
虽然简单的矢量绘图堆栈对你的Windows Phone 7应用程序的设计来说是非常宝贵的,但你总是需要在应用中使用的图像的。
<Image Source="http://wildermuth.com/images/headshot.jpg" />
Image元素支持JPEG、PNG文件。它不支持GIF文件。通过指定Source属性,图像元素显示了你在Source属性中指定了URI的图片。如果指定一个互联网UR,Image元素将尝试从互联网位置下载图像。Source属性也支持相对URI:
<Image Source="headshot.jpg" />
通过使用一个相对URI,图像元素尝试从应用程序本身下载图片。你可以添加一个现有的图像到Silverlight项目,只需要从项目菜单选择“添加|现有项目”。一旦你有了一个图像作为项目的一部分,它将被打包到你的应用程序中。因此,你可以简单地使用相对URI指定源属性。相对的URI是相对于项目根目录的。所以,如果你把一个图像在一个项目文件夹,URI需要导航到该路径:
<Image Source="Images/headshot.jpg" />
作为应用程序的一部分存储你的图像对静态图像(例如按钮图标,背景等等)来说是典型的做法。
默认情况下,Image元素设置为伸缩图像大小来适应元素。你可以通过指定Stretch属性伸缩图像。有效的伸缩类型包括:
l None:没有伸缩执行。
l Uniform: 拉伸图像,保留了原来的长宽比,来适应Image元素的框架。这是默认的。
l UniformToFill:拉伸图像,保留了原来的长宽比,来填充Image元素。如果从该图像与Image元素的长宽比不同,图像将被裁剪,以适应差异。
l Fill:拉伸图像填充Image元素不保持长宽比。
图3.2展示了不同类型伸缩的例子。
图3.2图像伸缩
当创建Image元素时,你可以简单的指定Stretch属性,像这样:
<Image Source="Images/headshot.jpg" Stretch="UniformToFill" />
因为图像元素也是设计语法的一部分,你可以指定大小通过使用高度和宽度或使用容器属性像任何其他元素一样 (例如 Grid.Row/Column, Margin, VerticalAlignment等)。
转换和动画
现在你已经有了设计应用程序外观的基本构建模块,让我们谈谈创建应用程序的“感觉”。应用程序的感觉是它与用户进行交互的方式。交互的层次取决于应用程序的性质,但是对用户来说许多应用程序应该感觉还活动着。通常这是通过给用户微妙的反馈来完成的,包括改变UI外观来响应用户的行为,或使用诸如触觉(例如震动)反馈。这种反馈是重要的,以帮助用户知道他是在做某事。一个常见的例子是历史悠久的按钮对象。在一个典型的桌面操作系统,当你把鼠标移动到一个按钮,它改变它的外观来告诉你你在按钮上。当你点击它时,它会改变它的外观给你留下了这样的印象,它确实被按下了(像现实中的按钮)。
这种反馈确保你可以放心,按下按钮做的事情正是你所希望的。一些网站缺少这种反馈,这使用户感到迷惑(他们常常不知道缺少什么)。这就是转换和动画可以帮助你提升你的用户界面设计的地方。
转换
让我们从转换开始。转换的想法是简单的改变一个元素在屏幕上的绘制方式,让我看一个简单的矩形:
<Grid>
<Rectangle Width="100"
Height="100"
Fill="Red" />
</Grid>
正如你期望的,这个长方形,将会被绘制为一个简单的正方形。让我们看看当我们加入转换会发生什么(通过将转换赋值给Rectangle的RenderTransform属性):
<Grid>
<Rectangle Width="100"
Height="100"
Fill="Red">
<Rectangle.RenderTransform>
<RotateTransform Angle="30" />
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
通过使用一个RotateTransform,你可以确保对象可以保持为一个矩形,但在绘制时,转化被应用了(如图3.3)。
图3.3转换在进行
对单个元素使用转换很少是正确的选择;通常一个转换应用于整个容器来改变容器的外观:
<Canvas>
<Canvas.RenderTransform>
<RotateTransform Angle="30"
CenterX="150"
CenterY="150" />
</Canvas.RenderTransform>
<Ellipse Width="300"
Height="300"
Stroke="Black"
Fill="Yellow"
StrokeThickness="2" />
<Ellipse Fill="Black"
Width="50"
Height="50"
Canvas.Left="50"
Canvas.Top="75" />
<Ellipse Fill="Black"
Width="50"
Height="50"
Canvas.Left="200"
Canvas.Top="75" />
<Path Stroke="Black"
StrokeThickness="5"
Data="M 50,200 S 150,275 250,200" />
</Canvas>
在这种情况下整个笑脸设计被旋转了(如图3.4)。
图3.4整个容器转换
表3.4描述了不同类型的转换并提供了示例。
表3.4 转换类型
类型 | 描述 | 例子 |
RotateTransform | 在一个对象或对象树上执行二维旋转(在二维 x-y 坐标系内围绕指定点顺时针旋转对象。) | <RotateTransform Angle="30" /> |
SkewTransform | 在一个对象或对象树上执行二维扭曲。 | <SkewTransform AngleX="30" AngleY="75" /> |
ScaleTransform | 在二维 x-y 坐标系内缩放对象或对象树 | <ScaleTransform ScaleX="1.5" ScaleY=".75" /> |
TranslateTransform | 在二维 x-y 坐标系内平移(移动)对象或对象树。 | <TranslateTransform X="1.5" Y=".75" /> |
CompositeTransform | 以一个优先顺序执行一个混合的旋转、倾斜、伸缩和变形转换 | <CompositeTransform Rotation="30" ScaleX="1.5" TranslateX="150" /> |
你可以单独的使用4种基本类型转换,或者如果你需要混合使用转换(例如缩放和旋转),你可以使用复合转换组合多个转换。
动画
虽然在应用程序中使用动画的想法可能会让你联想到创造下一个卖座动画大片,但这不是动画的全部。动画只是随着时间的推移来改变XAML元素属性的一种方法。例如,一个简单的动画来改变矩形的宽度应该像这样:。
<DoubleAnimation Storyboard.TargetName="theRectangle"
Storyboard.TargetProperty="Width"
From="50"
To="250"
Duration="00:00:05" />
动画元素告诉一个特定的属性如何随时间变化。这个例子展示了如何更改一个名为theRectangl的元素在五秒钟内宽度从50到250。附加属性(Storyboard.TargetName 和Storyboard.TargetProperty)暗示了事实上动画并不是自己执行的,而是宿主在一个叫做故事板的容器中执行的。例子:
<Grid.Resources>
<Storyboard x:Name="theStory">
<DoubleAnimation Storyboard.TargetName="theRectangle"
Storyboard.TargetProperty="Width"
From="50"
To="250"
Duration="00:00:05" />
</Storyboard>
</Grid.Resources>
故事板是内嵌在Resources节中(通常是在主要的容器或用户控件级别)并命名,以便可以通过代码来执行和控制它。动画的工作单元是Storyboard。故事板可以包含一个或多个动画,但所有动画是并发执行(非连续)。因此,如果我们扩展这个故事板来包含两个动画:
<Grid.Resources>
<Storyboard x:Name="theStory">
<DoubleAnimation Storyboard.TargetName="theRectangle"
Storyboard.TargetProperty="Width"
From="50"
To="250"
Duration="00:00:05" />
<DoubleAnimation Storyboard.TargetName="theEllipse"
Storyboard.TargetProperty="Opacity"
From="1"
To="0"
Duration="00:00:03" />
</Storyboard>
</Grid.Resources>
当这个故事板执行时,两个动画时是同一时间执行的(再次并行), 尽管是动画本身针对的是不同对象的不同属性。
到目前为止你看到的动画都是DoubleAnimation类型的。这些动画都是被用来更改一个数字(一个双精度值)。也有动画会改变颜色(ColorAnimation)和向量(PointAnimation)。所有这三种类型的动画在一致的时间框架(被称为时间轴动画)下更改值。还有动画称为关键帧动画(keyframe animations)。这些动画也随着时间的变化改变属性,但是这样的计算基于在动画中特定的时间值。例如:
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="theRectangle"
Storyboard.TargetProperty="Height">
<LinearDoubleKeyFrame KeyTime="00:00:00" Value="50" />
<LinearDoubleKeyFrame KeyTime="00:00:01" Value="150" />
<LinearDoubleKeyFrame KeyTime="00:00:03" Value="200" />
</DoubleAnimationUsingKeyFrames>
对于每个时间轴动画来说这些是关键帧动画(例如双精度,点和颜色)但是他们以UsingKeyFrames作为命名的后缀,如上所示。故事板的附加属性仍然是用于标识动画的目标,但是,不同于一个从某个值到指定值的简单动画,而是使用一个或多个关键帧。例如,LinearDoubleKeyFrame的元素指定在某个时间,值应该是一个具体的数值属性。
在这种情况下,在动画开始时高度应在50开始;在第一秒内迅速转移到150;最后慢下来,用最后两秒时间移动到200。在帧之间插值取决于关键帧的类型。在这个例子中插值是线性的。你还可以分别使用样条和离散来实现曲线插值和阶梯插值。
有了这些工具,你应该能够创造出微妙的互动效果,给用户的印象是,他与现实世界的对象进行交互。
XAML样式
当编写代码时,这是习惯,采取共同的代码块和重用他们的方法,包括创建基类,创建静态类,甚至创建可重用的类库。XAML有同样有建立可重用设计的需求。
不过,这种可重用性更多的是关于创建一致的应用程序外观,而无需复制相同的代码。考虑一下这个普通的XAML:
<TextBox x:Name="nameBox"
FontSize="36"
ontFamily="Segoe WP"
FontWeight="Black"
BorderBrush="Blue"
Foreground="White"
HorizontalAlignment="Stretch" />
<TextBox x:Name="emailBox"
FontSize="36"
FontFamily="Segoe WP"
FontWeight="Black"
BorderBrush="Blue"
Foreground="White"
HorizontalAlignment="Stretch" />
在这个XAML中许多属性从一个文本框复制到其他文本框。如果我们需要改变的任何一个属性,我们将这些拷贝到其他地方,来保持UI的一致性。此外, Foreground和BorderBrush使用的颜色能够或者应该纳入整体的外观和感觉的考虑。很有可能,我们希望使用笔刷来保持一致性,不仅仅是从文本框到文本框,还可跨整个应用程序。这就是样式和资源的由来。
理解资源
第一层的一致性是使用共享资源。当创建一个应用程序时,你通常会希望在整个应用程序使用常见的颜色或笔刷。Silverlight允许你创建对象用于多个区域通过指定在Resources节并使用x:Key属性来标示对象。例如,你可以在Resources节定义一个SolidColorBrush,像这样:
<Grid x:Name="LayoutRoot">
<Grid.Resources>
<SolidColorBrush x:Key="mainBrush"
Color="Blue" />
</Grid.Resources>
...
</Grid>
每个从FrameworkElement继承的类(这意味着大多数XMAL元素)支持一个Resources集合。我们可以通过使用StaticResource标记扩展引用这些命名元素,就像这样:
<Grid x:Name="LayoutRoot">
<Grid.Resources>
<SolidColorBrush x:Key="mainBrush"
Color="Blue" />
</Grid.Resources>
<TextBlock Foreground="{StaticResource mainBrush}"
Text="Hello World" />
...
</Grid>
StaticResource标记扩展告诉XAML解析器用main Brush来代替foreground。你可以在许多地方使用资源,以便将他们从以后的变更中隔离出来,当你改变将main brush更改为LinearGradientBrush,它会级联更改每一个使用StaticResource的地方。
StaticResource标记扩展通过检索XAML文档通过正确的名称找到资源(通过层次),甚至继续检索超越XAML文档的开头。上面的XAML文件是在手机应用程序项目的App.xaml文件中。通常情况下,你可以把任何应用程序范围内使用的资源存放在这里。例如,如果App.xaml文件看起来像这样:
<Application x:Class="PhoneControls.App"
xmlns="..."
xmlns:x="..."
xmlns:phone="..."
xmlns:shell="...">
<!--Application Resources-->
<Application.Resources>
<SolidColorBrush x:Key="mainBrush"
Color="Blue" />
</Application.Resources>
...
</Application>
mainBrush将被定义在应用程序级别,这样任何XAML文件想用笔刷都可以这么做,就像这样:
<Grid x:Name="LayoutRoot">
<TextBlock Foreground="{StaticResource mainBrush}"
Text="Hello World" />
...
</Grid>
尽管这个示例显只示了一个笔刷(这是一个非常普遍的资源),但是资源不仅仅只限于笔刷。创建的任何对象都可以用这种方式。此外,当你要跨项目共享这些资源,你可以使用 ResourceDictionary对象来完成。资源字典也是XAML文件,包含了共享资源,可以通过合并字典导入到App.xaml中。这些字典可以是平面的XAML文件,也可以包含在单独程序集中,引用到你的手机应用程序。合并字典的详细信息,请参阅说明文档。
理解样式
虽然共享资源可以帮助你定义一个共同的外观和感觉,样式堆栈通过允许你在一个公共位置指定控件的默认值,扩展了这个想法。Style对象允许你创建这些默认属性:
<Style TargetType="TextBox"
x:Key="mainTextBox">
<Setter Property="FontSize"
Value="18" />
<Setter Property="FontFamily"
Value="Segoe WP Bold" />
</Style>
Style对象采用对象类型方式,这些对象类型可以应用Style,然后是一系列Setter对象,这些Setter对象定义了属性的默认值。在这个例子中Style为一个文本框是提供FontSize和FontFamily属性的默认值。你可以通过StaticResource标记扩展将它映射到元素的样式属性,应用这种风格到一个对象上,就像这样:
<TextBox Style="{StaticResource mainTextBox}" />
使用StaticResource标记扩展来设置文本框的Style属性,TextBox的默认属性值使用Style来设置。样式仅仅是命名的资源,所以你通常可以的将他们与其他资源一起放到App.xaml文件中。另外,你可以在你的样式中使用其他资源,像这样:
<Application.Resources>
<SolidColorBrush x:Key="mainBrush"
Color="Blue" />
<Style TargetType="TextBox"
x:Key="mainTextBox">
<Setter Property="FontSize"
Value="18" />
<Setter Property="FontFamily"
Value="Segoe WP Bold" />
<Setter Property="Foreground"
Value="{StaticResource mainBrush}" />
</Style>
</Application.Resources>
通过这种方式,共享资源可能级联应用到样式堆栈中。另外,样式自身也可以级联通过使用BasedOn属性:
<Application.Resources>
<SolidColorBrush x:Key="mainBrush"
Color="Blue" />
<Style TargetType="TextBox"
x:Name="baseTextBox">
<Setter Property="FontSize"
Value="18" />
<Setter Property="FontFamily"
Value="Segoe WP Bold" />
</Style>
<Style TargetType="TextBox"
x:Key="mainTextBox"
BasedOn="{StaticResource baseTextBox}">
<Setter Property="Foreground"
Value="{StaticResource mainBrush}" />
</Style>
</Application.Resources>
最后,样式是可以多态的。换句话说,TargetType应用到一个基类时,还可以应用到这个类型的全部对象上。例如:
<Application.Resources>
<SolidColorBrush x:Key="mainBrush"
Color="Blue" />
<Style TargetType="Control"
x:Name="baseControl">
<Setter Property="BorderBrush"
Value="Black" />
</Style>
<Style TargetType="TextBox"
x:Key="mainTextBox"
BasedOn="{StaticResource baseControl}">
<Setter Property="FontSize"
Value="18" />
<Setter Property="FontFamily"
Value="Segoe WP Bold" />
<Setter Property="Foreground"
Value="{StaticResource mainBrush}" />
</Style>
</Application.Resources>
由于基础样式的TargetType是Control,它可以被任何控件用来继承样式(或任何从Control类派生的控件样式)。
隐式样式
当你经常使用命名样式(和StaticResource标记扩展名),以配合一个XAML元素的样式(如显示在前面节),你也可以创建适用于默认情况下元素的样式。这些被称为隐式样式。要创建一个隐式样式,你的样式绝不能包括一个Key。例如:
<Application.Resources>
<Style TargetType="TextBox">
<Setter Property="FontSize"
Value="18" />
<Setter Property="FontFamily"
Value="Segoe WP Bold" />
</Style>
</Application.Resources>
通过删除样式上的X:Key,样式将应用到每个TargetType的元素上(例如TextBox)。如果一个元素指定使用了一个明确的样式(例如,通过样式名),隐式样式完全被取代。因此,你可以任选一种隐式或显式样式应用到XAML中特定的元素,但不是两个。在大多数情况下,你有一个隐式样式来控制控件的主要风格,然后使用明确的样式来处理特定用途的控件。
一个重大的区别在隐式风格的TargetType不是多态的,所以它只适用于特定的类型,而不是派生类型。例如,如果你创建一个“Control”类型的隐式风格,它将只应用于XAML具体控件类的元素;文本框和按钮元素(继承于Control)将不使用这种样式。样式的其他规则(例如使用基础资源、级联样式)都适用。
我们在哪儿?
本章向你介绍了Silverlight for Windows Phone 7中基础的XAML生态系统。有了这些概念,你可以开始为你的应用程序设计用户界面了。我只触及了XAML本质的表面,所以你不应该假设,我在本章描述了每一个元素和每个属性。利用帮助文档来充实你对Silverlight中 XAML的了解。这一章重点在于理解XAML的文本表示,下一章将介绍在你的应用程序与用户进行交互时,如何使用控件。