说明:本系列基本上是《WPF揭秘》的读书笔记。在结构安排与文章内容上参照《WPF揭秘》的编排,对内容进行了总结并加入一些个人理解。

如果你有Web编程的经验,你会知道使用Style属性给Html元素添加样式,并且更好的做法是将这些样式提取到CSS文件中。在WPF/Silverlight中我们也可以把控件的样式提取出来并进行复用,这就是本节讨论的话题 – 样式支持。

所有外观效果相关的特性,如样式、模板或皮肤等的基础是资源的定义与使用,如果对于资源还不是很熟悉,可参考前文部分章节介绍。

样式由System.Windows.Style类支持,简单的说,其将属性归纳为组,从而使复用这一组属性变得简单。

假如,这里有一个TextBlock,我们来看一下如何将样式提取出来。

 <TextBlock Text="Click!" FontFamily="Comic Sans MS" Foreground="MediumBlue" FontSize="20"></TextBlock>

此时这个TextBlock看起来大概是这样(设计时):

同数据源的定义,我们也把样式定义于<Resource>标签中。定义一个样式第一步是要指定样式的名称及目标对象的类型,TargetType会限制Style应用到特定类型上。对于上面所示的TextBlock,Style定义如下:

 <Style TargetType="TextBlock" x:Key="TextBlockStyle">

我们可以给一种类型定义多种样式。这样可以给控件不同的实例应用不同的样式。具体样式的定义通过<Setter>标签来完成。其中定义你需要设置的属性及其对应的值(本质上<Setter>是用来给依赖属性设置一个值),下面的代码将TextBlock的Text.FontFamily, Foreground和FontSize属性提取到样式中:

 <Style TargetType="TextBlock" x:Key="TextBlockStyle">
<Setter Property="FontFamily" Value="Comic Sans Ms"></Setter>
<Setter Property="Text" Value="Click!"></Setter>
<Setter Property="Foreground" Value="MediumBlue"></Setter>
<Setter Property="FontSize" Value="20"></Setter>
</Style>

样式定义如上所示,要将样式应用到TextBlock,则是通过TextBlock的Style属性来完成。由于样式定义与<Resource>中,我们需要使用{StaticResource}标记扩展,参考如下代码:

 <TextBlock Style="{StaticResource TextBlockStyle}"></TextBlock>

这样只需要设置Style一个属性,就可以达到最初设置4个属性的效果,我们将这行XAML复制三份(放在一个StackPanel中,不然会叠在一起看不出效果),会得到3个样式完全相同的TextBlock:

当然如同Web编程中,我们可以在控件上直接使用属性覆盖Style中的设置。本地值比任何Style中的设置优先级高,这也符合依赖属性一文中描述的依赖属性提供程序优先级的说明。

另外,即使TextBlock位于其他内容控件的内部,也不影响使用Style给它设置样式。甚至后文介绍的模板中的控件,也可以引用Resource中定义的样式。下面的代码展示了我们把刚才定义的样式应用到一个按钮中的TextBlock上:

1 <Button x:Name="btn" Width="60" Height="80">
2 <Button.Content>
3 <StackPanel>
4 <Image Source="icon.jpg"/>
5 <TextBlock Text="Click!" Style="TextBlockStyle"/>
6 </StackPanel>
7 </Button.Content>
8 </Button>
按钮效果如下:

样式的作用域

由于样式定义在各级<Resource>中,如果是<Canvas.Resource>,则样式只能在此<Canvas>范围内使用。如需在应用范围内使用一个样式,可以将样式定义在App.xaml中的<Application.Resource>内。一个定义于<Canvas.Resource>或其它低级别元素中的样式(这对所有资源都适用)可以覆盖<Application.Resource>的样式定义。

样式的高级话题

<Style>中的<Setter>只允许设置与可视特性相关的属性,但这其中也包括一些复杂属性,如下面的设置:

 <Setter Property="Button.RenderTransformOrigin" Value="0.5,0.5"/>
<Setter Property="Button.RenderTransform">
<Setter.Value>
<RotateTransform Angle="36" />
</Setter.Value>
</Setter>

提示:通过使用BasedOn属性,一个Style可以从另一个Style继承。下面示例XAML中的Style在名为buttonStyle样式的基础上添加了Button.FontWight的设置。

 <Style x:Key="buttonStyleWithBold" BasedOn="{StaticResource buttonStyle}">
<Setter Property="Button.FontWeight" Value="Bold"/>
</Style>
 

在不同种类元素间共享样式

如我们有这样一个针对Button定义的样式:

 <Style x:Key="btnStyle">
<Setter Property="Button.FontSize" Value="22"/>
<Setter Property="Button.Background" Value="Azure"/>
<Setter Property="Button.Foreground" Value="Black"/>
<Setter Property="Button.Height" Value="50"/>
<Setter Property="Button.Width" Value="50"/>
<Setter Property="Button.RenderTransformOrigin" Value=".5,.5"/>
<Setter Property="Button.RenderTransform">
<Setter.Value>
<RotateTransform Angle="10"/>
</Setter.Value>
</Setter>
</Style>

Button样式如:

通过将样式中Button.XXX改为Control.XXX我们可以将这个样式应用到其它控件:

 <StackPanel.Resources>
<Style x:Key="controlStyle">
<Setter Property="Control.FontSize" Value="22"/>
<Setter Property="Control.Background" Value="Azure"/>
<Setter Property="Control.Foreground" Value="Black"/>
<Setter Property="Control.Height" Value="50"/>
<Setter Property="Control.Width" Value="50"/>
<Setter Property="Control.RenderTransformOrigin" Value=".5,.5"/>
<Setter Property="Control.RenderTransform">
<Setter.Value>
<RotateTransform Angle="10"/>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources>

我们来看一下将这个样式分别应用到ComboBox, Expander, TabControl等控件的代码与效果:

 <StackPanel Orientation="Horizontal">
<StackPanel.Resources>…略…</StackPanel.Resources>
<Button Style="{StaticResource controlStyle}">1</Button>
<ComboBox Style="{StaticResource controlStyle}">
<ComboBox.Items>2</ComboBox.Items>
</ComboBox>
<Expander Style="{StaticResource controlStyle}" Content="3"/>
<TabControl Style="{StaticResource controlStyle}">
<TabControl.Items>4</TabControl.Items>
</TabControl>
<ToolBar Style="{StaticResource controlStyle}">
<ToolBar.Items>5</ToolBar.Items>
</ToolBar>
<InkCanvas Style="{StaticResource controlStyle}"/>
<TextBox Style="{StaticResource controlStyle}" Text="7"/>
</StackPanel>

如代码,样式应用方式相同,都是给各控件的Style属性应用一个标记扩展。

当给一个元素应用一个样式,如果样式中某个依赖属性在元素中不存在对在的属性,WPF会安全的忽略这个属性,而其他属性会正常设置。这种高级的特性幕后是依赖属性所支持实现的。对于一个控件,其注册了几个专有的依赖属性,同时另一些依赖属性是几个控件共享的。如常见的TextBlock与Button等其他控件共享Foreground属性,InkCanvas与Panel, TextBlock, TextElement及FlowDocument共享Background属性。这样在被共享的样式的<setter>中设置该依赖属性任意一个所有者,这个设置会在所有共享该依赖属性元素上生效。见如下Setter:

 <Setter Property="TextBlock.Foreground" Value="Black"/>

如果我们把这个样式应用到Button上,这个setter也可以设置Button的Foreground。(见上文对依赖属性共享的举例)

所以如果只是在TextBlock与Button之间共享Foreground属性(或其它这两者间共享的依赖属性),则可以不把Button.XXX改为Control.XXX,而是直接使用。

针对上述这些复杂的情况,最好的做法是针对不同的控件定义不同的样式。

提示:

Style自己也提供一个Resources属性,当需要将Style中某个依赖属性的值设置为很复杂的值时,可以将其作为资源定义在<Style.Resources>中。这样可以避免必须将其定义在其他元素的Resources中以致可能出现的资源引用问题。

 

前文我们讲过TargetType的作用,如果尝试把一个Style应用到一个非TargetType类型的控件上会导致一个编译错误。如果TargetType被指定为{x:Type Control},则这个样式可以被应用到任意控件上,当然Style元素指定的依赖属性是否可以应用到目标元素的规则上文有介绍;当给TargetType显示设置了具体类型后,Setter中的依赖属性就不需要在指定具体的元素的名称,如:

 <Setter Property="Button.FontSize" Value="22"/>

可以写为

 <Setter Property="FontSize" Value="22"/>

类型化Style

如果在创建Style时不指定key属性,则将创建一个隐式的Style,其将被作用到所有目标类型的元素上。相对于之前介绍的命名样式,这种隐式Style常被称作类型化样式。

类型化样式的有效范围是由Style所在的<Resources>定义范围决定的,如一个类型化样式被添加在<Application.Resources>中,则它将被应用到整个应用程序中所有目标类型的对象。然而所有目标元素都可以通过命名Style来覆盖类型化样式。(前文讲到的显示设置属性覆盖类型化Style同样有效,甚至可以通过将元素Style设为null来恢复默认样式)

注意:

类型化Style的TargetType完全匹配要应用样式的类型。这表示TargetType的子类不会继承类型化Style。如一个Style的TargetType为ToggleButton,这个类型化样式不会应用给CheckBox等ToggleButton的子类。

在介绍资源时我们提过,<Resources>标签中定义的元素被作为ResourceDictionary的一员。而类型化样式中,我们没有显式设置这个字典对象的key,WPF隐式使用TargetType的值(Type类型,非字符串)作为这个资源的key对象。通过下面的语句可以显示访问类型化Style(这只是为了演示,默认情况下对类型化Style的引用系统会在幕后完成)

 <Button Style="{StaticResource {x:Type Button}}">按钮</Button>

在同一个<Resouces>元素内,对于一个TargetType只能有一个无key的Style,否则按我们上文分析在一个字典中将会出现相同键的对象,当然这是错误的。

提示:

对于FrameworkElement/FrameworkContentElement除了有一个Style属性外,还提供了一个FocusVisualStyle。FocusVisualStyle的Style是元素获得键盘焦点时展示的外观(该属性设置方式与Style一致)。另外对于其他一些控件,还有独有的设置。如ItemsControl提供ItemContainerStyle属性,其中的样式作用于ListBoxItem或ComboxItem等容器的项上,而像ToolBar则提供了ResourceKey属性,其中包含ButtonStyleKey与TextBoxStyleKey等xxxStyleKey属性。这些属性都是只读的,无法直接设置。但我们可以通过重写同key的样式来覆盖默认设置,从而使ToolBar中相应的控件按自定义的外观呈现。

 <Style x:Key="{x:Static ToolBar.ButtonStyleKey}" TargetType="{x:Type Button}" />

触发器

触发器在前面章节有提及,这里将详细介绍。类似<Style>触发器<Trigger>也使用<Setter>来定义。一个样式是无条件应用其中的设置,而触发器则会根据一个或多个条件来执行。在前面章节我们曾提到WPF提供的三种类型的触发器。

  • 属性触发器 – 当依赖属性的值改变时调用。
  • 数据触发器 – 当普通.NET属性的值改变时调用。
  • 事件触发器 – 当路由事件被触发时调用

FrameworkElement,Style,DataTemplate和ControlTemplate通过Triggers集合属性提供对触发器的支持,这其中(对于1.0版本的WPF)Style,DataTemplate和ControlTemplate支持全部3种触发器,而FrameworkElement仅支持事件触发器。对于1.0版本,这影响也不大,因为Style是设置触发器最理想的位置,样式直接与元素的可视部分相关,且可以很方便的共享。

这样我们以样式为例,依次详细介绍三个触发器

  1. 属性触发器

当某个依赖属性有一个特定的值(Trigger中设置的值)时,属性触发器会执行一系列Setter设置,并且在属性失去此特定值时把Setter的设置撤销。以如下XAML为例:

 <Style x:Key="buttonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="RenderTransform">
<Setter.Value>
<RotateTransform Angle="10"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground" Value="Honeydew"/>
</Trigger>
</Style.Triggers>
<Setter Property="FontSize" Value="22"/>
<Setter Property="Background" Value="Azure"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Height" Value="50"/>
<Setter Property="Width" Value="50"/>
<Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
</Style>

当鼠标在按钮之外时:

当鼠标移入按钮范围内后:

当鼠标离开按钮后,按钮样式恢复。

小提示,触发器的<setter>可以覆盖<style>中同名<setter>的设置。

接着,我们看一个更复杂的应用,前面我们学习过数据绑定,在数据无效时我们需要给用户一个友好的通知,我们只需在Validation.HasError属性上设置一个属性触发器:

 <Style x:Key="textboxStyle" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Red" />
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},Path=(Validation.Errors)[0].ErrorContent}"
/>
</Trigger>
</Style.Triggers>
</Style>

这段XAML中值得注意的是在数据绑定中使用RelativeSource从而获取任何应用了这个样式的元素的Validation.Errors属性,接着只需将此样式应用在TextBox上即可在验证失败时获得友好的提示。

  1. 数据触发器

对比属性触发器,数据触发器可以由任何.NET属性触发,而不仅限于依赖属性。(但setter中也是只能设置依赖属性,前文我们也提到<setter>就是用来设置依赖属性的。)为了使用.NET属性,需要通过Binding来指定触发相关属性,而不是普通的属性名。另外数据触发器使用<DataTrigger>定义,而不是<Trigger>。下面看一个例子:

 <Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}"
Value="disabled">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
<Setter Property="Background"
Value="{Binding RelativeSource={RelativeSource Self}, Path=Text}"/>
</Style>

上面代码中在指定数据触发器的触发属性时,我们再次使用了RelativeSource。另外样式中那个设置Background的<setter>是在StringToBrush类型转换器支持下实现的,当这个setter的值无法被转换为相应的Brush时,该TextBox会恢复默认颜色,这是WPF默认的数据绑定错误处理方式。

下列TextBox应用了上述类型化样式:

 <TextBox Margin="3" Text="Azure"/>
<TextBox Margin="3" Text="Green"/>
<TextBox Margin="3" Text="Orange"/>
<TextBox Margin="3" Text="Not a Color"/>
<TextBox Margin="3" Text="Disabled"/>

我们可以看到样式及其中触发器带来的效果:

触发器的组合使用

我们可以通过如下的方式组合使用触发器:

  • 将多个触发器应用到相同的元素上,实现逻辑或的效果。
  • 将多个属性借助一个触发器来判断,实现逻辑与的效果。

逻辑或

下面的例子中,我们在<Style.Triggers>集合中添加了两个触发器,两个触发器中的Setter相同,这样当至少有一个触发器满足条件时,触发器中Setter就可生效。

 1 <Style.Triggers>
2 <Trigger Property="IsMouseOver" Value="True">
3 <Setter Property="RenderTransform">
4 <Setter.Value>
5 <RotateTransform Angle="10"/>
6 </Setter.Value>
7 </Setter>
8 <Setter Property="Foreground" Value="Black"/>
9 </Trigger>
10 <Trigger Property="IsFocused" Value="True">
11 <Setter Property="RenderTransform">
12 <Setter.Value>
13 <RotateTransform Angle="10"/>
14 </Setter.Value>
15 </Setter>
16 <Setter Property="Foreground" Value="Black"/>
17 </Trigger>
18 </Style.Triggers>

提示:在单个或多个触发器(多个触发器同时处于激活状态)中如果有多个针对同一属性而值不同的setter – 即Setter冲突,这时最后一个setter会生效。

 

逻辑与

通过使用MultiTrigger(针对属性触发器)或MultiDataTrigger(针对数据触发器),可以实现逻辑与,这两个特殊的Trigger都提供一个Conditions集合属性,用于设置多个触发条件,参考代码(MultiTrigger为例):

 <Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
<Condition Property="IsFocused" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="RenderTransform">
<Setter.Value>
<RotateTransform Angle="10"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground" Value="Black"/>
</MultiTrigger>
</Style.Triggers>

当<conditions>中两个条件都满足时,将会应用<setter>中的效果,另外MultiDataTrigger在支持普通.NET属性的同时也支持MultiTrigger支持的依赖属性触发条件。

前文提到的通过IsMouseEnter属性作为触发条件的触发器,也可以通过EventSetter以事件驱动的方式来实现,如这段XAML:

 <Style x:Key="btnStyle" TargetType="{x:Type Button}">
<Setter Property="FontSize" Value="22"/>
<EventSetter Event="MouseEnter" Handler="Button_MouseEnter" />
</Style>

这需要一个程序代码实现事件处理函数。

本文完

参考:

《WPF揭秘》

WPF,Silverlight与XAML读书笔记第四十四 - 外观效果之样式的更多相关文章

  1. WPF,Silverlight与XAML读书笔记第三十九 - 可视化效果之3D图形

    原文:WPF,Silverlight与XAML读书笔记第三十九 - 可视化效果之3D图形 说明:本系列基本上是<WPF揭秘>的读书笔记.在结构安排与文章内容上参照<WPF揭秘> ...

  2. WPF,Silverlight与XAML读书笔记(3) - 标记扩展

    hystar的.Net世界 博客园 首页 新闻 新随笔 联系 管理 订阅 随笔- 103  文章- 0  评论- 107  WPF,Silverlight与XAML读书笔记(3) - 标记扩展   说 ...

  3. WPF,Silverlight与XAML读书笔记第四十七 - Silverlight与浏览器

    说明:本系列基本上是<WPF揭秘>的读书笔记.在结构安排与文章内容上参照<WPF揭秘>的编排,对内容进行了总结并加入一些个人理解. 这部分内容主要介绍Silverlight与浏 ...

  4. WPF,Silverlight与XAML读书笔记第四十三 - 多媒体支持之文本与文档

    说明:本系列基本上是<WPF揭秘>的读书笔记.在结构安排与文章内容上参照<WPF揭秘>的编排,对内容进行了总结并加入一些个人理解. Glyphs对象(WPF,Silverlig ...

  5. WPF,Silverlight与XAML读书笔记第四十八 - Silverlight网络与通讯

    说明:本系列基本上是<WPF揭秘>的读书笔记.在结构安排与文章内容上参照<WPF揭秘>的编排,对内容进行了总结并加入一些个人理解. 这一部分我们重点讨论下Silverlight ...

  6. WPF,Silverlight与XAML读书笔记第四十五 - 外观效果之模板

    说明:本系列基本上是<WPF揭秘>的读书笔记.在结构安排与文章内容上参照<WPF揭秘>的编排,对内容进行了总结并加入一些个人理解. 模板允许用任何东西完全替换一个元素的可视树, ...

  7. WPF,Silverlight与XAML读书笔记第四十六 - 外观效果之三皮肤与主题

    说明:本系列基本上是<WPF揭秘>的读书笔记.在结构安排与文章内容上参照<WPF揭秘>的编排,对内容进行了总结并加入一些个人理解. 皮肤 皮肤是应用程序中样式与模板的集合,可以 ...

  8. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十四章:曲面细分阶段

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十四章:曲面细分阶段 代码工程地址: https://github. ...

  9. OpenCV开发笔记(六十四):红胖子8分钟带你深入了解SURF特征点(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

随机推荐

  1. Flask备注三(Context)

    Flask备注三(Context) Flask支持不同的应用场景下,对应不同的local context(本地上下文环境),用来提供当前环境下的资源.lcoal context和全局变量以及局部变量最 ...

  2. Oracle DBA常用查询

    Oracle DBA常用查询 –1. 查询系统所有对象select owner, object_name, object_type, created, last_ddl_time, timestamp ...

  3. android开发学习笔记001a

    Android 应用与开发环境 1.使用SDK版本:Android 2.3 . 2.发展和历史 创始人:Andy Rubin,Android公司被Google收购.07年11月5日1.0发布. 3.平 ...

  4. QQ邮箱发送邮件,出现mail from address must be same as authorization user错误

    之前做的一个系统,有个发送邮件的功能,一直能正常使用,今天同事说QQ邮箱发送不了. 立马着手调试,发现服务器一直出现“mail from address must be same as authori ...

  5. Three ways to set specific DeviceFamily XAML Views in UWP

    Three ways to set specific DeviceFamily XAML Views in UWP http://igrali.com/2015/08/02/three-ways-to ...

  6. sql操作

    SQL Server 存储过程 Transact-SQL中的存储过程,非常类似于Java语言中的方法,它可以重复调用.当存储过程执行一次后,可以将语句缓存中,这样下次执行的时候直接使用缓存中的语句.这 ...

  7. vim的跨文件复制粘贴

    1.用vim打开一个文件,例如:a.cpp 2.在普通模式下,输入:":sp"(不含引号)横向切分一个窗口,或者":vsp"纵向切分一个窗口,敲入命令后,你将看 ...

  8. C# 部分语法总结(入门经典)

    class Program { static void Main(string[] args) { init(); System.Console.ReadKey(); } #region 接口 /// ...

  9. H5、CSS3属性的支持性以及flex

    一.项目中用到一个flex属性,但是应用了flex的父容器只设置了width,没有设置height,此时每一个应用了上面提到的属性的样式的div都重叠在了一起,在IE10,IE11出问题,IE9没有问 ...

  10. Asp.net使用代码修改配置文件的节点值

    使用代码修改配置文件的方法: 1.打开配置文件写入的权限 2.先按节点名称长到要修改的节点,然后删除,紧接着将有新值的节点添加回去 3.关闭配置文件写入的权限 修改Appsetting节点的值,修改其 ...