1.Style
Style就是一堆Setter/Trigger.
WPF元素可以通过元素的Style属性设置(Style属性在FrameworkElement中定义)使用样式。
Setter是Style中最重要的属性,Setter只支持依赖属性;
某些依赖属性无法使用简单的字符串设置值,这时使用嵌套元素的方法实现,比如ImageBrush。
<Window.Resources>
<Style x:Key="bigFontButtonStyle">
<Setter Property="Button.FontFamily" Value="Times New Roman"/>
<Setter Property="Button.FontSize" Value="18"/>
<Setter Property="Button.FontWeight" Value="Bold"/>
<Setter Property="Button.Foreground" Value="Red"/>
<Setter Property="Button.Background">
<Setter.Value>
<ImageBrush TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 32 32"
ImageSource="/images/open32.png"
Opacity="0.3"/>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Button Content="button" Style="{StaticResource bigFontButtonStyle}"/>
希望在多个Style中共用的画刷等资源,可以定义为资源,然后在各个样式中使用.
1)新建一个目录Resources
2)添加 Resource Dictionary
3)在Resource Dictionary xaml文件中定义资源
4)在需要使用它的窗口中使用
<!--定义了一个用于共享的image brush, MyRes.xaml-->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp5">
<ImageBrush x:Key="MyImgBrush" TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 32 32"
ImageSource="/images/open32.png"
Opacity="0.3"/>
</ResourceDictionary>
<!-- 合并资源 -->
<!--指定程序集,并使用component关键字
<ResourceDictionary Source="/CustomControls;component/themes/ColorPicker.xaml"/>-->
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/MyRes.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<!-- 使用 -->
<Button Content="tuple" Background="{StaticResource MyImgBrush}" Height="50"/>
<!--Resource Dictionary内部也可以使用资源合并-->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp5">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Resources/MyRes.xaml"/>
</ResourceDictionary.MergedDictionaries>
<ControlTemplate x:Key="customButtonTemplate" TargetType="{x:Type Button}">
<Border Name="Border" BorderThickness="2" CornerRadius="2"
Background="{StaticResource MyImgBrush}"
BorderBrush="{TemplateBinding BorderBrush}">
<Grid>
<Rectangle Name="FocusCue" Visibility="Hidden" Stroke="Red"
StrokeThickness="1" StrokeDashArray="1 2"
SnapsToDevicePixels="True"/>
<ContentPresenter Margin="{TemplateBinding Padding}"
RecognizesAccessKey="True"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter TargetName="FocusCue" Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ResourceDictionary>
用Setter设置属性时,需要指定类和属性名,类名不一定非得是定义该属性得类名,也可以是派生类的类名;
如果要设置的属性是针对某个特定的类型的,那么可以使用TargetType
<Style x:Key="bigFontButtonStyle" TargetType="Button">
<Setter Property="FontFamily" Value="Times New Roman"/>
<Setter Property="Foreground" Value="Red"/>
<Setter Property="Background">
<Setter.Value>
<ImageBrush TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 32 32"
ImageSource="/images/open32.png"
Opacity="0.3"/>
</Setter.Value>
</Setter>
</Style>
WPF很少使用EventSetter
<Style x:Key="MouseOverStyle">
<EventSetter Event="TextBlock.MouseEnter" Handler="elem_MouseEnter"/>
<EventSetter Event="TextBlock.MouseLeave" Handler="elem_MouseLeave"/>
</Style>
private void elem_MouseEnter(object sender, MouseEventArgs e)
{
((TextBlock)sender).Background = new SolidColorBrush(Colors.LightGoldenrodYellow);
}
private void elem_MouseLeave(object sender, MouseEventArgs e)
{
((TextBlock)sender).Background = null;
}
WPF Style支持继承(通过BasedOn属性),但不推荐使用,除非有特别原因,否则不要使用
<Window.Resources>
<Style x:Key="bigFontButtonStyle">
<Setter Property="Control.FontFamily" Value="Times New Roman"/>
<Setter Property="Control.FontSize" Value="18"/>
<Setter Property="Control.FontWeight" Value="Bold"/>
</Style>
<Style x:Key="enhenceBigFontButtonStyle" BasedOn="{StaticResource bigFontButtonStyle}">
<Setter Property="Control.Foreground" Value="Red"/>
<Setter Property="Control.Background">
<Setter.Value>
<ImageBrush TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 32 32"
ImageSource="/images/open32.png"
Opacity="0.3"/>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
可以通过类型自动应用样式:只要指定TargetType并去掉x:Key即可
<Style TargetType="Button">
<Setter Property="Control.Foreground" Value="Red"/>
<Setter Property="Control.FontWeight" Value="Bold"/>
</Style>
<!--上面的定义等价于下面的定义-->
<Style x:Key="{x:Type Button}">
<Setter Property="Control.Foreground" Value="Red"/>
<Setter Property="Control.FontWeight" Value="Bold"/>
</Style>
<StackPanel>
<!--取消使用上述style-->
<Button Content="tuple" Style="{x:Null}"/>
<!--自动使用上述style-->
<Button Content="button"/>
</StackPanel>
普通触发器Trigger
//触发器的优点是不需要为恢复写任何代码,多个触发器冲突时,后面的覆盖前面的
<Style x:Key="mybuttonstyle">
<Setter Property="Control.FontWeight" Value="Bold"/>
<Setter Property="Control.Foreground" Value="Red"/>
<Style.Triggers>
<Trigger Property="Control.IsFocused" Value="True">
<Setter Property="Control.Foreground" Value="Green"/>
</Trigger>
<Trigger Property="Button.IsPressed" Value="True">
<Setter Property="Control.Foreground" Value="Blue"/>
</Trigger>
</Style.Triggers>
</Style>
//多个条件的,使用MultiTrigger
<Style x:Key="myButtonStyle">
<Setter Property="Control.FontWeight" Value="Bold"/>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Control.IsFocused" Value="True"/>
<Condition Property="Control.IsMouseOver" Value="True"/>
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Control.Foreground" Value="Blue"/>
</MultiTrigger.Setters>
</MultiTrigger>
</Style.Triggers>
</Style>
事件触发器
普通触发器等待属性发生变化,事件触发器等待特定事件被引发,事件触发器通常被用于动画
<Style x:Key="myButtonStyle">
<Setter Property="Control.FontWeight" Value="Bold"/>
<Style.Triggers>
<!--- 在0.2s内,字体大小从当前值改变到22 -->
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<!--- DoubleAnimation在指定时间内将double值从当前值改变到指定值-->
<DoubleAnimation Duration="0:0:0.2"
Storyboard.TargetProperty="FontSize"
To="22"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<!--- 在2s内,字体大小从当前值改变回原来的值 -->
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<!--- 没有设置To,WPF假定使用第一次动画前的值 -->
<DoubleAnimation Duration="0:0:1"
Storyboard.TargetProperty="FontSize"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
2.模板
WPF的控件都是设计成无外观的(lookless),但控件行为是固化到控件类中的,外观可以自己定义。
WPF有3种类型的模板:
控件模板(由ControlTemplate类表示),
数据模板(由DataTemplate和HierarchicalDataTemplate类表示),
以及用于ItemsControl控件的面板模板(由ItemsPanelTemplate类表示)。
数据模板用于从对象中提取数据,并在内容控件或者列表控件的各个项中显示数据,在数据绑定中,数据模板非常有用;在一定程度上数据模板和控件模板相互重叠。
System.Windows.Controls.Primitives提供可以在各种控件中使用的基本要素,而Micosoft.Windows.Themes包含了用于渲染这些细节的基本绘图逻辑。SnapToDevicePixels确保单个像素的线条不会被放到两个像素的“中间”,这在自定义模板中也非常有用。
下面定义了一个模板,使用TemplateBinding把模板里面的某个属性和使用该模板的控件的属性绑定到一起,并使用触发器设置相关属性的值。
//可以共用的资源,可以提取处理,定义成资源
<RadialGradientBrush x:Key="HighlightBackground"
RadiusX="1" RadiusY="5" GradientOrigin="0.5,0.3">
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="Blue" Offset=".4"/>
</RadialGradientBrush>
<RadialGradientBrush x:Key="PressedBackground"
RadiusX="1" RadiusY="5" GradientOrigin="0.5,0.3">
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="Blue" Offset="1"/>
</RadialGradientBrush>
//模板定义
<ControlTemplate x:Key="MyButtonTemplate" TargetType="{x:Type Button}">
<Border Name="Border"
//按钮的边框颜色
BorderBrush="{TemplateBinding BorderBrush}"
//按钮的边框宽度
BorderThickness="{TemplateBinding BorderThickness}"
//按钮的背景
Background="{TemplateBinding Background}"
//按钮文本颜色(ContentPresenter里面默认是一个透明的TextBlock)
TextBlock.Foreground="{TemplateBinding Foreground}">
<Grid>
//focus时显示虚线
<Rectangle Name="FocusCue" Visibility="Hidden" Stroke="Green"
StrokeThickness="1" StrokeDashArray="1 2" SnapsToDevicePixels="True"/>
//文本内容的margin和文本的对齐(注意不要和按钮对齐方式混淆)
<ContentPresenter Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
//鼠标移到按钮上,背景变成HighlightBackground
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background"
Value="{StaticResource HighlightBackground}"/>
</Trigger>
//鼠标按下,背景变成PressedBackground,边框变成DarkKhaki
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Border" Property="Background"
Value="{StaticResource PressedBackground}"/>
<Setter TargetName="Border" Property="BorderBrush" Value="DarkKhaki"/>
</Trigger>
//foucs显示虚线,通过触发器显示隐藏是最常见的用法
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter TargetName="FocusCue" Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Button x:Name="btn1"
Content="My Button Template" Width="300"
Template="{StaticResource MyButtonTemplate}"
//控件右对齐
HorizontalAlignment="Right"
//文本中间对齐
HorizontalContentAlignment="Center"
//参见上面模板里面的TemplateBinding
BorderBrush="Yellow"
BorderThickness="5"
Background="BurlyWood"
Foreground="Red"/>
3.通过样式应用模板
新建Custom Control时,VS会自动添加使用样式的模板。
<Style x:Key="MyButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Name="Border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
TextBlock.Foreground="{TemplateBinding Foreground}">
<Grid>
<Rectangle Name="FocusCue" Visibility="Hidden" Stroke="Green"
StrokeThickness="1" StrokeDashArray="1 2" SnapsToDevicePixels="True"/>
<ContentPresenter Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background"
Value="{StaticResource HighlightBackground}"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Border" Property="Background"
Value="{StaticResource PressedBackground}"/>
<Setter TargetName="Border" Property="BorderBrush" Value="DarkKhaki"/>
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter TargetName="FocusCue" Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<!-- trigger可以放这里,也可以放ControlTemplate,
但放这里无法设置模板内部的元素,因为访问不到.
如果同时在模板和style里面设置了触发器,那么style里面的优先级更高
-->
</Style.Triggers>
</Style>
<Button x:Name="btn1"
Content="My Button Template" Width="300"
Style="{StaticResource MyButtonStyle}"/>
4.复杂模板
控件模板和为其提供支持的代码之间有一个隐含的约定:如果使用自定义的模板替换默认模板,就需要确保新模板能够满足控件的实现代码的所有需要。对于简单控件,这个很容易,因为它对模板几乎或者完全没有真正的需求。但对于复杂控件就要小心了,因为控件外观和实现不是完全相互独立的。
UserControl从ContentControl派生;
UserControl改变了一些默认值:IsTabStop和Focusable改成false,并将HorizontalAlignment&VerticalAlignment设置为Stretch,从而填充可用空间;
UserControl应用了一个由ContentPresenter和Border组成的新的控件模板。
UserControl改变了路由事件的源。当事件从用户控件内的控件向外冒泡或隧道路由时,事件源变为指向用户控件而不是原始元素。
UserControl有控件模板,但很少改变控件模板,并且将模板作为类的一部分提供标记,创建控件后会使用InitializeComponent()方法处理这些标记。
无外观控件没有标记,需要的所有内容都在模板中。调用DefaultStyleKeyProperty.OverrideMetadata()通知WPF提供新的样式,否则就简单的使用默认样式。
模板中,创建链接到父控件属性的绑定表达式时,需要使用RelativeSource属性指示需要绑定到父控件,如果单向绑定可以满足需要通常使用轻量级的TemplateBinding表达式
<Slider Minimum="0" Maximum="255"
Margin="{TemplateBinding Padding}"
Value="{Binding Path=Red,
RelativeSource={RelativeSource TemplatedParent}}"/>
除非希望关联事件处理程序或者通过代码与元素进行交互,否则不要在控件模板中命名元素,需要命名时使用”PART_ElemName”的形式。
可以使用Style来应用样式,如果设置了一个新的样式,它会合并到默认样式中。如果新的样式与默认样式发生冲突,则新的样式会胜出并覆盖默认样式中的属性设置和触发器;而没有覆盖的细节仍然保留。
可以使用Style style = Application.Current.FindResource(typeof(Button))这样的方式查找默认样式。
如果不需要特定控件的功能,那么Control作为基类是最好的选择