• WPF,Silverlight与XAML读书笔记第四十四


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

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

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

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

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

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

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

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

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

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

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

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

    1 <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>只允许设置与可视特性相关的属性,但这其中也包括一些复杂属性,如下面的设置:

    1 <Setter Property="Button.RenderTransformOrigin" Value="0.5,0.5"/>
    2 <Setter Property="Button.RenderTransform">
    3     <Setter.Value>
    4         <RotateTransform Angle="36" />
    5     </Setter.Value>
    6 </Setter>

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

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

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

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

     1 <Style x:Key="btnStyle">
     2     <Setter Property="Button.FontSize" Value="22"/>
     3     <Setter Property="Button.Background" Value="Azure"/>
     4     <Setter Property="Button.Foreground" Value="Black"/>
     5     <Setter Property="Button.Height" Value="50"/>
     6     <Setter Property="Button.Width" Value="50"/>
     7     <Setter Property="Button.RenderTransformOrigin" Value=".5,.5"/>
     8     <Setter Property="Button.RenderTransform">
     9         <Setter.Value>
    10             <RotateTransform Angle="10"/>
    11         </Setter.Value>
    12     </Setter>
    13 </Style>

    Button样式如:

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

     1 <StackPanel.Resources>
     2     <Style x:Key="controlStyle">
     3         <Setter Property="Control.FontSize" Value="22"/>
     4         <Setter Property="Control.Background" Value="Azure"/>
     5         <Setter Property="Control.Foreground" Value="Black"/>
     6         <Setter Property="Control.Height" Value="50"/>
     7         <Setter Property="Control.Width" Value="50"/>
     8         <Setter Property="Control.RenderTransformOrigin" Value=".5,.5"/>
     9         <Setter Property="Control.RenderTransform">
    10             <Setter.Value>
    11                 <RotateTransform Angle="10"/>
    12             </Setter.Value>
    13         </Setter>
    14     </Style>
    15 </StackPanel.Resources>

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

     1 <StackPanel Orientation="Horizontal">
     2     <StackPanel.Resources>…略…</StackPanel.Resources>
     3     <Button Style="{StaticResource controlStyle}">1</Button>
     4     <ComboBox Style="{StaticResource controlStyle}">
     5         <ComboBox.Items>2</ComboBox.Items>
     6     </ComboBox>
     7     <Expander Style="{StaticResource controlStyle}" Content="3"/>
     8     <TabControl Style="{StaticResource controlStyle}">
     9         <TabControl.Items>4</TabControl.Items>
    10     </TabControl>
    11     <ToolBar Style="{StaticResource controlStyle}">
    12         <ToolBar.Items>5</ToolBar.Items>
    13     </ToolBar>
    14     <InkCanvas Style="{StaticResource controlStyle}"/>
    15     <TextBox Style="{StaticResource controlStyle}" Text="7"/>
    16 </StackPanel>

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

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

    1 <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中的依赖属性就不需要在指定具体的元素的名称,如:

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

    可以写为

    1 <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的引用系统会在幕后完成)

    1 <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中相应的控件按自定义的外观呈现。

    1 <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为例:

     1 <Style x:Key="buttonStyle" TargetType="{x:Type Button}">
     2     <Style.Triggers>
     3         <Trigger Property="IsMouseOver" Value="True">
     4             <Setter Property="RenderTransform">
     5                 <Setter.Value>
     6                     <RotateTransform Angle="10"/>
     7                 </Setter.Value>
     8             </Setter>
     9             <Setter Property="Foreground" Value="Honeydew"/>
    10         </Trigger>
    11     </Style.Triggers>
    12     <Setter Property="FontSize" Value="22"/>
    13     <Setter Property="Background" Value="Azure"/>
    14     <Setter Property="Foreground" Value="Black"/>
    15     <Setter Property="Height" Value="50"/>
    16     <Setter Property="Width" Value="50"/>
    17     <Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
    18 </Style>

    当鼠标在按钮之外时:

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

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

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

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

     1 <Style x:Key="textboxStyle" TargetType="{x:Type TextBox}">
     2     <Style.Triggers>
     3         <Trigger Property="Validation.HasError" Value="True">
     4             <Setter Property="Background" Value="Red" />
     5             <Setter Property="ToolTip" 
     6         Value="{Binding RelativeSource={RelativeSource Self},Path=(Validation.Errors)[0].ErrorContent}"
     7         />
     8         </Trigger>
     9     </Style.Triggers>
    10 </Style>

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

    1. 数据触发器

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

     1 <Style TargetType="{x:Type TextBox}">
     2     <Style.Triggers>
     3         <DataTrigger
     4             Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}"
     5             Value="disabled">
     6             <Setter Property="IsEnabled" Value="False"/>
     7         </DataTrigger>
     8     </Style.Triggers>
     9     <Setter Property="Background"
    10             Value="{Binding RelativeSource={RelativeSource Self}, Path=Text}"/>
    11 </Style>

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

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

    1 <TextBox Margin="3" Text="Azure"/>
    2 <TextBox Margin="3" Text="Green"/>
    3 <TextBox Margin="3" Text="Orange"/>
    4 <TextBox Margin="3" Text="Not a Color"/>
    5 <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为例):

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

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

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

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

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

    本文完

    参考:

    《WPF揭秘》

  • 相关阅读:
    ♫【网站优化】
    ☀【html】锚点
    ↗☻【PHP与MySQL动态网站开发(第4版本) #BOOK#】第1章 PHP概述
    _#【jQuery插件】Carousel 传送带
    _#【jQuery插件】Autocomplete 自动补全
    【兼容】ie6 hover
    【兼容】ie6/ie7 overflow:hidden;失效
    Online Mono for Android training now available in Spanish
    MonoDevelop 3.0.4 发布啦!
    猴子选大王的四种VB解法
  • 原文地址:https://www.cnblogs.com/lsxqw2004/p/4632265.html
Copyright © 2020-2023  润新知