WPF 视觉状态VisualState使用与动态主题实现
VisualState基本使用
首先搭建一个自定义控件,继承自ContentControl
,自动生成了这些文件
由于CustomButton
在Custom
命名空间中,所以改一下xaml中的引用
xmlns:local="clr-namespace:WpfApp1.Custom"
- 定义部件和视觉状态
TemplatePart
是模板中的部件名
TemplateVisualState
是模板中的样式名
[TemplatePart(Name = "P_Border")]
[TemplateVisualState(Name = mouseoverstate, GroupName = mousegroup)]
[TemplateVisualState(Name = mouseleavestate, GroupName = mousegroup)]
public class CustomButton : ContentControl
{
public const string mouseoverstate = "mouseover";
public const string mouseleavestate = "mouseleave";
public const string mousegroup = "mousegroup1";
}
- 添加样式
推荐把视觉状态放在模板的根元素下,这个例子中是Border
<Style TargetType="{x:Type local:CustomButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomButton}">
<Border
x:Name="P_Border"
Padding="5"
CornerRadius="5"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0">
<TextBlock Text="{TemplateBinding Content}" Foreground="White" HorizontalAlignment="Center"/>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="mousegroup1">
<!--GoToState第三个参数useTransitions为false时进入这里-->
<VisualState x:Name="mouseover">
<Storyboard>
<ColorAnimation Storyboard.TargetName="P_Border" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" Duration="0:0:0" To="#FF16b777"/>
</Storyboard>
</VisualState>
<VisualState x:Name="mouseleave">
<Storyboard>
<ColorAnimation Storyboard.TargetName="P_Border" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" Duration="0:0:0" To="#CC16b777"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
- 后台代码
这里在鼠标事件中改变视觉状态
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
VisualStateManager.GoToState(this, mouseleavestate, false);
}
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
VisualStateManager.GoToState(this, mouseoverstate, false);
}
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
VisualStateManager.GoToState(this, mouseleavestate, false);
}
- 效果
增加VisualTransition过渡效果
但这没有过渡,添加过渡需要设置GoToState(this, mouseoverstate, true)
同时还要添加过渡效果的xaml元素
需要注意的是,由于使用了(Border.Background).(SolidColorBrush.Color)
,所以Border
的Background
必须有个预设值
<VisualStateGroup x:Name="mousegroup1">
<!--GoToState第三个参数useTransitions为true时进入这里-->
<VisualStateGroup.Transitions>
<!--from是前一个状态名,to是后一个状态名-->
<VisualTransition To="mouseover">
<Storyboard>
<!--(Border.Background).(SolidColorBrush.Color)是PropertyPath类型限定的单个属性语法,格式为(targetTtype.property)-->
<ColorAnimation Storyboard.TargetName="P_Border" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" Duration="0:0:1" To="#FF167777"/>
</Storyboard>
</VisualTransition>
<VisualTransition To="mouseleave">
<Storyboard>
<ColorAnimation Storyboard.TargetName="P_Border" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" Duration="0:0:1" To="#AA167777"/>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<!--GoToState第三个参数useTransitions为false时进入这里-->
<VisualState x:Name="mouseover">
<Storyboard>
<ColorAnimation Storyboard.TargetName="P_Border" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" Duration="0:0:0" To="#FF167777"/>
</Storyboard>
</VisualState>
<VisualState x:Name="mouseleave">
<Storyboard>
<ColorAnimation Storyboard.TargetName="P_Border" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" Duration="0:0:0" To="#AA167777"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
再把GoToState(this, mouseoverstate, false)
改为true
但注意OnApplyTemplate
中不要改,因为那里是首次加载,许需要过渡
- 效果
动态主题
- 首先确定哪些样式需要改为
资源
- 按钮文字颜色 FontColor
- 按钮背景颜色 BackgroundColor
- 鼠标悬浮时按钮背景颜色 HoverBackgroundColor
- 为每个主题建立资源字典
- 设置每个主题中这些资源
//Light.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1.Custom">
<SolidColorBrush x:Key="FontColor" Color="#FF000000"/>
<Color x:Key="BackgroundColor">#FFaaaaaa</Color>
<Color x:Key="HoverBackgroundColor">#66aaaaaa</Color>
</ResourceDictionary>
//Dark.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1.Custom">
<SolidColorBrush x:Key="FontColor" Color="#FFffffff"/>
<Color x:Key="BackgroundColor">#FF000000</Color>
<Color x:Key="HoverBackgroundColor">#55000000</Color>
</ResourceDictionary>
//Normal.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="FontColor" Color="#FFffffff"/>
<Color x:Key="BackgroundColor">#FF167777</Color>
<Color x:Key="HoverBackgroundColor">#AA167777</Color>
</ResourceDictionary>
- 将控件样式中的固定值改为资源引用
特别注意,Border
的背景颜色不能用动态资源,只能用静态资源。按钮需要一个初始颜色
<SolidColorBrush x:Key="DefaultBackgoundColor" Color="#FF167777"/>
<Style TargetType="{x:Type local:CustomButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomButton}">
<Border
x:Name="P_Border"
Padding="5"
CornerRadius="5"
Background="{StaticResource DefaultBackgoundColor}"
BorderBrush="Transparent"
BorderThickness="0">
<TextBlock Text="{TemplateBinding Content}" Foreground="{DynamicResource FontColor}" HorizontalAlignment="Center"/>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="mousegroup1">
<!--GoToState第三个参数useTransitions为true时进入这里-->
<VisualStateGroup.Transitions>
<!--from是前一个状态名,to是后一个状态名-->
<VisualTransition To="mouseover">
<Storyboard>
<!--(Border.Background).(SolidColorBrush.Color)是PropertyPath类型限定的单个属性语法,格式为(targetTtype.property)-->
<ColorAnimation Storyboard.TargetName="P_Border" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" Duration="0:0:1" To="{DynamicResource BackgroundColor}"/>
</Storyboard>
</VisualTransition>
<VisualTransition To="mouseleave">
<Storyboard>
<ColorAnimation Storyboard.TargetName="P_Border" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" Duration="0:0:1" To="{DynamicResource HoverBackgroundColor}"/>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<!--GoToState第三个参数useTransitions为false时进入这里-->
<VisualState x:Name="mouseover">
<Storyboard>
<ColorAnimation Storyboard.TargetName="P_Border" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" Duration="0:0:0" To="{DynamicResource BackgroundColor}"/>
</Storyboard>
</VisualState>
<VisualState x:Name="mouseleave">
<Storyboard>
<ColorAnimation Storyboard.TargetName="P_Border" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" Duration="0:0:0" To="{DynamicResource HoverBackgroundColor}"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
效果
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!--<ResourceDictionary Source="Themes/Light.xaml"/>-->
<!--<ResourceDictionary Source="Themes/Dark.xaml"/>-->
<ResourceDictionary Source="Themes/Normal.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>