管理按钮中的新视觉状态
Manage a new visual state in buttons
我想向 WPF 按钮添加新的 "Activated" 状态,并且我想避免从头开始重新创建控件。
此新状态链接到 IsActivated
依赖项 属性,并且必须更改 Button 的背景颜色。 IsEnabled
和 IsActivated
依赖属性之间相互作用的真相 table:
我写了一个 class 从 Button 扩展,创建依赖属性,并在 IsActivated
的回调中,我计算了按钮的视觉状态。
问题是 ButtonBase 类型已经通过 ChangeVisualState function 管理视觉状态,无法覆盖。
管理两个依赖属性的回调后,IsActivated
和 IsEnabled
之间的交互按预期工作,但单击按钮或将鼠标放在按钮上会覆盖 Activated
视觉状态。
是否可以使用视觉状态来完成此操作,还是我应该使用简单的触发器?
目前控件的代码:
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace OrganizationName.BasicControls.Primitive
{
public class MultiStateButton : Button
{
static MultiStateButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiStateButton), new FrameworkPropertyMetadata(typeof(MultiStateButton)));
IsEnabledProperty.OverrideMetadata(typeof(MultiStateButton), new FrameworkPropertyMetadata(propertyChangedCallback: IsEnabledCallback));
}
internal void ChangeVisualState(bool useTransitions)
{
if (!IsEnabled)
{
VisualStateManager.GoToState(this, "Disabled", useTransitions);
}
else if (IsActivated)
{
VisualStateManager.GoToState(this, "Activated", useTransitions);
}
else if (IsPressed)
{
VisualStateManager.GoToState(this, "Pressed", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Normal", useTransitions);
}
}
#region IsEnabled override
private static void IsEnabledCallback(DependencyObject o, DependencyPropertyChangedEventArgs args)
{
MultiStateButton multiStateButton = o as MultiStateButton;
if (multiStateButton == null) return;
multiStateButton.ChangeVisualState(true);
}
#endregion IsEnabled override
#region DP IsActivated
public bool IsActivated
{
get { return (bool)GetValue(IsActivatedProperty); }
set { SetValue(IsActivatedProperty, value); }
}
private static void IsActivatedCallback(DependencyObject o, DependencyPropertyChangedEventArgs args)
{
MultiStateButton multiStateButton = o as MultiStateButton;
if (multiStateButton == null) return;
multiStateButton.ChangeVisualState(true);
}
private readonly static FrameworkPropertyMetadata IsActivatedMetadata = new FrameworkPropertyMetadata
{
PropertyChangedCallback = IsActivatedCallback,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
public static readonly DependencyProperty IsActivatedProperty =
DependencyProperty.Register("IsActivated", typeof(bool), typeof(MultiStateButton), IsActivatedMetadata);
#endregion DP IsActivated
}
}
按钮的默认样式:
<sys:Double x:Key="ButtonCornerRadiusValue">5</sys:Double>
<CornerRadius x:Key="ButtonCornerRadius"
TopLeft="{StaticResource ButtonCornerRadiusValue}"
BottomLeft="{StaticResource ButtonCornerRadiusValue}"
TopRight="{StaticResource ButtonCornerRadiusValue}"
BottomRight="{StaticResource ButtonCornerRadiusValue}"/>
<CornerRadius x:Key="ButtonCornerRadiusLeft"
TopLeft="{StaticResource ButtonCornerRadiusValue}"
BottomLeft="{StaticResource ButtonCornerRadiusValue}"/>
<CornerRadius x:Key="ButtonCornerRadiusRight"
TopRight="{StaticResource ButtonCornerRadiusValue}"
BottomRight="{StaticResource ButtonCornerRadiusValue}"/>
<CornerRadius x:Key="ButtonCornerRadiusTop"
TopRight="{StaticResource ButtonCornerRadiusValue}"
TopLeft="{StaticResource ButtonCornerRadiusValue}"/>
<CornerRadius x:Key="ButtonCornerRadiusBottom"
BottomLeft="{StaticResource ButtonCornerRadiusValue}"
BottomRight="{StaticResource ButtonCornerRadiusValue}"/>
<Style x:Key="{x:Type primitives:MultiStateButton}"
TargetType="{x:Type primitives:MultiStateButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type primitives:MultiStateButton}">
<Border TextBlock.Foreground="{TemplateBinding Foreground}"
x:Name="Border"
CornerRadius="{StaticResource ButtonCornerRadius}"
Background="White"
BorderThickness="1">
<Border.Effect>
<DropShadowEffect Color="#CDD5E3"
ShadowDepth="0"
BlurRadius="13"
Direction="0"/>
</Border.Effect>
<Border.BorderBrush>
<RadialGradientBrush Center="0.5,0.5"
RadiusY="1"
RadiusX="5"
GradientOrigin="0.5,0.5">
<RadialGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="Transparent"
Offset="0" />
<GradientStop Color="Transparent"
Offset="0.5" />
<GradientStop Color="{StaticResource BorderPushedColor}"
Offset="0.8" />
<GradientStop Color="{StaticResource BorderPushedColor}"
Offset="1" />
</GradientStopCollection>
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</Border.BorderBrush>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver"/>
<VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Border.Width)"
Storyboard.TargetName="LeftBorder"
Duration="00:00:00"
To="10"/>
<DoubleAnimation Storyboard.TargetProperty="(Border.Width)"
Storyboard.TargetName="RightBorder"
Duration="00:00:00"
To="10"/>
<DoubleAnimation Storyboard.TargetProperty="(Border.Height)"
Storyboard.TargetName="TopBorder"
Duration="00:00:00"
To="10"/>
<DoubleAnimation Storyboard.TargetProperty="(Border.Height)"
Storyboard.TargetName="BottomBorder"
Duration="00:00:00"
To="10"/>
<DoubleAnimation Storyboard.TargetProperty="(Border.Effect).(DropShadowEffect.BlurRadius)"
Storyboard.TargetName="Border"
Duration="00:00:00"
To="10"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Background)"
Storyboard.TargetName="Border">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SkyblueLight}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Activated">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Background)"
Storyboard.TargetName="Border">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ReflexBlue}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid Background="Transparent">
<ContentPresenter Margin="2"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True"/>
<Border BorderThickness="0"
Width="0"
HorizontalAlignment="Left"
CornerRadius="{StaticResource ButtonCornerRadiusLeft}"
x:Name="LeftBorder">
<Border.Background>
<LinearGradientBrush StartPoint="1,0.5" EndPoint="0,0.5">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Transparent"/>
<GradientStop Offset="1" Color="#D9DDE4"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Border.Background>
</Border>
<Border BorderThickness="0"
Width="0"
HorizontalAlignment="Right"
CornerRadius="{StaticResource ButtonCornerRadiusRight}"
x:Name="RightBorder">
<Border.Background>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Transparent"/>
<GradientStop Offset="1" Color="#D9DDE4"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Border.Background>
</Border>
<Border BorderThickness="0"
Height="0"
VerticalAlignment="Top"
CornerRadius="{StaticResource ButtonCornerRadiusTop}"
x:Name="TopBorder">
<Border.Background>
<LinearGradientBrush StartPoint="0.5,1" EndPoint="0.5,0">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Transparent"/>
<GradientStop Offset="1" Color="#D9DDE4"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Border.Background>
</Border>
<Border BorderThickness="0"
Height="0"
VerticalAlignment="Bottom"
CornerRadius="{StaticResource ButtonCornerRadiusBottom}"
x:Name="BottomBorder">
<Border.Background>
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Transparent"/>
<GradientStop Offset="1" Color="#D9DDE4"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Border.Background>
</Border>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
在向现有控件添加简单的视觉状态时,我通常会避免创建子class,而是使用附加属性。
在为现有控件实现新的视觉状态时,您根本不必使用 VisualStateManager
。特别是如果您不使用动画。
我建议改用 Triggers
。
如果您想继续使用您的 MultiStateButton
控件,您可以简单地执行以下操作:
<ControlTemplate ...>
...
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsEnabled" Value="True" />
<Condition Property="IsActivated" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="Border" Property="Background" Value="{StaticResource ReflexBlue}" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
但是,如果您不需要继续使用MultiStateButton
,我会保留您的自定义按钮样式和控件模板,并使用附加的属性 class 添加新 属性.
public static class MultiStateButtonProperties
{
public static readonly DependencyProperty IsActivatedProperty = DependencyProperty.RegisterAttached("IsActivated", typeof(bool), typeof(MultiStateButtonProperties), new FrameworkPropertyMetadata(false));
public static bool GetIsActivated(DependencyObject obj)
{
return (bool)obj.GetValue(IsActivatedProperty);
}
public static void SetIsActivated(DependencyObject obj, bool value)
{
obj.SetValue(IsActivatedProperty, value);
}
}
然后,在您的样式的控件模板中,您可以像上面那样使用 MultiTrigger 并执行如下操作:
<ControlTemplate ...>
...
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsEnabled" Value="True" />
<Condition Property="ap:MultiStateButtonProperties.IsActivated" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="Border" Property="Background" Value="{StaticResource ReflexBlue}" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
希望对您有所帮助。
我想向 WPF 按钮添加新的 "Activated" 状态,并且我想避免从头开始重新创建控件。
此新状态链接到 IsActivated
依赖项 属性,并且必须更改 Button 的背景颜色。 IsEnabled
和 IsActivated
依赖属性之间相互作用的真相 table:
我写了一个 class 从 Button 扩展,创建依赖属性,并在 IsActivated
的回调中,我计算了按钮的视觉状态。
问题是 ButtonBase 类型已经通过 ChangeVisualState function 管理视觉状态,无法覆盖。
管理两个依赖属性的回调后,IsActivated
和 IsEnabled
之间的交互按预期工作,但单击按钮或将鼠标放在按钮上会覆盖 Activated
视觉状态。
是否可以使用视觉状态来完成此操作,还是我应该使用简单的触发器?
目前控件的代码:
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace OrganizationName.BasicControls.Primitive
{
public class MultiStateButton : Button
{
static MultiStateButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiStateButton), new FrameworkPropertyMetadata(typeof(MultiStateButton)));
IsEnabledProperty.OverrideMetadata(typeof(MultiStateButton), new FrameworkPropertyMetadata(propertyChangedCallback: IsEnabledCallback));
}
internal void ChangeVisualState(bool useTransitions)
{
if (!IsEnabled)
{
VisualStateManager.GoToState(this, "Disabled", useTransitions);
}
else if (IsActivated)
{
VisualStateManager.GoToState(this, "Activated", useTransitions);
}
else if (IsPressed)
{
VisualStateManager.GoToState(this, "Pressed", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Normal", useTransitions);
}
}
#region IsEnabled override
private static void IsEnabledCallback(DependencyObject o, DependencyPropertyChangedEventArgs args)
{
MultiStateButton multiStateButton = o as MultiStateButton;
if (multiStateButton == null) return;
multiStateButton.ChangeVisualState(true);
}
#endregion IsEnabled override
#region DP IsActivated
public bool IsActivated
{
get { return (bool)GetValue(IsActivatedProperty); }
set { SetValue(IsActivatedProperty, value); }
}
private static void IsActivatedCallback(DependencyObject o, DependencyPropertyChangedEventArgs args)
{
MultiStateButton multiStateButton = o as MultiStateButton;
if (multiStateButton == null) return;
multiStateButton.ChangeVisualState(true);
}
private readonly static FrameworkPropertyMetadata IsActivatedMetadata = new FrameworkPropertyMetadata
{
PropertyChangedCallback = IsActivatedCallback,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
public static readonly DependencyProperty IsActivatedProperty =
DependencyProperty.Register("IsActivated", typeof(bool), typeof(MultiStateButton), IsActivatedMetadata);
#endregion DP IsActivated
}
}
按钮的默认样式:
<sys:Double x:Key="ButtonCornerRadiusValue">5</sys:Double>
<CornerRadius x:Key="ButtonCornerRadius"
TopLeft="{StaticResource ButtonCornerRadiusValue}"
BottomLeft="{StaticResource ButtonCornerRadiusValue}"
TopRight="{StaticResource ButtonCornerRadiusValue}"
BottomRight="{StaticResource ButtonCornerRadiusValue}"/>
<CornerRadius x:Key="ButtonCornerRadiusLeft"
TopLeft="{StaticResource ButtonCornerRadiusValue}"
BottomLeft="{StaticResource ButtonCornerRadiusValue}"/>
<CornerRadius x:Key="ButtonCornerRadiusRight"
TopRight="{StaticResource ButtonCornerRadiusValue}"
BottomRight="{StaticResource ButtonCornerRadiusValue}"/>
<CornerRadius x:Key="ButtonCornerRadiusTop"
TopRight="{StaticResource ButtonCornerRadiusValue}"
TopLeft="{StaticResource ButtonCornerRadiusValue}"/>
<CornerRadius x:Key="ButtonCornerRadiusBottom"
BottomLeft="{StaticResource ButtonCornerRadiusValue}"
BottomRight="{StaticResource ButtonCornerRadiusValue}"/>
<Style x:Key="{x:Type primitives:MultiStateButton}"
TargetType="{x:Type primitives:MultiStateButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type primitives:MultiStateButton}">
<Border TextBlock.Foreground="{TemplateBinding Foreground}"
x:Name="Border"
CornerRadius="{StaticResource ButtonCornerRadius}"
Background="White"
BorderThickness="1">
<Border.Effect>
<DropShadowEffect Color="#CDD5E3"
ShadowDepth="0"
BlurRadius="13"
Direction="0"/>
</Border.Effect>
<Border.BorderBrush>
<RadialGradientBrush Center="0.5,0.5"
RadiusY="1"
RadiusX="5"
GradientOrigin="0.5,0.5">
<RadialGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="Transparent"
Offset="0" />
<GradientStop Color="Transparent"
Offset="0.5" />
<GradientStop Color="{StaticResource BorderPushedColor}"
Offset="0.8" />
<GradientStop Color="{StaticResource BorderPushedColor}"
Offset="1" />
</GradientStopCollection>
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</Border.BorderBrush>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver"/>
<VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Border.Width)"
Storyboard.TargetName="LeftBorder"
Duration="00:00:00"
To="10"/>
<DoubleAnimation Storyboard.TargetProperty="(Border.Width)"
Storyboard.TargetName="RightBorder"
Duration="00:00:00"
To="10"/>
<DoubleAnimation Storyboard.TargetProperty="(Border.Height)"
Storyboard.TargetName="TopBorder"
Duration="00:00:00"
To="10"/>
<DoubleAnimation Storyboard.TargetProperty="(Border.Height)"
Storyboard.TargetName="BottomBorder"
Duration="00:00:00"
To="10"/>
<DoubleAnimation Storyboard.TargetProperty="(Border.Effect).(DropShadowEffect.BlurRadius)"
Storyboard.TargetName="Border"
Duration="00:00:00"
To="10"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Background)"
Storyboard.TargetName="Border">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SkyblueLight}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Activated">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Background)"
Storyboard.TargetName="Border">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ReflexBlue}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid Background="Transparent">
<ContentPresenter Margin="2"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True"/>
<Border BorderThickness="0"
Width="0"
HorizontalAlignment="Left"
CornerRadius="{StaticResource ButtonCornerRadiusLeft}"
x:Name="LeftBorder">
<Border.Background>
<LinearGradientBrush StartPoint="1,0.5" EndPoint="0,0.5">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Transparent"/>
<GradientStop Offset="1" Color="#D9DDE4"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Border.Background>
</Border>
<Border BorderThickness="0"
Width="0"
HorizontalAlignment="Right"
CornerRadius="{StaticResource ButtonCornerRadiusRight}"
x:Name="RightBorder">
<Border.Background>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Transparent"/>
<GradientStop Offset="1" Color="#D9DDE4"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Border.Background>
</Border>
<Border BorderThickness="0"
Height="0"
VerticalAlignment="Top"
CornerRadius="{StaticResource ButtonCornerRadiusTop}"
x:Name="TopBorder">
<Border.Background>
<LinearGradientBrush StartPoint="0.5,1" EndPoint="0.5,0">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Transparent"/>
<GradientStop Offset="1" Color="#D9DDE4"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Border.Background>
</Border>
<Border BorderThickness="0"
Height="0"
VerticalAlignment="Bottom"
CornerRadius="{StaticResource ButtonCornerRadiusBottom}"
x:Name="BottomBorder">
<Border.Background>
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Transparent"/>
<GradientStop Offset="1" Color="#D9DDE4"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Border.Background>
</Border>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
在向现有控件添加简单的视觉状态时,我通常会避免创建子class,而是使用附加属性。
在为现有控件实现新的视觉状态时,您根本不必使用 VisualStateManager
。特别是如果您不使用动画。
我建议改用 Triggers
。
如果您想继续使用您的 MultiStateButton
控件,您可以简单地执行以下操作:
<ControlTemplate ...>
...
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsEnabled" Value="True" />
<Condition Property="IsActivated" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="Border" Property="Background" Value="{StaticResource ReflexBlue}" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
但是,如果您不需要继续使用MultiStateButton
,我会保留您的自定义按钮样式和控件模板,并使用附加的属性 class 添加新 属性.
public static class MultiStateButtonProperties
{
public static readonly DependencyProperty IsActivatedProperty = DependencyProperty.RegisterAttached("IsActivated", typeof(bool), typeof(MultiStateButtonProperties), new FrameworkPropertyMetadata(false));
public static bool GetIsActivated(DependencyObject obj)
{
return (bool)obj.GetValue(IsActivatedProperty);
}
public static void SetIsActivated(DependencyObject obj, bool value)
{
obj.SetValue(IsActivatedProperty, value);
}
}
然后,在您的样式的控件模板中,您可以像上面那样使用 MultiTrigger 并执行如下操作:
<ControlTemplate ...>
...
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsEnabled" Value="True" />
<Condition Property="ap:MultiStateButtonProperties.IsActivated" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="Border" Property="Background" Value="{StaticResource ReflexBlue}" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
希望对您有所帮助。