WPF样式

29 篇文章 0 订阅
19 篇文章 1 订阅

样式是组织和重用格式化选项的重要工具。不是使用重复的标记填充XAML,以便设置外边距、内边距、颜色以及字体等细节,而是创建一系列封装所有这些细节的样式,然后在需要之处通过属性来应用样式。

样式基础

样式是可应用与元素的属性值集合。WPF样式系统与HTML标记中的层叠样式表(CSS)标准担当类似的角色。与CSS类似,通过WPF样式可定义通用的格式化特性集合,并且为了保证一致性,在整个应用程序中应用它们。与CSS一样,WPF样式也能够自动工作,指定具体的元素类型为目标,并通过元素树层叠起来。然而,WPF样式功能更加强大,因为它们能够设置任何依赖属性。这意味着可以使用它们标准化未格式化的特效,如控件的行为。WPF样式也支持触发器,当属性发生变化时,可通过触发器改变控件的样式,并可以通过模板重新定义控件的内置外观。

可以在任意元素的资源内定义样式:

    <Window.Resources>
        <Style x:Key="BigFontControlStyle">
            <Setter Property="Control.FontFamily" Value="Times New Roman"/>
            <Setter Property="Control.FontSize" Value="18"/>
            <Setter Property="Control.FontWeight" Value="Bold"/>
        </Style>
    </Window.Resources>

每个WPF元素都可以使用一个样式(或者没有样式),样式通过元素的Style属性插入到元素中

<Button Style="{StaticResource BigFontControlStyle}">A Custom Button</Button>

也可以通过代码设置样式

AnotherCustomButton.Style = (Style)AnotherCustomButton.FindResource("BigFontControlStyle");

样式系统增加了许多优点,不仅可以创建多组明显相关的属性设置,而且使用这些设置更加容易,从而精简标记。最让人满意的是,可应用样式而不用关系设置了哪些属性。Setters集合是Style类中最重要的属性,但并非唯一属性。Style类共有5个重要属性:

Setters:设置属性值以及自动关联事件处理程序的Setter对象或EventSetter对象的集合。

Triggers:继承自TriggerBase类并能自动改变样式设置的对象集合。例如,当另一个属性改变时,或者当发生某个事件时,可以修改样式。

Resources:希望用于样式的资源集合。例如,可能需要使用一个对象设置多个属性值。这时,更高效的做法是作为资源创建对象,然后在Setter对象中使用该资源(而不是使用嵌套的标签作为每个Setter对象的一部分创建对象)。

BaseOn:通过该属性可创建继承自(并且可以有选择的进行重写)其他样式设置的更具体样式。

TargetType:该属性标识应用样式的元素的类型。通过该属性可创建只影响特定元素的设置器,还可以创建能够为恰当的元素类型自动起作用的设置器。

设置属性

每个Style对象都封装了一个Setter对象的集合。每个Setter对象设置元素的单个属性。唯一的限制是设置器只能改变依赖项属性——不能修改其他属性。

在某些情况下,不能使用简单的特性字符串设置属性值。例如,不能使用简单字符串创建ImageBrush对象。对于此类情况,可以使用嵌套元素代替特性。

        <Style x:Key="HappyTiledElementStyle">
            <Setter Property="Control.Background">
                <Setter.Value>
                    <ImageBrush TileMode="Tile" ViewportUnits="Absolute" Viewport="0 0 32 32" ImageSource="happyface.jpg" Opacity="0.3"></ImageBrush>
                </Setter.Value>
            </Setter>
        </Style>

为了标识希望设置的属性,需要提供类和属性的名称。然而,使用的类名未必是定义属性的类名,也可以是继承了属性的派生类。

        <Style x:Key="BigFontButtonStyle">
            <Setter Property="Button.FontFamily" Value="Times New Roman"/>
            <Setter Property="Button.FontSize" Value="18"/>
            <Setter Property="Button.FontWeight" Value="Bold"/>
        </Style>

这种方式与前面使用Control类是等价的。如果将这个样式应用到Label组件上,同样可以生效:

<Label Style="{StaticResource BigFontButtonStyle}">A Custom Label</Label>

还可以使用另一种技巧来简化样式声明。如果所有属性都准备用于相同的元素类型,就设置Style的TargetType属性来指定准备应用属性的类。

        <Style x:Key="BigFontButtonStyle" TargetType="Button">
            <Setter Property="FontFamily" Value="Times New Roman"/>
            <Setter Property="FontSize" Value="18"/>
            <Setter Property="FontWeight" Value="Bold"/>
        </Style>

如果不使用样式键名,TargetType属性还可以作为自动应用样式的快捷方式。

关联事件处理程序

属性设置器是所有样式中最常见的要素,但也可以创建为事件关联特定事件处理程序的EventSetter对象的集合。

        <Style x:Key="MouseOverHighlightStyle">
            <EventSetter Event="TextBlock.MouseEnter" Handler="element_MouseEnter"/>
            <EventSetter Event="TextBlock.MouseLeave" Handler="element_MouseLeave"/>
            <Setter Property="TextBlock.FontSize" Value="20"/>
        </Style>



        <TextBlock Style="{StaticResource MouseOverHighlightStyle}">A MouseOverHighlightStyle TextBlock</TextBlock>
        private void element_MouseEnter(object sender, MouseEventArgs e)
        {
            ((TextBlock)sender).Background = new SolidColorBrush(Colors.LightGoldenrodYellow);
        }
        private void element_MouseLeave(object sender, MouseEventArgs e)
        {
            ((TextBlock)sender).Background = null;
        }

WPF极少使用事件设置器这种技术。如果需要使用此功能,可能更好的选择是事件触发器,它以声明的方式定义了所希望的行为(并且不需要任何代码)。事件触发器是专为实现动画而设计的,当创建鼠标悬停效果时他们更为有用。

当处理使用冒泡路由策略的事件时,事件设置器并非好的选择。对于这种情况,在高层次的元素上处理希望处理的事件通常更容易。例如,如果希望将工具栏上的所有按钮连接到同一个Click事件处理程序,最好为包含所有按钮的ToolBar元素关联单个事件处理程序。对于这种情况,没有必要使用事件设置器。

在许多情况下,明确的定义所有事件并完全避免使用事件设置器会更加清晰。如果需要为几个元素连接同一个事件处理程序,可手动进行,还可以使用容器级别关联事件处理程序以及通过命令几种逻辑等技巧。

多层样式

尽管可在许多不同层次定义任意数量的样式,但每个WPF元素一次只能使用一个样式对象。乍一看,这像是一种限制,但由于属性值继承和样式继承特性,这种限制实际上并不存在。

例如,假设希望为一组控件使用相同的字体,又不想为每个控件应用相同的样式。对于这种情况,可将他们放置到父容器中,并设置容器的样式。只要设置的属性具有属性值继承特性,这些值就会被传递到子元素。使用这种模型的属性包括 IsEnabled、IsVisible、Foreground以及所有字体属性。

对于另一些情况,可能希望在另一个样式的基础上创建样式。可通过为样式设置 BaseOn特性来使用此类样式继承。

        <Style x:Key="BigFontButtonStyle">
            <Setter Property="Button.FontFamily" Value="Times New Roman"/>
            <Setter Property="Button.FontSize" Value="18"/>
            <Setter Property="Button.FontWeight" Value="Bold"/>
        </Style>
        <Style x:Key="EmphasizedBigFontButtonStyle" BasedOn="{StaticResource BigFontButtonStyle}">
            <Setter Property="Button.Foreground" Value="White"/>
            <Setter Property="Button.Background" Value="DarkBlue"/>
        </Style>

可使用BaseOn属性创建一条完整的样式继承链。唯一的规则是,如果两次设置了同一个属性,最后的属性设置器(在继承链中最远的派生类中设置器)会覆盖其他以前的定义。

尽管样式继承看起来好像非常方便,但通常不值得因为它增加这么多麻烦。因为样式继承和代码继承存在相同的问题:依赖性使应用程序更脆弱。除非有特殊原因要求一个样式继承自另一个样式(例如,第二个样式是第一个样式的特例,并且只改变了继承来的大量设置中的几个特征),否则不要使用样式继承。

通过类型自动应用样式

前面已经提到了自动应用样式的方式,只需要设置TargetType属性以指定合适的类型,并完全湖绿键名。这样做时,WPF实际上是使用类型标记扩展来隐式的设置键名,如下所示:

x:Key = "{x:Type Button}"

如此,样式会自动应用于整个元素树中的所有按钮上。但是可以主动给某个按钮设置Style以阻止自动应用样式。

        <Button>Auto Style Button</Button>
        <Button Style="{x:Null}">A Normal Button</Button>

尽管自动样式非常方便,但它们会让设计变得复杂,下面列出几条原因:

1、在具有许多样式和多层样式的复杂窗口中,很难跟踪是否通过属性值继承或通过样式设置了某个特定属性。因此,如果希望改变某个简单细节,就需要查看整个窗口的全部标签。

2、窗口中的格式化操作在开始时通常更一般,但会逐渐变得越来越详细。如果刚开始为窗口应用了自动样式,在许多地方可能需要使用显示的样式覆盖自动样式。这会使整个设计变得复杂。为每个希望设置的格式化特征的组合创建命名的样式,并根据名称应用它们会更加直观。

3、再比如,如果为TextBlock元素创建自动样式,那么会同时修改使用TextBlock 的其他控件(如模板驱动的ListBox控件)。

触发器

WPF中有个主题,就是以声明方式扩展代码的功能。当使用样式、资源或数据绑定时,将发现即使不使用代码,也能完成不少工作。

触发器是另一个实现这种功能的例子。使用触发器,可自动完成简单的样式改变,而这通常需要使用样板事件处理逻辑。例如,当属性发生变化是可以进行响应,并自动调整样式。

触发器通过Style.Triggers集合链接到样式。每个样式可以有任意多个触发器,而且每个触发器都是 System.Windows.TriggerBase 的派生类的实例。继承自TriggerBase的类有:

Trigger:这是一张最简单的触发器。可以监测依赖项属性的变化,然后使用设置器改变样式。

MultiTrigger:与Trigger类似,但这种触发器联合了多个条件。只有满足所有这些条件,才会启动触发器。

DataTrigger:这种触发器使用数据绑定。与Trigger类似,只不过监视的是任意绑定数据的变化。

MultiDataTrigger:联合多个数据触发器。

EventTrigger:这是最复杂的触发器。当事件发生时,这种触发器应用动画。

通过使用FrameworkElement.Triggers集合,可直接为元素应用触发器,而不需要创建样式。但这存在一个相当大的缺陷。这个Triggers集合只支持事件触发器(并非技术上的原因造成了该限制,只是因为WPF团队没有时间实现该特性,将来的版本可能包含该特性)。

简单触发器

        <Style x:Key="TriggerStyle">
            <Style.Triggers>
                <Trigger Property="Control.IsFocused" Value="True">
                    <Setter Property="Control.Foreground" Value="DarkRed"/>
                </Trigger>
            </Style.Triggers>
        </Style>


        <Button Style="{StaticResource TriggerStyle}">A TriggerStyle Button</Button>

触发器的优点是不需要为翻转它们而编写任何逻辑。只要停止应用触发器,元素就会恢复到正常外观。

为理解触发器的工作原理,需要记住依赖项属性系统。本质上,触发器是纵多覆盖从依赖项属性返回的值的属性提供者之一。但原始的属性值(不管是在本地设置的还是通过样式设置)仍会保留。只要触发器被禁用,触发器之前的属性值就会再次可用。

可创建应用于同一元素的多个触发器。如果这些触发器设置不同的属性,这种情况就不会出现混乱。然而,如果多个触发器修改同一属性,那么最后的触发器将有效。

        <Style x:Key="TriggerStyle2">
            <Style.Triggers>
                <Trigger Property="Control.IsFocused" Value="True">
                    <Setter Property="Control.Foreground" Value="DarkRed"/>
                </Trigger>
                <Trigger Property="Control.IsMouseOver" Value="True">
                    <Setter Property="Control.Foreground" Value="LightYellow"/>
                    <Setter Property="Control.FontWeight" Value="Bold"/>
                </Trigger>
                <Trigger Property="Button.IsPressed" Value="True">
                    <Setter Property="Control.Foreground" Value="Blue"/>
                </Trigger>
            </Style.Triggers>
        </Style>


        <Button Style="{StaticResource TriggerStyle2}">A TriggerStyle2 Button</Button>

在这个示例中,要得到美观的按钮,触发器不能满足全部需求。还要受到按钮控件模板的限制,控件模板锁定了按钮外观的某些特定方面。当自定义元素时,为了得到这种程度的最佳结果,需要使用控件模板。然而,控件模板不能代替触发器——实际上,控件模板经常使用触发器以充分利用这两个特征:可以完全地自定义控件,并且可以响应鼠标悬停、单击以及其他事件来改变它们可视化外观的某些方面。

如果希望创建只有当几个条件都为真时才激活的触发器,可以使用MultiTrigger。这种触发器提供了一个Conditions集合,可通过该集合定义一系列属性和值的组合。

        <Style x:Key="MultiTriggerStyle">
            <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="DarkRed"/>
                        <Setter Property="Control.FontWeight" Value="Bold"/>
                    </MultiTrigger.Setters>
                </MultiTrigger>
            </Style.Triggers>
        </Style>


        <Button Style="{StaticResource MultiTriggerStyle}">A MultiTriggerStyle Button</Button>

事件触发器

普通触发器等待属性发生变化,而事件触发器等待特定的事件被引发。事件触发器要求用户提供一系列修改控件的动作。这些动作通常被用于动画。

        <Style x:Key="EventTriggerStyle">
            <Style.Triggers>
                <EventTrigger RoutedEvent="Mouse.MouseEnter">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Duration="0:0:1" Storyboard.TargetProperty="FontSize" To="22"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
                <EventTrigger RoutedEvent="Mouse.MouseLeave">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="FontSize"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Style.Triggers>
        </Style>


        <Button Style="{StaticResource EventTriggerStyle}">A EventTriggerStyle Button</Button>

这里使用了WPF的动画技术,动画必须定义在故事板内,故事板为动画提供了时间线。用户可以在故事板内定义希望使用的一个或多个动画对象。每个动画对象执行本质上相同的任务:在一定时期内修改依赖项属性。

这里使用了预先构建的DoubleAnimation类,该类能在一段给定的时间内将任意双精度数字逐渐改变为设定的目标值。

MainWindow.xaml

<Window x:Class="TestStyle.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestStyle"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <Style x:Key="BigFontControlStyle">
            <Setter Property="Control.FontFamily" Value="Times New Roman"/>
            <Setter Property="Control.FontSize" Value="18"/>
            <Setter Property="Control.FontWeight" Value="Bold"/>
        </Style>
        <Style x:Key="BigFontButtonStyle">
            <Setter Property="Button.FontFamily" Value="Times New Roman"/>
            <Setter Property="Button.FontSize" Value="18"/>
            <Setter Property="Button.FontWeight" Value="Bold"/>
        </Style>
        <Style x:Key="HappyTiledElementStyle">
            <Setter Property="Control.Background">
                <Setter.Value>
                    <ImageBrush TileMode="Tile" ViewportUnits="Absolute" Viewport="0 0 32 32" ImageSource="happyface.jpg" Opacity="0.3"></ImageBrush>
                </Setter.Value>
            </Setter>
        </Style>
        <Style x:Key="BigFontStyle">
            <Setter Property="Button.FontFamily" Value="Times New Roman"/>
            <Setter Property="Button.FontSize" Value="18"/>

            <Setter Property="TextBlock.FontFamily" Value="Arial"/>
            <Setter Property="TextBlock.FontSize" Value="10"/>
        </Style>
        <Style TargetType="Button">
            <Setter Property="FontFamily" Value="Times New Roman"/>
            <Setter Property="FontSize" Value="18"/>
            <Setter Property="FontWeight" Value="Bold"/>
        </Style>
        <Style x:Key="MouseOverHighlightStyle">
            <EventSetter Event="TextBlock.MouseEnter" Handler="element_MouseEnter"/>
            <EventSetter Event="TextBlock.MouseLeave" Handler="element_MouseLeave"/>
            <Setter Property="TextBlock.FontSize" Value="20"/>
        </Style>
        <Style x:Key="EmphasizedBigFontButtonStyle" BasedOn="{StaticResource BigFontButtonStyle}">
            <Setter Property="Button.Foreground" Value="White"/>
            <Setter Property="Button.Background" Value="DarkBlue"/>
        </Style>
        <Style x:Key="TriggerStyle">
            <Style.Triggers>
                <Trigger Property="Control.IsFocused" Value="True">
                    <Setter Property="Control.Foreground" Value="DarkRed"/>
                </Trigger>
            </Style.Triggers>
        </Style>
        <Style x:Key="TriggerStyle2">
            <Style.Triggers>
                <Trigger Property="Control.IsFocused" Value="True">
                    <Setter Property="Control.Foreground" Value="DarkRed"/>
                </Trigger>
                <Trigger Property="Control.IsMouseOver" Value="True">
                    <Setter Property="Control.Foreground" Value="LightYellow"/>
                    <Setter Property="Control.FontWeight" Value="Bold"/>
                </Trigger>
                <Trigger Property="Button.IsPressed" Value="True">
                    <Setter Property="Control.Foreground" Value="Blue"/>
                </Trigger>
            </Style.Triggers>
        </Style>
        <Style x:Key="MultiTriggerStyle">
            <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="DarkRed"/>
                        <Setter Property="Control.FontWeight" Value="Bold"/>
                    </MultiTrigger.Setters>
                </MultiTrigger>
            </Style.Triggers>
        </Style>

        <Style x:Key="EventTriggerStyle">
            <Style.Triggers>
                <EventTrigger RoutedEvent="Mouse.MouseEnter">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Duration="0:0:1" Storyboard.TargetProperty="FontSize" To="22"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
                <EventTrigger RoutedEvent="Mouse.MouseLeave">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="FontSize"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <StackPanel>
        <Button Style="{StaticResource BigFontControlStyle}">A Custom Button</Button>
        <Button Name="AnotherCustomButton">Another Custom Button</Button>
        <Button Style="{StaticResource HappyTiledElementStyle}">ImageButton</Button>
        <Label Style="{StaticResource BigFontButtonStyle}">A Custom Label</Label>
        <Button Style="{StaticResource BigFontStyle}">BigFontStyle</Button>
        <Button>Auto Style Button</Button>
        <Button Style="{x:Null}">A Normal Button</Button>
        <TextBlock Style="{StaticResource MouseOverHighlightStyle}">A MouseOverHighlightStyle TextBlock</TextBlock>
        <Button Style="{StaticResource EmphasizedBigFontButtonStyle}">A EmphasizedBigFontButtonStyle Button</Button>

        <Button Style="{StaticResource TriggerStyle}">A TriggerStyle Button</Button>
        <Button Style="{StaticResource TriggerStyle2}">A TriggerStyle2 Button</Button>
        <Button Style="{StaticResource MultiTriggerStyle}">A MultiTriggerStyle Button</Button>
        <Button Style="{StaticResource EventTriggerStyle}">A EventTriggerStyle Button</Button>
    </StackPanel>
</Window>

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace TestStyle
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            AnotherCustomButton.Style = (Style)AnotherCustomButton.FindResource("BigFontControlStyle");
        }

        private void element_MouseEnter(object sender, MouseEventArgs e)
        {
            ((TextBlock)sender).Background = new SolidColorBrush(Colors.LightGoldenrodYellow);
        }
        private void element_MouseLeave(object sender, MouseEventArgs e)
        {
            ((TextBlock)sender).Background = null;
        }
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值