WPF中Style样式及其触发器
开发工具与关键技术:WPF C#
作者:黄济民
撰写时间:2021年10月15日
1、Setter,设置器。什么的设置器呢?属性值的。我们给属性赋值的时候一般都采用“属性名=属性值”的形式。Setter类的Property属性用来指明你想为目标的那个属性赋值;Setter类的Value属性则是你提供的属性值。
2、下面的例子中在Window的资源词典中放置一个针对TextBlock的Style,Style中使用若干Setter来设定TextBlock的一些属性,这样程序中的TextBlock就会具有统一的风格,除非你使用{x:Null}显示地清空Style
XAML代码如下:
<Window.Resources>
<!--定义TextBlock样式-->
<Style TargetType="TextBlock">
<Style.Setters>
<!--字体大小:24-->
<Setter Property="FontSize" Value="24"/>
<!--文本装饰:下划线-->
<Setter Property="TextDecorations" Value="Underline"/>
<!--字体样式:斜体-->
<Setter Property="FontStyle" Value="Italic"/>
</Style.Setters>
</Style>
</Window.Resources>
<StackPanel Margin="5">
<TextBlock Text="Hello WPF"/>
<TextBlock Text="This is a sample for Style!"/>
<!--使用{x:Null}显示地清空Style-->
<TextBlock Text="by Tim 2020.06.19" Style="{x:Null}"/>
</StackPanel>
因为Style的内容属性是Setters,所以我们可以直接在
<Style TargetType="TextBlock">
<!--字体大小:24-->
<Setter Property="FontSize" Value="24"/>
<!--文本装饰:下划线-->
<Setter Property="TextDecorations" Value="Underline"/>
<!--字体样式:斜体-->
<Setter Property="FontStyle" Value="Italic"/>
</Style>
3、Style中的Trigger
Trigger,触发器,即当某些条件满足时会触发一个行为(比如某些值的变化或动画的发生等)。触发器比较像事件。事件一般是由用户操作触发的,而触发器除了有事件触发型的EventTrigger外还有数据变化触发型的Trigger/DataTrigger及多条件触发型的MultiTrigger/MultiDataTrigger等。
一、基本Trigger
Trigger类是最基本的触发器。类似于Setter,Trigger也有Property和Value这两个属性,Property是Trigger关注的属性名称,Value是触发条件。Trigger类还有一个Setter属性,此属性值是一组Setter,一旦触发条件被满足,这组Setter的“属性—值”就会被应用,触发条件不在满足后,各属性值会被还原。下面这个例子中包含一个针对CheckBox的Style,当CheckBox的IsChecked属性为true的时候前景色和字体会改变。XAML代码如下:
<Window.Resources>
<!--复选框CheckBox样式-->
<Style TargetType="CheckBox">
<Style.Triggers>
<!--IsChecked选中改变字体大小为20,字体样式为橙色Orange-->
<Trigger Property="IsChecked" Value="true">
<Trigger.Setters>
<Setter Property="FontSize" Value="20"/>
<Setter Property="Foreground" Value="Orange"/>
</Trigger.Setters>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel Margin="5">
<CheckBox Content="悄悄的我走了" Margin="5"/>
<CheckBox Content="正如我悄悄的来" Margin="5,0"/>
<CheckBox Content="我挥一挥衣袖" Margin="5"/>
<CheckBox Content="不带走一片云彩" Margin="5,0"/>
</StackPanel>
因为Triggers不是Style的内容属性,所以 <Style.Triggers>… </Style.Triggers>这层标签
不能省略,但Trigger是Trigger的内容属性,所以<Trigger.Setters>…<Trigger.Setters>这
层标签是可以省略的,以上代码可以简化为:
<Window.Resources>
<Style TargetType="CheckBox">
<Style.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter Property="FontSize" Value="20"/>
<Setter Property="Foreground" Value="Orange"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel Margin="5">
<CheckBox Content="悄悄的我走了" Margin="5"/>
<CheckBox Content="正如我悄悄的来" Margin="5,0"/>
<CheckBox Content="我挥一挥衣袖" Margin="5"/>
<CheckBox Content="不带走一片云彩" Margin="5,0"/>
</StackPanel>
二、MultiTrigger
1、MultiTrigger是个容易让人误解的名字,会让人以为是多个Trigger集成在一起,实际上叫MultiConditionTrigger更合适,因为必须多个条件同时成立时才会被触发。
2、MultiTrigger比Trigger多了一个Conditions属性,需要同时成立的条件就存储在这个集合
中。
3、让我们稍微改动一下上面的例子,要求同时满足CheckBox被选中且Content为“正如
我悄悄的来”时才会被触发。XAML代码如下(仅style部分):
<!--复选框CheckBox样式-->
<Style TargetType="CheckBox">
<Style.Triggers>
<!--多条件触发器-->
<MultiTrigger>
<!--1、条件存储的集合-->
<MultiTrigger.Conditions>
<!--条件:1:复选框选中状态-->
<Condition Property="IsChecked" Value="true"/>
<!--条件2:复选框内容是“正如我悄悄的来”-->
<Condition Property="Content" Value="正如我悄悄的来"/>
</MultiTrigger.Conditions>
<!--2、多个条件同时成立触发-->
<MultiTrigger.Setters>
<!--字体大小:20-->
<Setter Property="FontSize" Value="20"/>
<!--字体颜色:橙色-->
<Setter Property="Foreground" Value="Orange"/>
</MultiTrigger.Setters>
</MultiTrigger>
</Style.Triggers>
</Style>
三、由数据触发的DataTrigger
1、程序中经常会遇到基于数据执行某些判断情况,遇到这种情况时我们可以考虑使用DataTrigger。DataTrigger对象的Binding属性会把数据源源不断送过来,一旦送来的值与Value属性一致,DataTrigger即被触发。
下面例子中,当TextBox的Text长度小于7个字符时其Border会保持红色。XAML代码如下:
<Window.Resources>
<local:L2BConverter x:Key="cvtr"/>
<Style TargetType="TextBox">
<Style.Triggers>
<!--数据触发器:Value="false"-->
<DataTrigger Binding="{Binding RelativeSource={x:Static RelativeSource.Self},Path=Text.Length,Converter={StaticResource cvtr}}" Value="false">
<!--边框:红色-->
<Setter Property="BorderBrush" Value="Red"/>
<!--边框粗度:1-->
<Setter Property="BorderThickness" Value="1"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<TextBox Margin="5"/>
<TextBox Margin="5,0"/>
<TextBox Margin="5"/>
</StackPanel>
2、这个例子中唯一需要解释的就是DataTrigger的Binding。为了将控件自己作为数据源,我们使用RelativeSource,初学者经常认为“不明确指出Source的值Binding就会将控件自己作为数据的来源”,这是错误的,因为不名曲指出Source时Binding会把控件的DataContext属性作为数据源而非把控件自身当作数据源。Binding的Path被设置为Text.Length,即我们关注的是字符串的长度。长度是一个具体的数字,如何基于这个长度值做判断呢?这就用到了Converter。我们创建如下的Converter:
这里新建一个类:L2BConverter 就是这个类
using System;
using System.Globalization;
using System.Windows.Data;
namespace MyWPF
{
public class L2BConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//获取输入值并强制转换
int textLength = (int)value;
//三目运算符:判断数值长度是否>6 ? 是返回true : 否返回false
return textLength > 6 ? true : false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
3、经Converter转换后,长度值会转换成bool类型值。DataTrigger的Value被设置为false,也就是说当TextBox的文本长度小于7时DataTrigger会使用自己的一组Setter把TextBox的边框设置为红色。
4、多数据条件触发的MultiDataTrigger
–有时我们会遇到要求多个数据条件同时满足时才能触发变化的需求,此时可以考虑使用MultiDataTrigger。比如有这样一个需求:用户界面上使用ListBox显示了一列Student数据,当Student对象同时满足ID为2、Name为Tom的时候,条目的高亮显示。
示例的XAML代码如下:
<Window.Resources>
<Style TargetType="ListBoxItem">
<!--使用Style设置 DataTemplate-->
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ID}" Width="60"/>
<TextBlock Text="{Binding Name}" Width="120"/>
<TextBlock Text="{Binding Age}" Width="60"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<!--多数据条件触发的MultiDataTrigger-->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=ID}" Value="2"/>
<Condition Binding="{Binding Path=Name}" Value="Tom"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="Orange"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<!--多数据条件触发的MultiDataTrigger-->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=ID}" Value="4"/>
<Condition Binding="{Binding Path=Age}" Value="21"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="SkyBlue"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<ListBox x:Name="listBoxStudent" Margin="5"/>
</StackPanel>
然后在 Title=“QQ” Height=“400” Width=“400” Loaded=“Window_Loaded”>事件C#如下:
/// <summary>
/// 学生实体
/// </summary>
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public string Age { get; set; }
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
List<Student> sss = new List<Student>() {
new Student() { ID = 1, Name = "Tim", Age="16" },
new Student() { ID = 2, Name = "Tom", Age="18" },
new Student() { ID = 3, Name = "Yue", Age="20" },
new Student() { ID = 4, Name = "Kenny", Age="21" },
new Student() { ID = 5, Name = "Yue", Age="22" },
};
listBoxStudent.ItemsSource = sss;
}
5、由事件触发的EventTrigger
EventTrigger是触发器中最特殊的一个。首先,它不是由属性值或数据的变化来触发而由事件来触发;其次,被触发后它并非应用一组Setter,而是执行一段动画。因此,UI层的动画效果往往与 EventTrigger事件触发,另一个由MouseLeave事件触发。XAML代码如下:
<Window.Resources>
<Style TargetType="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 To="150" Duration="0:0:0.2" Storyboard.TargetProperty="Width"/>
<DoubleAnimation To="150" Duration="0:0:0.2" Storyboard.TargetProperty="Height"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Canvas>
<Button Width="40" Height="40" Content="OK"/>
</Canvas>
注意:虽然在Style中大量使用触发器,但是触发器并非只能应用在Style中——各种Template也可以拥有自己的触发器,请大家根据设计需要决定触发器放在Style中还是Template中。
上一篇: New操作符实现原理
下一篇: WPF通过c#码实现Style样式