概述
请想象这样一个场景,小型企业通过自己的站点可以让更多的顾客了解到您的产品。在许多商业领域,公司都会有自己的站点来宣传自己。现在,越来越多的商业客户希望将自己的站点升级到Web 2.0,其中,许多客户选择了Silverlight。客户同时希望将自己的后端系统集成到Web站点中。类似这样的场景我们会始终贯穿整本书。
在本章,我们将:
- 我们将在现有站点中添加一个使用Silverlight技术创建的导航栏部件
- 理解Expression Blend并且学习如何与Visual Studio无缝结合
- 使用控件模板来增强导航部件的可视化效果
- 将导航部件添加到现有站点
- 使用Silverlight创建一个交互式图标并整合到站点中
开始改装站点
一开始,客户可能希望在站点中添加一种耳目一新的导航控件并且看上去非常有趣,在页面顶部添加一个交互式图标。
首先,我们将着手实现导航控件来替换掉原先在页面左手边的基于文本的导航链接。正如您在下图所看到的,当前站点的导航看上去一点都不酷,不过非常简洁。尽管如此,在保留简洁易用的同时,客户还是希望站点看上去更有现代感一些。
使用Silverlight技术创建更有活力的导航栏
我们的客户希望在站点中添加一种更炫的导航部件。他们委托了一名设计师设计了如下图所示的导航部件。
题外话:关于搜索引擎优化
我们可以轻易的使用Silverlight来创建包含所有网站内容和功能的站点。尽管如此,这么做会让站点被搜索引擎检索的时候带来一些麻烦。搜索引擎会使用一种叫做蜘蛛机器人的程序进行爬网。通常,这些程序只能够检索出HTML页中暴露出来的文本内容。搜索结果排名基于这种纯文本内容。如果将整个站点的内容放在基于像Silverlight这样的RIA平台会对搜索引擎的搜索造成一定的影响。站点的内容被搜索引擎检索的程度会打折扣。
注意:所有的富互联网应用程序(RIA)平台都有类似这样的问题存在。
除非这个问题被解决,否则我们还是尽量应该将一些关键词信息文本放入HTML,这样才更容易让搜索引擎检索到。
开始构建导航控件
在前面的章节,我们学习了两种不同类型的布局面板:Grid和Canvas。另外,Silverlight 4还有StackPanel、Border、WrapPanel、ViewBox和ScrollViewr。为什么会有这么多呢?好吧,每种布局元素都有自己的使用场景。
选择一种合适的容器
在现实生活中,您肯定不会拿纸盒去装水或者汽油桶去喝牛奶一样,在Silverlight中每种布局元素都有自己的用途。
举例来说,当您想创建一个工具栏是,您可能会使用StackPanel或者WrapPanel,而不会是Canvas。为什么?在你可以手工用代码来控制布局逻辑来管理容器下所有子控件前,没有什么具体的理由。毕竟,已经有这些控件来帮您完成这些繁重的任务。
下表中显示的就是在Silverlight 4中我们使用最多的一些布局容器:
容器 | 布局行为 |
Canvas | 使用基于Canvas左上角X、Y坐标来定位元素的容器 |
Grid | 使用Grid中事先定义好的行和列来布局元素 |
InkPresenter | 该画布可以处理数字墨水技术(可以在这个容器内部显示出类似墨水笔迹的效果) |
StackPanel | 可以实现从上至下或从左到右依次排列元素的容器 |
WrapPanel | 将元素包装在改容器中,如果某行元素的宽度总和超过了WrapPanel的宽度,那么这行元素的最后一项将被自动地移动到下一行 |
Border | 在元素周围绘制一个边框 |
ViewBox | 在ViewBox可用区域缩放元素 |
ScrollViewer | 在控件外面放置一个带有滚动条的容器 |
Silverlight也支持编写自己的布局代码。不过在您这么干之前还是应该先考虑一些现有的布局控件是否可以满足您的需求。
使用StackPanel
基于当前站点的导航链接,StackPanel应该是最佳选择。就像他的名字所描述的,他可以将子控件进行堆叠,这个特性用在我们的导航控件中真是再合适不过了。
现在,让我们在站点中创建一个用StackPanel包装的按钮控件组。为了实现这个目的,我们将会按照下面的方式来操作:
- 启动Visual Studio 2010,点击文件|新建工程
- 如下图所示选择创建一个新的Silverlight应用程序
- 将工程命名为CakeNavigationButtons并且点击确定按钮,所有设置保持默认选项。
- 在MainPage.xaml文件中,在Grid标签中编写以下代码:
<StackPanel>
<Button Content="Home" />
<Button Content="Gallery"/>
<Button Content="Order"/>
<Button Content="Locations"/>
<Button Content="Contact Us"/>
<Button Content="Franchise Opportunities"/>
</StackPanel>
返回到Visual Studio 2010并且点击调试->启动调试或者点击F5来启动应用程序 出现以下窗口,点击确定启动调试. 启动后您的应用程序看上去应该类似下面的样子:
到目前为止,我们已经创建了一个使用StackPanel包装的按钮组为站点提供导航功能,不过程序看起来还不能让人提起兴趣,这些按钮什么都做不了。接下来我们需要做什么来将设计师的设计反映出来,并且在点击按钮是可以实现在站点内进行导航呢?
刚刚发生了什么
目前我们所作的只是为最终成为动态导航控件的第一步。您已经创建了一个Silverlight应用程序,添加了一个StackPanel,然后添加了一组按钮。接下来让我们把导航栏变得更炫一些。
使用Styles添加一些样式效果
许多人认为Silverlight控件是无外观的,所有的Silverlight控件都有自己默认的样式,而这些样式是来自于系统内部的一套资源文件。好消息是我们可以为任何控件定义自己的资源文件。您可以像定义层叠样式表那样定义Silverlight控件样式。
样式
举例来说,我们希望按钮中的文字更大一些,我们可以给每个按钮添加一个FontSize属性,因此我们的XAML代码会这么写:
<StackPanel>
<Button Content="Home" FontSize="18" />
<Button Content="Gallery" FontSize="18"/>
<Button Content="Order" FontSize="18"/>
<Button Content="Locations" FontSize="18"/>
<Button Content="Contact Us" FontSize="18"/>
<Button Content="Franchise Opportunities" FontSize="18"/>
</StackPanel>
这样我们就可以得到期望的效果,不过这样一来也会让我们的代码显得很臃肿,这样一来您将会敲入许多重复的代码。样式为这种问题提供了更加优雅的解决方案。例如,我们可以定义一种样式来统一管理字体的大小,如下面代码所示:
<Style x:Name="biggerTextStyle" TargetType="Button">
<Setter Property="FontSize" Value="18"/>
</Style>
上面的代码片段实际上定义了一个名字叫做biggerTextStyle的样式并且声明该样式应用于按钮控件。在样式定义中,Setter节点可以定义任意多个。在该样式中,仅有一个Setter节点用来设置FontSize属性的值为18.要使用该样式,我们打算做两件事:将样式加入到应用程序中并告诉按钮去引用这些样式。
在Silverlight中,样式被认为是一种资源,任何类型的数据都被存储在一个对象中。因此,我们将会把样式放在UserControl的资源集合中,而UserControl是MainPage.XAML文件的根元素。这一点有点类似于HTML中的HEAD节点:
<UserControl.Resources>
<Style x:Name="biggerTextStyle" TargetType="Button">
<Setter Property="FontSize" Value="18"/>
</Style>
</UserControl.Resources>
在Silverlight中我们有几种方式来存储资源。许多控件有资源集合并且可以将它们保存到App.xaml文件中,这样整个应用程序都可以访问到位于App.xaml中的样式资源。或者,我们甚至可以定义一个资源字典,这是一种包含资源的独立文件。资源字典类似HTML中的外部CSS文件。资源字段可以跨应用程序共享。
一旦定义好样式,我们需要告诉按钮来使用他们,通过在按钮的Style属性中添加对样式的引用,因此按钮的XAML代码如下所示:
<Button Content="Home" Style="{StaticResource biggerTextStyle}" />
<Button Content="Gallery" Style="{StaticResource biggerTextStyle}" />
<Button Content="Order" Style="{StaticResource biggerTextStyle}" />
<Button Content="Locations" Style="{StaticResource biggerTextStyle}"
/>
<Button Content="Contact Us" Style="{StaticResource biggerTextStyle}"
/>
<Button Content="Franchise Opportunities" Style="{StaticResource
biggerTextStyle}" />
您可能会想,这些大括号里面的代码都是干什么用的。这些大括号中的代码会告知XAML处理引擎去执行一些特定的命令,这些命令称作标记扩展。上面的标记扩展告诉Silverlight运行时设置样式属性为名称为biggerTextStyle的样式。
我们为所有的按钮都应用了biggerTextStyle样式资源。这样便确保我们所有的按钮看上去都是一致的。不过代码看上去还是很臃肿。有什么办法来创建一种默认的样式应用于所有按钮呢?幸运的是,这种特性被添加到了Silverlight 4中。现在我们有一中选择定义一种默认样式应用于部分控件。在样式声明中我们删除x:Name=”biggerTextStyle”属性,最后XAML看上去应该像下面的代码:
<Style TargetType="Button">
<Setter Property="FontSize" Value="18"/>
</Style>
通过删除x:Name属性,我们创建了一个匿名样式,或者说默认样式,这将应用于所有按钮。现在,我们的XAML标签看上去更干净更小巧:
<Button Content="Home" />
<Button Content="Gallery" />
<Button Content="Order"/>
<Button Content="Locations"/>
<Button Content="Contact Us"/>
<Button Content="Franchise Opportunities"/>
警告:重写匿名样式
如果您希望重写匿名样式并且恢复控件的默认样式,可以设置Style属性为null:
Style="{x:Null}"
好吧,理论知识已经足够多了,让我们创建一个默认样式吧!
使用Expression Blend创建应用程序
到目前为止,您已经使用Silverlight完成了所做的工作。通过智能感知手工编写XAML,就这个例子而言还行,但是如果要创建更复杂的设计则需要借助其他一些工具了。
智能感知是Visual Studio的一个特性,并且在Blend中有一种类似的功能叫自动完成,当您敲入关键词、方法或变量名时,这些名称会自动出现在一个列表中供您选择。
Expression Blend在一开始肯定会吓跑那些第一次接触的开发人员,因为他的界面跟熟悉的Visual Studio有很大区别,不过如果您在仔细观察一下会发现Blend与Visual Studio有许多共同点。对于初学者,两种工具使用了同样的解决方案和工程文件格式。这意味着他们之间是100%兼容的,并且使开发人员和设计人员可以无缝结合。您甚至可以使用Visual Studio和Blend打开同一个工程。只不过当您在Visual Studio中修改了源代码后切换到Blend会有如下提示:
如果您以前与设计人员一同工作过,他们一般会使用图形设计工具将界面模拟出来然后交付给开发团队。在很多时候,一个简单的润色效果就会让我们这些开发人员伤透脑经。大家知道,使用HTML做一个圆角的矩形框是多么困难的一件事情(译者注:这种情况在HTML 5里面得到了改进,在HTML 5中得益于CSS 3,实现圆角矩形变得非常容易了)。好消息是,Silverlight的出现使那些黑暗的日子一去不复返了。
Expression Blend速成课
在下面的截图中,我们的CakeNavigationButton工程使用Expression Blend加载。打开解决方案的方法与您在Visual Studio中一致。
就像在Visual Studio中一样,您可以自定义Expression Blend的界面以符合您的习惯。您可以拖动标签栏,将标签栏停靠在指定区域,通过这些操作您可以自定义出自己的工作区来符合您的偏好。
Blend是美化版的Visual Studio?
如果您看过了CakeNavigationButton工程,那么在应用程序窗口左手边的工具栏与Visual Studio中的工具栏有着本质的区别。
Blend中的工具栏更接近于Adobe Photoshop或者Adobe Illustrator这样的图形设计工具的工具栏。如果您将鼠标光标停放在这些按钮上,您将看到一个提示信息告诉您这些按钮的作用,当然也包含了这些按钮对应的快捷键。在左上角,您会看到一个名字为工程的标签页。这相当于Visual Studio中的解决方案视图。在正中间名为MainPage.XAML的标签也旁边有个星号,则表示这个文件修改过但还没有保存。请看下图,展示了Blend的工程视图:
我们观察上面的截图,我们会找到文档标签和设计界面,在Blend中称作art board。在art board的右上角,有三个小按钮,他实现了在Design View、XAML View和Split View之间进行切换。
在art board边缘稍下一点的地方,有一些用于修改设计界面视图的控件。您可以放大设计界面,打开snap Grid可见性(译者注:这个功能打开后可以将您的控件在设计界面拖动的时候自动的吸附在Grid网格的附近,经常做设计的朋友应该不会陌生吧。)或者关闭该功能。
接下来我们看看右上角有些什么,我们会看到有Properties标签,您可以把它理解为一个改进版的Visual Studio Properties标签栏。通过截图我们可以看到,颜色拾取器提供了更多颜色选择。还有一个非常酷的功能 - 搜索,您只需要键入属性名的关键词,就可以把相关的属性列举出来。
在左下角有一个叫做对象与时间线的视图,他显示了当前打开文档的对象层次结构。在CakeNavigationButtons工程中的MainPage.XAML,该页面有一个StackPanel和六个Button包含在一个名叫LayoutRoot的Grid中。在对象与时间线视图中点击选择某个对象会反映在设计视图中,反之亦然。
Expression Blend 是一种相对复杂但功能强大的软件。我们会在整本书中贯穿式地学习Blend。
试一试 - 用Blend来定义样式
在本章前面一部分,我们使用Visual Studio在XAML中创建并且引用了样式。现在让我们使用Blend来修改样式,并且学习如何以图形化用户界面的方式完成这些工作。请按以下步骤来做:
- 使用Blend打开CakeNavigationButton解决方案。
- 在Blend的右上角,有三个标签栏分别是属性、资源和数据。
- 在资源标签栏,依次展开三个标识为UserControl的节点然后点击名称为【Button default】的节点。
- 您的画布看起来应该如下图所示:
- 点击属性标签栏并且向下滚动至文本分类:
- 将字体修改为14个像素并且点击按钮B,可以在粗体或者非粗体间切换。
- 在搜索栏输入cursor。请注意,搜索栏只会显示含有cursor关键词的那些属性。
- 接下来,点击下拉菜单选择Hand:
- 接下来在搜索栏输入margin,在margin的四个文本框中填入5,如下图所示:
10. 请看截图中左手边的地方;您将看到一个名叫对象与时间线的标签栏。点击那个向上的箭头(截图中红色箭头所指的地方)。这将使您推出样式编辑模式返回到应用程序顶层。
11. 从工程菜单中选择运行工程或点击F5运行工程。
12. 现在您应该看到改变了吧。现在每一个按钮周围都有间隔了,字体也发生了改变,而且当鼠标放在按钮上是也会出现手型图标。现在,您的应用程序看上去应该如下图所示:
13. 现在所有的按钮都有了红色的背景。
刚刚发生了什么?
我们刚刚使用Expression Blend编辑了样式并且该软件为我们简化了许多操作。如果您在回过头来看看生成的XAML代码,您会发现在Style中包含了几个Setter节点。每个Setter节点都跟我们做的设置有关系:
<Style TargetType="Button">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Background" Value="Red"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="FontStyle" Value="Italic"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Margin" Value="5,5,5,5"/>
</Style>
您可以手工编写XAML,不过正如您所看到的,使用Blend会更方便一些。在许多实际的开发中,您应该根据情况来选择手工编写还是使用Blend。
试一试
现在,我们已经按照我们期望的那样设置了按钮的样式。如果我们要重写某个属性该怎么办呢?例如,如果您只希望名称为Home的按钮的字体大小区别于其他按钮,一样,您可以使用Blend通过在属性标签栏设置相关属性或者在XAML中编写如下代码:
<Button Content="Home" FontSize="9" />
通过以上代码,您可以看到如下效果:
或者,您可以定义另外一种样式;一种用来指定更小的字体。我们在XAML中这么做了,但还没有在Blend中实现。
现在让我们开始吧。确定选择了UserControl元素,然后点击对象菜单,然后点击编辑样式|新建空样式…您将看到如下对话框:
接下来,修改Name为smallerTextStyle然后点击确定。使用属性标签栏来修改字体大小到9。您也可以放心的尝试修改其他属性。
当您完成这些修改之后,点击对象与时间线标签栏的向上箭头退出样式编辑模式。一旦您返回到了主视图,点击选择Home按钮。在属性标签栏的搜索栏输入style。您会在杂项分类中找到Style属性,Style属性旁边有一个小矩形框。在Blend中,这意味着您可以针对这个属性做更多的设置。点击小矩形框显示出高级属性选单。
一旦您点击了小矩形框后,一下所示的上下文菜单就会显示出来。选择本地资源|smallerTextStyle,如下图所示:
再次运行工程或者查看设计视图来观察按钮的变化。您可以将这些样式应用于其他的按钮,编辑现有的样式或者创建一种全新的样式。如果应用了smallerTextStyle样式,那么那些按钮上面的文字也会跟着变小。如果应用的是默认样式,那么按钮相对来说会大一些。
为控件应用皮肤
到目前为止,您看到了样式可以改变某个控件的样式,现在只能做到这一步。不管我们做多少的修改,按钮还是看起来没有根本变化。确实,一定有一种方法进一步的符合我们期望的样式。有那么一种方法,叫做皮肤。
Silverlight中的控件相当灵活和可定制化。这种灵活性实际上得益于控件既有可视化树(VisualTree)也有逻辑树(LogicalTree)。可视化树处理控件中的可视化元素,逻辑树处理所有的逻辑元素。Silverlight中的所有控件都继承了默认的样式模板,这些样式定义决定了控件的样式。您可以通过重定义控件的可视化树来非常容易的重写这些默认样式。
设计人员既可以在Blend中直接设计图形也可以使用诸如Expression Design这样的图形设计工具进行图形设计工作。您还可以从Adobe Illustrator和Adobe Photoshop中导入图形。
在我们这个例子中,我们假设有一个专门负责图形设计的团队,他们可以提供专业的图形设计给我们,如果我们运气不错,最好是XAML的代码片段。在这种情况下,设计人员发给我们的是XAML,这些XAML都可以运用到这些按钮上了。
<Rectangle Stroke="#7F646464" Height="43" Width="150"
StrokeThickness="2" RadiusX="15" RadiusY="15" VerticalAlignment="Top"
>
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFEE9D9D" Offset="0.197"/>
<GradientStop Color="#FFFF7D7D" Offset="0.847"/>
<GradientStop Color="#FFF2DADA" Offset="0.066"/>
<GradientStop Color="#FF7E4F4F" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
在输入了上述代码后,您可以看到下面的按钮样式:
我们要让这个样式应用于所有的按钮。
动手时刻 - 给控件运用皮肤
我们准备运用上面的XAML代码片段来装饰我们的按钮。为了达到这个目的我们需要按照如下的步骤操作:
1. 打开CakeNavigationButtons工程
2. 在MainPage.XAML文件中,切换到XAML视图,或者点击XAML按钮或者选择视图(View)菜单|激活文档视图(Active Document View)|XAML。
3. 输入下面的XAML代码(在StackPanel结束标记之后键入):
(</StackPanel>)
<Rectangle Stroke="#7F646464" Height="43" Width="150"
StrokeThickness="2" RadiusX="15" RadiusY="15"
VerticalAlignment="Top" >
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFEE9D9D" Offset="0.197"/>
<GradientStop Color="#FFFF7D7D" Offset="0.847"/>
<GradientStop Color="#FFF2DADA" Offset="0.066"/>
<GradientStop Color="#FF7E4F4F" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
4. 切换回设计视图,或者选择视图(View)|激活文档视图(Active Document View)|设计视图(Design View)
5. 右键点击矩形然后点击Make Into Control。
6. 在对话框中,选择Button,修改Name(Key)字段为navButtonStyle然后点击OK。
7. 您现在就已经处于模板编辑模式。有两种屏幕指示器告诉您处于该模式:一种是Objects and Timeline标签栏:
8. 另外一种是在MainControl.xaml中(位于art board顶部):
9. 点击Objects and Timeline标签栏的向上箭头退出模板编辑模式。
10. 删除被矩形覆盖的按钮。
11. 选择StackPanel中的所有按钮,您可以选择第一个按钮然后按住Shift不放在点击最后一个按钮。
12. 当所有按钮被选择后,在Properties标签栏,在搜索栏输入Style。
13. 使用您刚刚掌握的技术,将样式修改为navButtonStyle,因此您的应用程序看上去应该如下图所示:
现在的样式看上去还是不太好,不过已经很接近了。我们需要把字体弄小一点;幸运的是,我们现在已经学会了怎么在Blend中做到这一点。
14. 点击Object菜单|Edit Style|Edit Current。
15. 在Properties标签栏,修改字体大小为18,Cursor为Hand,Height为45,Width为200。这是您应该可以马上看到变化了。光标的变化只能在程序运行之后才可以看到。
16. 退出模板编辑模式。
17. 有个小问题关于最后一个按钮;因为字母比较多,如果设置字体大小为18则显得不太协调,我们试着将字体改为12。
18. 运行之后您的程序看上去应该如下图所示:
19. 将光标放在按钮上后,按钮无法产生交互效果了,我们将在下面修复这个问题。
刚刚发生了什么?
我们刚才放置了几个普通的默认样式按钮,然后使用设计人员提供的样式装饰了一下我们的按钮,不过我们到底是怎么办到的呢?
有疑问的时候,最好的办法就是去看XAML
最好的学习Silverilght的方法就是去研究XAML代码,这样会让您有一个更深入的理解。通常Blend或者Visual Studio会隐藏一些实现细节。
对于初学者,我们使用Blend告诉Silverlight我们需要一个按钮,然后通过设置样式告诉Silverlight按钮应该看上去是什么样子。这个数据是被封装到了样式中并且我们告诉所有的按钮使用新样式。当新样式被创建后,我们丢失了许多格式化的数据。然后我们在将这些格式化数据重新加进来,另外在添加一些属性。
如果您真的非常好奇想了解到底怎么办到的,好吧,让我们好好看看下面的代码:
<Style TargetType="Button">
<Setter Property="FontSize" Value="18.667"/>
<Setter Property="Background" Value="Red"/>
<Setter Property="FontStyle" Value="Italic"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Margin" Value="5"/>
</Style>
<Style x:Key="smallerTextStyle" TargetType="Button">
<Setter Property="FontSize" Value="9"/>
</Style>
<Style x:Key="navButtonStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<Rectangle RadiusY="15" RadiusX="15" Stroke="#7F646464"
StrokeThickness="2">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1"
StartPoint="0.5,0">
<GradientStop Color="#FFEE9D9D" Offset="0.197"/>
<GradientStop Color="#FFFF7D7D" Offset="0.847"/>
<GradientStop Color="#FFF2DADA" Offset="0.066"/>
<GradientStop Color="#FF7E4F4F" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<ContentPresenter HorizontalAlignment="{TemplateBinding
HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding
VerticalContentAlignment}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="FontSize" Value="24"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Height" Value="45"/>
<Setter Property="Width" Value="200"/>
</Style>
您会立刻发现这些XAML代码多么冗长啊。我们还没开始大量的工作就已经生成了这么多代码了。像Blend这样的工具的确可以节约我们敲击键盘的时间。接下来我们要看看我们设置Template属性,在样式定义中插入一个Setter节点。其实还有一些设置交互样式的节点,我们称作视觉状态管理器(Visual State Manager)。
在前面我们修改了控件的模板,您要记住,当您移动鼠标到任何按钮时,他们会通过改变颜色来体现交互特性。这种视觉上的反馈对于用户体验来说非常好。
状态的理念
视觉状态管理器(Visual State Manager)正如名字所描述的,它是用来帮助您管理控件的视觉状态的。他非常简单并且强大,通过VSM意味着可以管理控件的视觉状态过渡,很多细节的动画机制被VSM隐藏了。
动手时刻 - 学习使用视觉状态管理器
在该练习中,我们将要使用视觉状态管理器为我们的控件模板提供一些视觉提示。我们用Blend生成我们需要的XAML代码。为了达到这个目的,我们需要按照下面的步骤去做:
1. 在MainPage.xaml文件中,在任何应用了navButtonStyle的按钮上点击右键。
2. 点击Edit Template|Edit Current,如下图所示:
3. 在Blend左上角点击States标签栏:
4. 点击Normal。请注意art board的边框出现了一个红色边框,这个就是告诉您当前已经进入到录制状态(recording is on。)
5. 点击MouseOver然后点击Objects and Timeline标签栏上的矩形框。
6. 使用Properties标签栏来修改矩形框的背景颜色,并且将背景模式切换为梯度颜色(Colors of the gradient)。您可以点击每一个stops小按钮(下图中红圈标注的即是)来改变梯度颜色的一些起始和终止颜色:
7. 先点击其中一个stops,然后使用颜色拾取器选择一个新颜色。重复上述操作修改剩余的stops按钮。
8. 在Objects and Timeline标签栏里面点击向上的小箭头退出模板编辑模式。
9. 运行程序,此时每个按钮都会在鼠标停靠在上面的时候改变颜色。
10. 然后,关闭浏览器返回到Blend。虽然可以正常工作了,但是看上去还是没有默认的按钮样式那么自然。
11. 我们再次回到模板编辑模式在States标签栏里面修改Default Transition属性为0.2秒,如下图所示:
12. 再次运行程序,现在看起来好多了。
刚刚发生了什么?
我们实现了看上去更加自然的导航按钮。并且现在我们可以根据喜好自由的定义各种过渡效果和视觉状态了。
如果您仔细查看XAML,这些代码开始变得臃肿了。然而,我们的按钮代码看上去还是比较简洁的:
<Button Content="Home" Style="{StaticResource navButtonStyle}" />
<Button Content="Gallery" Style="{StaticResource navButtonStyle}" />
<Button Content="Order" Style="{StaticResource navButtonStyle}" />
<Button Content="Locations" Style="{StaticResource navButtonStyle}" />
<Button Content="Contact Us" Style="{StaticResource navButtonStyle}"
/>
<Button Content="Franchise Opportunities" Style="{StaticResource
navButtonStyle}" FontSize="14" />
显然,所有的工作都在样式和控件模板中完成了。这些XAML代码看起来很长。我只把重点代码拿出来给大家看一下:
<vsm:VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00"
Duration="00:00:00.0010000" Storyboard.TargetName="rectangle"
Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[
3].(GradientStop.Color)">
<EasingColorKeyFrame KeyTime="00:00:00" Value="#FFFFFFFF"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00"
Duration="00:00:00.0010000" Storyboard.TargetName="rectangle"
Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[
1].(GradientStop.Color)">
<EasingColorKeyFrame KeyTime="00:00:00" Value="#FFB1DCF4"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00"
Duration="00:00:00.0010000" Storyboard.TargetName="rectangle"
Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[
0].(GradientStop.Color)">
<EasingColorKeyFrame KeyTime="00:00:00" Value="#FFE2E2E2"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00"
Duration="00:00:00.0010000" Storyboard.TargetName="rectangle"
Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[
2].(GradientStop.Color)">
<EasingColorKeyFrame KeyTime="00:00:00" Value="#FFFDFDFD"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
添加事件处理
终于可以写一些C#代码了。到目前为止,我们使用工具做了很多事倍功半的事情却没有写一行真正的代码。确实,我们已经创建了许多XAML代码,却没有写任何的C#或者VB.NET代码。怎么会这样呢?
细心的读者可能已经注意到我们的MainPage.xaml文件还有一个附带的后台代码文件MainPage.xaml.cs。
下面为后台代码的详细内容:
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace CakeNavigationButtons
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
}
}
现在我们打开Visual Studio。
动手实践 - 开始编写代码
我们打开后台代码文件,看看当我们创建工程后,Visual Studio或Blend都为我们创建了那些基础代码。接下来我们会为我们的导航按钮添加一些事件处理代码。为了完成该练习,您需要按如下步骤操作:
1. 在Visual Studio中打开CakeNavigationButtons工程。
2. 打开MainPage.xaml.cs文件。
3. 在方法InitializeComponent上点击鼠标右键然后选择Go to Definition。
4. 请注意,此时我们进入到了一个叫做mainpage.g.cs的文件中,这个文件是创建工程时自动创建的。
5. 关闭该文件回到MainPage.xaml.cs文件,然后注释掉方法InitializeComponent。
6. 运行程序看看发生了什么,其实什么都不会发生。
7. 停止调试并且关闭浏览器。
8. 取消InitializeComponent的注释。
9. 打开文件MainPage.xaml然后进入到Home按钮的XAML节点,输入Click=,此时智能感知将会显示创建事件处理代码的提示:
10. 点击<New Event Handler>
11. 现在您的按钮已经和某个事件程序绑定在一起了。
12. 右键点击Click="Button_Click"然后点击Navigate to Event Handler。
13. 在事件处理程序中,添加下面代码然后重新运行程序。
MessageBox.Show("Hello from Silverlight");
14. 点击Home按钮您将看到:
15. 点击Ok然后关闭浏览器。
16. 删除下面这样代码:
MessageBox.Show("Hello from Silverlight");
17. 使用下面的代码替换刚才的代码:
System.Windows.Browser.HtmlPage.Window.Navigate(new Uri(http://
www.packtpub.com/));
18. 再次运行程序点击Home按钮,这将会将您导航到该书出版社的主页。
刚刚发生了什么?
这与ASP.NET的开发非常类似;有一个标记文档,对应标记文档的有一个后台代码文件与之关联。我们还在浏览器里面弹出了一个警告框。这跟使用javascript弹出的提示框很类似。只不过javascript是受浏览器托管的,而Silverlight中的这个信息提示框则是由托管代码运行时托管的。最后我们修改了代码,使用户点击Home按钮时跳转到了出版社站点的首页,这得益于Silverlight中一种称作HTML桥接的技术(HTML Bridge),这种技术允许Silverlight和宿主浏览器或者HTML文档进行交互访问。我们也可以使用Blend编写后台的事件处理程序。
我们学到了什么
是时候了解我们的代码运作机制了。是的,我们知道Silverlight是一种跨浏览器、跨平台的技术,它可以运行在诸如Windows、Macintoshes、Linux甚至是某些型号的手机上。这意味着您的应用程序实际上运行在Silverlight运行时,而Silverlight运行时又是运行在宿主浏览器中的,而宿主浏览器又是运行在用户操作系统中的。好累…。
正如您看到下面的图表展示的,在您的代码和各个运行时之间有许多层次:
Silverlight 4 有一种所谓的托浏览器模式(Out Of Browser)。在这种模式下无法使用HTML桥接技术,不过他也带来了一些好处,即可以使我们创建的Silverlight应用程序更像桌面应用,并且可以设置这些应用程序为受信任的程序,这样一来可以解决从Silverlight 3以来一直困扰我们的跨域访问问题。
在Silverlight中应用动画
Silverlight驱动了一套丰富的动画系统,并且易于实现。Silverlight中的动画模型是以时间为基础的。这就意味着所有部件的运动都是基于一组时间线。每个动画都是以故事板(StoryBoard)作为中心的,包括所有的动画数据和依赖时间线。Silverlight控件可以包含任何数量的故事板。
故事板包含了一个或多个关键帧(Key frame)元素,他们负责使屏幕上的对象移动位置、改变颜色或者其他什么行为。在Silverlight 4 中主要有4种类型的关键帧:线性(Linear)、离散(Discrete)、曲线(Spline)和缓和(Easing)。下面的表格说明了每种关键帧的含义:
关键帧类型 | 描述 |
线性(Linear) | 以一种平滑的方式从起点状态移动到终点状态 |
离散(Discrete) | 瞬间从起点状态跳至终点状态 |
曲线(Spline) | 基于计算曲线以变化的速度从起点状态移动到终点状态 |
缓和(Easing) | 这是曲线关键帧的一个改进版,这种类型的关键帧会以一种缓和的方式从起点状态移动到重点状态。 |
与Flash有很大不同
Silverlight中的动画模型与Flash中的动画模型有很大区别。在Flash中,动画是基于帧的,而在Silverlight中则是基于时间的。
术语故事板(StoryBoard)来自动画行业,电影由每个场景所组成。
动手实践 - 制作动画
客户往往更喜欢将他们那么单板乏味的纯文本Logo替换为制作精良的动态Logo。现在,假设设计人员将设计好的图形发给了开发人员。我们需要按如下步骤进行:
1. 使用Blend从第二章节所在文件夹吧CakeORama logo工程打开。
2. 此时Blend应该已经自动加载了文件MainControl.xaml并且您的屏幕看上去应如下图所示:
3. 在Objects and Timeline标签栏,您将看到一个由矢量图形组成的对象列表。每个字母都是由Path对象绘制而成。
4. 让我们开始添加一些动画吧。在Objects and Timeline标签栏,点击加号(+)来创建一个新的故事板(StoryBoard)。
5. 在Create Storyboard Resource对话框中,输入introAnimationStoryboard然后点击OK。
6. 您将会发现屏幕上的一组变化。第一个变化,art board被一个红色边框包裹起来并且提示:introAnimationStoryboard timeline recording is on,如下图所示:
7. 如果您观察一下Objects and Timeline标签栏,您将看到我们刚刚创建的这个introAnimationStoryboard的时间线编辑视图:
8. 我们现在就添加一个关键帧。那条垂直的黄色线条表示播放头(play head),他标志了您当前在时间线的位置。选择canvas1对象。
您可以通过点击F6切换到动画工作区视图(Animation Workspace)。
9. 在位置0上点击带有绿色加号标识的矩形框按钮来创建一个新的关键帧。一个白色椭圆形的标志出现在了您刚才创建关键帧的位置。请参见下图所示:
10. 移动播放头(就是那个垂直的黄条了)到0.7秒的位置,然后在1秒稍靠左的位置点击一下。
11. 点击在第9步中的那个按钮创建另外一个关键帧,之后您的界面应如下图所示:
12. 将播放头移动回0的位置。
13. 确保canvas1对象仍被选中。点击并且抓取logo图形,将这些文字拖离舞台。
14. 点击下图所示的播放‘按钮,现在可以开始预览动画了:
15. 现在,我们所需要做的就是告诉Silverlight,当控件完成加载后开始运行动画,不过我们要先离开录制模式。要这么做,请在Objects and Timeline标签栏点击X按钮。
16. 在Objects and Timeline标签栏点击【UserControl】。
17. 在Properties标签栏,您将看到一个闪电形状的按钮。点击它来查看UserControl相关的事件:
18. 要让事件处理程序和Loaded事件关联起来,请在Loaded旁边的文本框中输入UserControl_Loaded,如下图所示:
19. 一旦您点击回车键,后台代码文件将会立即出现,并且光标会放在这个事件处理程序的方法体内。
20. 添加以下代码到事件处理方法中:
introAnimationStoryboard.Begin();
通过菜单栏或者点击F5运行该工程。您应该可以看到这些logo文字平滑地从舞台外部掉入到舞台内。如果无法显示该动画请尝试刷新浏览器,您应该就能看到了。
刚刚发生了什么
刚刚您使用Silverlight创建了第一个动画程序。首先,您创建了故事板(Storyboard)然后添加了一组关键帧。您修改了canvas的属性,创建了一个起始关键帧,并且Silverlight自动的在起点和终点间创建了余下的过程动画。然后您还学习了Silverlight的动画是基于时间的,而不是基于帧的。
就像我们之前所作的,让我们仔细看看这些XAML代码,了解动画到底是怎么运作的。您会发现Storyboard的xaml代码是在UserControl中的。这些资源内容总是出现在xaml文档的顶部进行定义。别担心下面那些参数值与您工程中的不一致:
<Storyboard x:Name="introAnimationStoryboard">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.
TargetName="canvas1" Storyboard.TargetProperty="(UIElement.RenderTrans
form).(TransformGroup.Children)[3].(TranslateTransform.Y)">
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="-229"/>
<EasingDoubleKeyFrame KeyTime="00:00:00.7000000" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.
TargetName="canvas1" Storyboard.TargetProperty="(UIElement.RenderTrans
form).(TransformGroup.Children)[3].(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
<EasingDoubleKeyFrame KeyTime="00:00:00.7000000" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
这里,我们做了两件事,给故事板命名:
<Storyboard x:Name="introAnimationStoryboard">
这够简单吧,不过下一个节点怎么办呢?这行代码告诉故事板我们要修改Double值从0秒开始。这也会进一步为我们的动画指定一个目标:canvas1和我们目标上的一个属性:
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.
TargetName="canvas1" Storyboard.TargetProperty="(UIElement.RenderTrans
form).(TransformGroup.Children)[3].(TranslateTransform.Y)">
已经很清楚了,不过TargetProperty值代表了什么含义呢?请看下面代码:
(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTr
ansform.Y)
我们知道为了产生动画效果,logo从可视区域上部区域移动到了他原来的位置。如果我们熟悉XY坐标系,X代表了水平坐标,Y代表了垂直坐标,然后TranslateTransform.Y这部分就很容易理解了。在Silverlight术语中,叫做变换Canvas的Y属性。那么,TransformGroup又是什么呢?
看看我们的canvas1节点。请看如下代码:
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Canvas.RenderTransform>
当我们试图创建动画时Blend会自动地在Canvas节点中插入这些代码。如果您删除TransformGroup节点下面的所有子节点,然后运行程序会出现如下的异常信息,该错误信息报告无法解析TargetProperty:
弄清楚这些代码非常重要,不过到底这里是怎么回事呢?TranslateTransform对象是一个类型为Transform对象来决定在Silverlight中对象如何变化。他们被打包在一个称作TransformGroup的对象中,这些属性可以设置在UIElement对象下所有对象里名为RenderTransform属性里。UIElement是所有可视化元素的基类。
通过刚才学到的知识,我们现在看看(TransformGroup.Children)[3]访问的是第四个元素,因为索引是基于0的。不太一致地,TranslateTransform节点是TransformGroup的第四项,如果试图改变这些项在TransformGroup中的顺序同样会导致运行时异常。
上面那几行XAML代码告诉Silverlight运行时我们打算启动动画,现在我们告诉他如何做并且什么时候这么做:
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="-229"/>
<EasingDoubleKeyFrame KeyTime="00:00:00.7000000" Value="0"/>
第一个EasingDoubleKeyFrame节点告诉Silverlight,在0秒的时候,我们希望值为-299。这时Logo处于舞台的顶部,并且是不可见的。第二个EasingDoubleKeyFrame节点告诉Silverlight,在0.7秒时,我们希望该值为0。这时Logo将在舞台中。
Silverlight在开始点与结束点之间处理所有的值改变。Silverlight默认的帧率为60帧每秒,不过Silverlight会在程序运行的过程中根据硬件条件自动切换帧率。Silverlight可以调节这些值来始终保证动画按照预期的效果表现。如果您不得不重新加载web页面来查看动画运行,然后您已经体验了这些表现。再一次,请注意您仅写了一行代码,而其余的代码都是由Blend帮您完成的。
放手一试 - 探索动画选项
我们刚才创建了一个可以运行的动画效果,不过感觉太机械化了,不够自然。有一种方式,我们可以加入一些重力效果在这个动画里面。
在真实世界里,物体掉落在一个硬质表面上都会有一些反弹效果。现在我们就可以利用缓和功能来达到这个效果。请按照以下操作来完成本练习:
1. 回到Expression Blend并且点击下图中箭头指示的下拉菜单:
2. 点击introAnimationStoryboard编辑他的时间线
3. 在0.7秒的地方点击关键帧,出现白色椭圆形图示
4. 您会注意到Properties标签栏会有一个带有带弧度的直线下拉菜单:
5. 点击标识为Bounce的那些选项,如下图所示:
6. 运行工程,可以看到现在Logo掉入舞台后会有一种弹力效果。
7. 您可以调节Bounces或者Bounciness的值。
8. 为了进一步体验这些效果,您可以点击KeySpline,会出现动画的帧率。默认地,初始值之一条直线:
9. 点击播放按钮可以预览该动画。
10. 您可以通过点击并且拖拽来修改这条线或者直接在下面的四个文本框中填入值:
11. 再次预览动画观察变化
12. 点击Hold In按钮:
将两个Silverlight应用程序整合到同一个页面
到目前为止,我们创建了两种不同的Silverlight工程;一个是导航按钮,另外一种是一个带有动画效果的Logo。现在的问题是我们如何将这两个Silverlight应用程序放到一个页面中来达到客户的要求呢?
我们来看看Blend或者Visual Studio自动为我们创建的测试页。
动手实践 - 将Silverlight应用程序整合在一起
我们需要将导航按钮和logo动画整合到一个web页面中。在该练习中,我们将为我们的web页面带来一些新的活力:
1. 在Visual Studio中打开CakeNavigationButtons解决方案。
2. 我们仔细研究一下CakeNavigationButtons.Web工程
3. 除了多出一个名叫ClientBin的文件夹以外,其余的内容跟许多ASP.NET工程一样
4. 双击CakeNavigationButtonsTestPage.html进入页面编辑模式查看如下代码:
<div id="silverlightControlHost">
<object data="data:application/x-silverlight-2,"
type="application/x-silverlight-2" width="100%" height="100%">
<param name="source" value="ClientBin/CakeNavigationButtons.
xap"/>
<param name="onerror" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="4.0.41108.0" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=141205"
style="text-decoration: none;">
<img src="http://go.microsoft.com/fwlink/?LinkId=108181"
alt="Get Microsoft Silverlight" style="border-style: none"/>
</a>
</object>
<iframe style='visibility:hidden;height:0;width:0;border:0px'>
</iframe>
</div>
有经验的web开发人员会很快发现在DIV中有一个插件对象。一般来说,高和宽都设置为100%,这将会填充所有的页面空间。source参数表示在ClientBin文件夹中的CakeNavigationButtons.xap文件。xap包中包含了那些编译过后的Silverlight程序。
minRuntimeVersion参数表示用户至少要有指定的这个版本Silverlight插件才可以查看该内容。还有一个silverlight.js文件,该文件包含了所有插件侦测代码。
5. 让我们回到原始的HTML文档,来看看一些新代码:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Cake-O-Rama</title>
<style type="text/css">
<!--
.Headline {
color: #039;
}
-->
</style>
</head>
<body>
<h1 align="center" class="Headline">Cake-O-Rama</h1>
<p align="left"><a href="#">Home</a></p>
<p align="left"><a href="#">Gallery</a></p>
<p align="left"><a href="#">Order</a></p>
<p align="left"><a href="#">Locations</a></p>
<p align="left"><a href="#">Contact Us</a></p>
<p align="left"><a href="#">Franchise Opportunities</a></p>
</body>
</html>
6. 在与Cake-O-RamaHTML文件同级的文件夹,我们要创建名为ClientBin的文件夹。或者任何您喜欢的名字,不过现在,我们还是严格遵守命名约定吧。
7. 拷贝CakeNavigationButtons.xap和CakeORama_Logo.xap文件到ClientBin文件夹。
8. 修改HTML,body标签部分的代码如下所示:
<div align="center">
<object data="data:application/x-silverlight-2,"
type="application/x-silverlight-2" width="908" height="258">
<param name="source" value="ClientBin/CakeORama_Logo.xap"/>
<param name="background" value="white" />
<param name="minRuntimeVersion" value="4.0.41108.0" />
<param name="autoUpgrade" value="true" />
<H1 align="center" class="Headline">Cake-O-Rama</H1>
<a href="http://go.microsoft.com/fwlink/?LinkID=141205"
style="text-decoration: none;">Get Silverlight</a> to experience
this site's interactive features.
</object>
</div>
<object data="data:application/x-silverlight-2,"
type="application/x-silverlight-2" width="214" height="281">
<param name="source" value="ClientBin/CakeNavigationButtons.
xap"/>
<param name="background" value="white" />
<param name="minRuntimeVersion" value="4.0.41108.0" />
<param name="autoUpgrade" value="true" />
<p align="left"><a href="#">Home</a></p>
<p align="left"><a href="#">Gallery</a></p>
<p align="left"><a href="#">Order</a></p>
<p align="left"><a href="#">Locations</a></p>
<p align="left"><a href="#">Contact Us</a></p>
<p align="left"><a href="#">Franchise Opportunities</a></p>
<a href="http://go.microsoft.com/fwlink/?LinkID=141205"
style="text-decoration: none;">Get Silverlight</a> to experience
this site's interactive features.
</object>
在浏览器中加载这个页面。如果您在本地文件系统运行该页面或收到如下提示的警告信息:
如果您没有安装Silverlight,您会看到如下图所示的页面:
9. 右键点击浏览器上方的安全警告栏选择Allow Blocked Content。
10. 此时页面会重新加载,现在应该看到了吧。
刚刚发生了什么?
我们将两个Silverlight工程整合在了一个页面里面进行展现。通过上述步骤,我们学会了如果将Silverlight嵌入到静态页面。
客户的主页目前只是一个静态页面,不过也可以将Silverlight加入到动态页面中。您只需要记住,Silverlight是客户端技术,后端服务可以运行在任何平台,使用任何技术或语言。
总结
在本章,我们学习了如何使用Expression Blend,容器控件,视觉状态管理,动画和设计人员/开发人员工作流程。我们让老式的页面看上去更加酷炫。最后我们将两个Silverlight应用程序整合到一张页面上。
特别地,我们:
- 使用容器控件
- 使用样式来自定义控件的视觉感受
- 使用Expression Blend
- 使用控件模板修改控件的外观
- 使用视觉状态管理器添加了一点控件上的视觉反馈效果
- 使用Silverlight创建了一个动画效果并且使用缓和关键帧效果使动画看起来更平滑
- 将Silverlight程序嵌入到静态页。
- 声明式语言的威力
我们学会了使用强大的XAML来创建富客户端体验而没有写任何一行逻辑代码。考虑这样一件事情:我们仅写了两行C#代码。大多数工作都通过XAML完成了,而大多数XAML都是通过Blend生成的。