如果把WPF窗体看作一个舞台,那么窗体上的控件就是一个演员,它们的职责就是在用户界面上按照业务逻辑的需要扮演自己的角色。为了让同一种控件能担起不同的角色,程序员就要为它们设计多种多样的外观样式和行为动作,这就是Style。构成Style最重要的两种元素是Setter和Trigger,Setter类帮助我们设置控件的静态外观风格,Trigger则帮助我们设置控件的行为风格。
Setter,设置器。什么的设置器呢?即属性值。我们给属性赋值的时候一般都采用“属性名=属性值”的形式。Setter类的Property属性用来指明你想为属性的哪个属性赋值;Setter类的Value属性则是你提供的属性值。
Trigger, 触发器,即当某些条件满足的时候会触发一个行为(比如某些值的变化或动画的发生等)。触发器比较像事件。事件一般由用户操作触发的,而触发器除了有事件触发型的EventTrigger外还有数据变化触发型的Trigger/DataTrigger及多条件触发型MultiTrigger/MultiDataTrigger等。
1. 基本的Trigger
Trigger是最基本的触发器。类似于Setter,Trigger也有Property和Value两个属性,Property是Trigger关注的属性名称,Value是触发条件。Trigger还有一个Setters属性,此属性是一组Setter,一旦触发条件满足,这组Setter的“属性---值”就会被应用,触发条件不在满足后,各属性值会被还原。
下面这个例子针对的是CheckBox的Style,当CheckBox的IsCheck属性为True时前景色和字体会改变。XAML代码如下:
<Window x:Class="WpfApplication11.wnd11521" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="wnd11521" Height="200" Width="300"> <Window.Resources> <Style TargetType="{x:Type CheckBox}"> <!--当单个条件满足后触发--> <Style.Triggers> <Trigger Property="IsChecked" Value="true"> <Setter Property="FontSize" Value="20"></Setter> <Setter Property="Foreground" Value="Red"></Setter> </Trigger> </Style.Triggers> </Style> </Window.Resources> <StackPanel> <CheckBox Content="悄悄的我走了"></CheckBox> <CheckBox Content="正如我悄悄的来"></CheckBox> <CheckBox Content="我挥挥衣袖"></CheckBox> </StackPanel> </Window>
2. MultiTrigger
MultiTrigger是一个很容易让人误解的名字,会让人以为是多个Trigger集成在一起,实际上叫MultiConditionTrigger更合适,因为必须多个条件同时成立才会被触发。MultiTrigger比Trigger多了一个Conditions属性,需要同时成立的条件就放在这个集合当中。让我们稍微改动一下上面的例子:
<Window x:Class="WpfApplication11.wnd11522" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="wnd11522" Height="200" Width="300"> <Window.Resources> <Style TargetType="{x:Type CheckBox}"> <!--当多个条件满足后触发--> <Style.Triggers> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsChecked" Value="true"></Condition> <Condition Property="Content" Value="正如我悄悄的来"></Condition> </MultiTrigger.Conditions> <Setter Property="FontSize" Value="20"></Setter> <Setter Property="Foreground" Value="Red"></Setter> </MultiTrigger> </Style.Triggers> </Style> </Window.Resources> <StackPanel> <CheckBox Content="悄悄的我走了"></CheckBox> <CheckBox Content="正如我悄悄的来"></CheckBox> <CheckBox Content="我挥挥衣袖"></CheckBox> </StackPanel> </Window>
3. 由数据触发DataTrigger
程序中经常会遇到基于数据执行某些判断情况,遇到这种情况我们就可以考虑使用DataTrigger。DataTrigger对象的Binding属性会把数据源源不断的送出来,一旦送出来的值与Value属性一致,DataTrigger即被触发。下面的例子中,当TextBox的Text长度小于7个字符其Border会保持红色。XAML代码如下:
<Window x:Class="WpfApplication11.wnd11523" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication11" Title="wnd11523" Height="200" Width="300"> <Window.Resources> <local:Len2BoolConvert x:Key="l2b"></local:Len2BoolConvert> <!--数据满足条件后,触发--> <Style TargetType="{x:Type TextBox}"> <Style.Triggers> <DataTrigger Binding="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Text.Length, Converter={StaticResource l2b}}" Value="false"> <Setter Property="BorderBrush" Value="Red"></Setter> <Setter Property="BorderThickness" Value="1"></Setter> </DataTrigger> </Style.Triggers> </Style> </Window.Resources> <StackPanel> <TextBox Margin="5" ></TextBox> <TextBox Margin="5" ></TextBox> </StackPanel> </Window>为了将控件自身作为数据源,我们使用了RelativeSource,初学者经常认为“不明确指出Source的值Binding就会将自己作为数据的来源”,这是错误的,因为不明确指出Source的值Binding就会把控件的DataContext作为自己的数据来源。Binding的Path设置为Text.Length,即我们关注的是字符串的长度。长度是一个具体的数字,如何基于这个长度值来做判断呢?这就用到了Converter。我们创建如下Converter:
/// <summary> /// 数据长度转bool类型 /// </summary> public class Len2BoolConvert:IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { int len = (int)value; return((len > 6)?(true):(false)); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }经过Converter转换以后,长度值就会变为bool类型值。当TextBox的文本长度小于7时DataTrigger会使用自己一组Setter把TextBox的边框设置为红色。运行效果如下图:
EventTrigger是触发器中最特殊的一个。首先,它不是由属性值或者数据的变化来触发而是由事件触发;其次,被触发以后它并非应用一组Setter,而是执行一段动画。因此,UI的动画效果往往和EventTrigger相关联。
在下面这个例子中创建一个针对Button的Style,这个Style包含两个EventTrigger,一个由MouseEnter触发,另一个由MouseLeave触发。XAML代码如下:
<Window x:Class="WpfApplication11.wnd1524" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="wnd1524" Height="300" Width="300"> <Window.Resources> <Style TargetType="{x:Type Button}"> <!--事件触发--> <Style.Triggers> <!--鼠标进入--> <EventTrigger RoutedEvent="MouseEnter"> <BeginStoryboard> <Storyboard> <DoubleAnimation To="150" Duration="0:0:0.2" Storyboard.TargetProperty="Width"/> <DoubleAnimation To="150" Duration="0:0:0.2" Storyboard.TargetProperty="Height"/> </Storyboard> </BeginStoryboard> </EventTrigger> <!--鼠标离开--> <EventTrigger RoutedEvent="MouseLeave"> <BeginStoryboard> <Storyboard> <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="Width"/> <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="Height"/> </Storyboard> </BeginStoryboard> </EventTrigger> </Style.Triggers> </Style> </Window.Resources> <Canvas> <Button Width="50" Height="50"></Button> </Canvas> </Window>
提醒大家一点:虽然在Style里面大量使用触发器,但触发器并非只能应用在Style中-----各种Template也可以拥有自己的触发器,请大家根据需要决定触发器放在Style里面还是Template里面。