WPF的样式–style直译过来就是“风格”,“样式”。如果把WPF窗体看做一个舞台,那么窗体上的控件就是一个个演员,它们的职责就是在用户界面上按照业务逻辑的需要扮演自己的角色。为了让同一种控件能担当起不同的角色,程序员就要为它们设计多种外观样式和行为动作,这就是Style。构成Style最重要的两种元素是Setter和Trigger,Setter类帮助我们设置控件的静态外观风格,Trigger类则帮助我们设置控件的行为风格。
Style中的触发器
Setter,设置器。什么的设置器呢?属性值的。我们给属性赋值的时候一般都采用“属性名=属性值”的形式。Setter类的Property属性用来指明你想为目标的那个属性赋值; Setter类的Value属性则是你提供的属性值。
下面的例子中在Window的资源词典中放置一个针对TextBlock的Style,Style中使用若干
Setter来设定TextBlock的一些属性,这样程序中的TextBlock就会具有统一的风格,除非
你使用{x:Null}显示地清空Style。
XAML代码如下:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="450" Width="800">
<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>
</Window>;
因为Style的内容属性是Setters,所以我们可以直接在
<Style TargetType="TextBlock">
<!--字体大小:24-->
<Setter Property="FontSize" Value="24"/>
<!--文本装饰:下划线-->
<Setter Property="TextDecorations" Value="Underline"/>
<!--字体样式:斜体-->
<Setter Property="FontStyle" Value="Italic"/>
</Style>
Style中的Trigger
Trigger,触发器,即当某些条件满足时会触发一个行为(比如某些值的变化或动画的
发生等)。触发器比较像事件。事件一般是由用户操作触发的,而触发器除了有事件触
发型的EventTrigger外还有数据变化触发型的Trigger/DataTrigger及多条件触发型的
MultiTrigger/MultiDataTrigger等。
触发器会有以下几种的Trigger:基本Trigger、MultiTrigger、由数据触发的DataTrigger、多数据条件触发的MultiTrigger、由事件触发的EventTrigger。
第一种,基本触发器Trigger
Trigger类是最基本的触发器。类似于Setter,Trigger也有Property和Value这两个属
性,Property是Trigger关注的属性名称,Value是触发条件。Trigger类还有一个Setter属
性,此属性值是一组Setter,一旦触发条件被满足,这组Setter的“属性—值”就会被应用,
触发条件不在满足后,各属性值会被还原。
下面这个例子中包含一个针对CheckBox的Style,当CheckBox的IsChecked属性为true
的时候前景色和字体会改变。XAML代码如下:
<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
MultiTrigger是个容易让人误解的名字,会让人以为是多个Trigger集成在一起,实际
上叫MultiConditionTrigger更合适,因为必须多个条件同时成立时才会被触发。
MultiTrigger比Trigger多了一个Conditions属性,需要同时成立的条件就存储在这个集合
中。
让我们稍微改动一下上面的例子,要求同时满足CheckBox被选中且Content为“正如
我悄悄的来”时才会被触发。XAML代码如下(仅style部分):
<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
程序中经常会遇到基于数据执行某些判断情况,遇到这种情况时我们可以考虑使用
DataTrigger。DataTrigger对象的Binding属性会把数据源源不断送过来,一旦送来的值与
Value属性一致,DataTrigger即被触发。
下面例子中,当TextBox的Text长度小于7个字符时其Border会保持红色。XAML代码
如下:
<!--复选框CheckBox样式-->
<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>
</Window>
这个例子中唯一需要解释的就是DataTrigger的Binding。为了将控件自己作为数据源,我
们使用了RelativeSource,初学者经常认为“不明确指出Source的值Binding就会将控件自
己作为数据的来源”,这是错误的,因为不名曲指出Source时Binding会把控件的
DataContext属性作为数据源而非把控件自身当作数据源。Binding的Path被设置为
Text.Length,即我们关注的是字符串的长度。长度是一个具体的数字,如何基于这个长度
值做判断呢?这就用到了Converter。我们创建如下的Converter:
using System;
using System.Globalization;
using System.Windows.Data;
namespace Converter
{
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();
}
}
}
经Converter转换后,长度值会转换成bool类型值。DataTrigger的Value被设置为false,
也就是说当TextBox的文本长度小于7时DataTrigger会使用自己的一组Setter把TextBox的边框设置为红色。运行效果如图所示:
第四种,多数据条件触发的MultiDataTrigger
有时我们会遇到要求多个数据条件同时满足时才能触发变化的需求,此时可以考虑使
用MultiDataTrigger。比如有这样一个需求:用户界面上使用ListBox显示了一列Student
数据,当Student对象同时满足ID为2、Name为Tom的时候,条目的高亮显示。
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MultiDataTrigger" Height="146" Width="300"
Loaded="Window_Loaded">
<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>
</Window>
示例的C#代码部分包括声明带有ID、Name、Age属性的Student 类和将一个
List实例赋值给ListBox的ItemsSource属性
using System.Collections.Generic;
using System.Windows;
namespace WpfApplication1
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
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;
}
}
}
程序运行效果如下:
第五种,由事件触发的EventTrigger
EventTrigger是触发器中最特殊的一个。首先,它不是由属性值或数据的变化来触发
而由事件来触发;其次,被触发后它并非应用一组Setter,而是执行一段动画。因此,UI
层的动画效果往往与 EventTrigger事件触发,另一个由MouseLeave事件触发。XAML代
码如下:
<Window x:Class="测试后面删除.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:测试后面删除"
Title="Window1" Height="240" Width="240">
<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>
</Window>
无需要任何C#代码,我们就获得了效果(界面上一些可展开/收拢的工具箱或菜单就是使
用这种EventTrigger制作的)
至此各种触发器就介绍完了,提醒大家注意一点:虽然在Style中大量使用触发器,但是
触发器并非只能应用在Style中——各种Template也可以拥有自己的触发器,请大家根据
设计需要决定触发器放在Style中还是Template中