样式与模板[翻译]
WPF的样式与模板涉及到了一套功能(styles, templates, triggers 和 storyboards)允许开发者和设计者创建吸引视觉的效果,并且为产品创建一致的界面。尽管开发者和设计者可以基于application-by-application广泛的自定义界面。为了允许维持和共享应用程序的表面,需要一个强的样式和模板模型。WPF提供了这个模型。
WPF样式模型的另一个特征是表现层和逻辑层的分离。这就意味着设计师可以仅使用XAML完成应用程序的界面,与此同时,开发人员可以使用C#或者VB完成程序的逻辑。
这篇概述主要集中在应用程序的样式和模板,并没有讨论数据绑定的概念。
另外,理解资源是很重要的,资源使得样式和模板能够重复使用。
这个话题包含了如下几个部分:
Styling and Templating Sample
Style basics
Data Templates
Control Templates
Triggers
Shared Resources and Themes
Related Topics
Styling and Templating Sample
概述中使用的代码例子是基于一些简单的照片,如下所示:
这个简单的图片例子使用了样式和模板创造了一个引人入胜用户体验。这个例子有两个Textblock元素和一个绑定到一组图片的ListBox控件。
Style basics
你可以将Style视为一种方便的方法应用到一组属性值而不仅是一个元素,考虑如下的TextBlock元素和他们默认的界面。
<TextBlock>My Pictures</TextBlock>
<TextBlock>Check out my new pictures!</TextBlock>
你可以通过直接设置TextBox的属性改变默认的界面,例如FontSize和FontFamily,然而,如果你想你的Textblock元素共享一些属性,你可以在XAML文件资源段里创建样式,正如这里所示:
<Window.Resources>
<Style Target=”TextBlock”>
<Setter Property=”HorizontalAlignment” Value=”Center”/>
<Setter Property=”FontFamily” Value=”Comic Sans MS”/>
<Setter Property=”FontSize” Value=”14”/>
</Style>
</Window.Resources>
当你设置样式的TargetType为TextBlock类型时,样式将被应用到所有的窗口TextBlock元素。
TextBlock元素的界面如下所示:
样式扩展
也许你想要两个TextBlock元素共享同一种属性值值,例如FontFamily和HorizontalAlignment,但是同样希望“My Picture”有些附加的属性。你可以基于第一个样式来创建一个新的样式,如下所示:
<Window.Resources>
<Style BasedOn=”{StaticResource {x:Type TextBlock}}”
TargetType=”TextBlock”
X: Key=”TitleText”>
<Setter Property=”FontSize” Value=”26”/>
<Setter property=”Foreground”>
<Setter.Value>
<LinearGradientBrush StartPoint=”0.5,0” EndPoint=”0.5,1”>
<LinearGradientBrush.GradientStops>
<GradientStop Offset=”0.0” Color=”#90DDDD”/>
<GradientStop Offset=”1.0” Color=”#5BFFFF”/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Window.Resources>
注意到上面的样式给出了x:Key。为了应用这个样式,你设置样式的属性到你的TextBlock上需要x:Key值,如下所示:
<TextBlock Style=”{StaticResource TitleText}” Name=”textblock1”>My Pictures</TextBlock>
<TextBlock>Check out my pictures!</TextBlock>
TextBlock 样式现在有HorizontalAlignment为中间,FontFamily的值为Comic Sans MS,FontSize的值为26。Foreground的值为LinearGradientBrush,如下例所示。注意到它重载了父类型的FontSize。如果在Style里有超过一组的Setter,最后声明的Setter优先。
如下显示了TextBlock元素:
TitleText样式扩展了用于TextBlock的样式。你同样可以扩展样式具有x:Key,通过使用x:Key值。
TargetType属性和x:Key属性的关系
正如第一个例子所示,设置TargetType属性为TextBlock,而没有给样式分配x:Key时将会导致样式被应用到所有的TextBlock元素。在这个例子中,x:Key隐式的设置为{x:Type TextBlock},这也就意味着如果你显示的设置x:Key值为别的类型,而不是{x:Type TextBlock}, Style将不会应用到所有的TextBlock元素。也就是,你必须应用将样式应用到TextBlock上。如果资源中的样式没有设置TargetType,你必须提供一个x:Key。
为了给x:Key提供一个默认值,TargetType属性指定了设置的属性将应用到哪些类型。如果你没有规定一个TargetType,你必须在你的Setter对象里利用类名设置属性,例如Property = ”ClassName.Property”。例如,不是设置Property = “FontSize”,而是设置Property为 “ TextBlock.FontSize ” 或者 ”Control.FontSize”。
同样注意到许多WPF控件是由WPF的控件组合而成。如果你想创建一个样式应用到所有类型的控件,你可能得到一个意想不到的结果。例如,如果你创建了样式的目标为窗口的Textblock类型,样式将被应用到所有的TextBlock控件,尽管TextBlock是另外一个控件的一部分,例如ListBox。
样式和资源
你可以在任何一个继承于FrameworkElement和FrameworkContentElement 元素上使用样式。最常用的方法是在XAML文件里的Resources段声明一个样式作为资源,正如上面的例子所示。因为样式是资源,所以需要准守所有应用在资源上的作用域规则;样式在哪里声明影响了样式在哪里使用。例如,如果你在应用程序的根元素的XAML里声明样式,样式可以应用到应用程序的任何一个地方。如果你创建了导航应用程序,并且声明了样式是应用程序XAML文件里的一部分,样式只能在XAML文件里使用。
编程的方式设置样式
为了用编程的方式给元素指定样式名称,从资源集里获得样式,并且把它分配到元素样式属性。注意到资源集里的项目是对象类型。因此,你必须在把它分配到样式属性之前,检索到样式风格。例如,设置定义了的TitleText样式到名字为textblock1的TextBlock。做如下所示:
Textblock1.Style = (Style)(this.Resources[“TitleText”]);
注意到一旦应用了样式,它将被封装并且不能改变。如果你想动态的改变一个已经应用了的样式。你必须去创建一个新的样式去代替一个现存的。
你可以创建一个对象,基于自定义的逻辑选择一种样式来应用,例如,样式选择器。
Bindings, Dynamic Resources, 和事件处理器
注意到你可以使用Setter.Value属性去规定一个绑定标记扩展和一个动态资源标记扩展。
到目前为止,这个
概述只讨论了使用setters设置属性值。你也可以规定事件处理程序的样式。
Data Templates
在这个案例中,有一个ListBox控件与一组照片绑定。
<ListBox ItemSource=”{Binding Source={StaticResource MyPhotos}}”
Background=”Silver” Width=”600” Margin=”10” SelectedIndex=”0”/>
目前的ListBox如下所示:
大部分控件都有一些内容,这些内容经常是与数据进行绑定的。在这个例子中,数据是一组照片。在WPF里,你使用DataTemplate去定义数据的可视化表示。基本上,你把什么放在数据模板里决定了那些数据在应用程序上呈现。
在我们的应用程序例子中,每个自定义的图片对象有一个Source属性来指定图片的路径。目前,图片对象仅出现在文件路径里。
<Window.Resources>
<DataTemplate DataType=”{x:Type local:Photo}”
<Border margin=”3”>
<Image Source=”{Binding Source}” />
</Border>
</DataTemplate>
</Window.Resources>
注意到DataType属性与样式的TargetType属性很相似。如果你的数据模板在资源段,当你规定DataType属性为一种类型,而不给它分配x:Key时,DataTemplate将被应用到任何时候该类型出现。你同样可以选择使用x:Key来分配DataTemplate,然后将它设置为StaticResource用于数据模板的属性,例如ItemTemplate 属性和ContentTemplate属性。
本质上讲,上例的数据模板定义了只要是一个Photo对象,它都应该以一个图片呈现在Border里。使用这个DataTemplate,我们的应用程序如下所示:
数据模板也提供了其他的特征。例如,如果你呈现的数据集包含了别的集,这些集使用了HeaderedItemsControl类型,例如Menu或者TreeView,这里有一种HierarchicalDataTemplate。另外的数据模板特征是DataTemplateSelector,允许你基于自定义的逻辑选择数据模板。
Control Templates
在WPF,控件的控件模板定义了控件的界面。你可以通过为控件定义一个新的ControlTemplate来改变结构和界面。在许多情况下,这给了你足够的灵活性,所以你可以写自己的自定义控件。
Triggers
一个触发设置属性和一个起始操作,例如动画,当一个属性值改变或者抛出一个事件的时候。样式,控件模板和数据模板都有Triggers属性来设置触发器。有各种类型的触发器。
属性触发器
Trigger是设置属性值,或者基于某个属性值来开始行动,称之为属性触发器。
为了声明怎样使用属性触发器,你可以使ListBoxItem部分透明,除非它被选择。如下的样式设置了ListBoxItem的Opacity值为0.5.当IsSelected属性值为真时,Opacity设置为1.0.
<Style TargetType=”ListBoxItem”>
<Setter Property=”Opacity” Value=”0.5” />
<Setter Property=”MaxHeight” Value=”75” />
<Style.Triggers>
<Trigger Property=”IsSelected” value=”True”>
<Setter Property=”Opacity” value=”1.0” />
</Trigger>
</Style.Triggers>
<Style>
这个例子使用了Trigger去设置属性值,但是注意到Trigger类也有EnterActions和ExitActions属性,来使触发执行操作。
注意到ListBoxItem的MaxHeight属性设置为了75.如下图所示,第三项是被选择的。
EventTriggers and Storyboards
另一种类型的触发器是EventTrigger,开始了基于一个事件引发的一组操作,如下的EventTrigger对象规定了,当鼠标进入了ListBoxItem,经过0.2s MaxHeight变为了90.当鼠标从选项上离开时,属性在1S内回到原来的值。注意到没有必要指定MouseLeave时的值,这是因为动画能够跟踪原始值。
<EventTrigger RoutedEvent=”Mouse.MouseEnter”>
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Duration=”0:0:0.2”
Storyboard.TargetProperty=”MaxHeight”
To=”90” />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent=”Mouse.MouseLeave”>
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Duration=”0:0:1”
Storyboard.TargetProperty=”MaxHeight” />
</Storyboard>
<BeginStoryboard>
<EventTrigger.Actions>
</EventTrigger>
如下图所示,鼠标指向了第三项:
MultiTriggers, DataTriggers, MultiDtaTriggers
除了Trigger与EventTrigger外,还有一些其他类型的触发器。MultiTrigger允许你根据多个条件设置属性值。当你的属性是数据绑定时,使用DataTrigger和MultiDataTrigger。
Shared Resources and Themes
一个典型的WPF程序可能有几个用户界面资源贯穿了整个应用程序。共同的,这些资源集可以考虑作为应用程序的主题。WPF支持将UI资源包装成主题,通过资源字典封装成ResouceDictionary类。
WPF主题是通过样式和模板机制进行定义的,WPF公开了任何元素的视觉效果。
WPF主题资源存储在嵌入的资源字典里。这些资源字典必须嵌入到一个签名的程序集,也可以作为代码嵌入到一个相同的程序集,或者在一个side-by-side程序集。在PresentationFramewoek.dll情况下,包含WPF控件,主题资源的程序集是一些列的side-by-side 程序集。
当寻找一个元素的样式时,主题成为了最后一个地方。典型的,搜索将从元素树开始寻找一个适当的资源,然后查看应用程序资源集,在最终查询系统。这给了应用程序开发员一个机会给树的任何对象和应用程序级别抵达主题前,重新定义样式。
你可以定义资源字典作为单独的文件,能够使你在多个应用程序里使用主题。你同样可以通过定义多个资源字典创建热插拔的主题,提供了具有不同值得同一个资源。在应用程序级别呈现这些样式或者资源是应用程序皮肤的推荐方法。
为了共享这些资源,包含了样式和模板,交叉应用,你可以创建一个XAML文件,并且定义资源字典。例如,查看如下的说明,显示了Styling with ControlTemplate Sample的一部分。
如果你看到了例子里的XAML文件,你必须注意到文件里都包含了这句话:
<ResourceDictionary.MergedDictionaries>
< ResourceDictionary Source=”Shared.xaml” />
<ResourceDictionary.MergedDictionaries>
这是共享了shared.xaml,定义了资源字典,包含了一组样式和画刷资源,使得控件有一致性的外表。
参考:http://msdn.microsoft.com/en-us/library/ms745683(VS.100).aspx