UI 设计中经常会用到 Toggle Button,用于切换不同的状态。
UWP 中有 ToggleSwitch, 长这样:
WPF 中有 ToggleButton, 长这样:
嗯?????(黑人问号脸)
=========== 分割线 ===========
WPF 中需要给 ToggleButton 自定义控件模板,以实现 ToggleSwitch 的效果,先上效果图:
Style:
<ControlTemplate.Resources>
<Storyboard x:Key="OnChecked">
<ThicknessAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Margin)"
Storyboard.TargetName="path">
<EasingThicknessKeyFrame KeyTime="0" Value="40,0,0,0"/>
</ThicknessAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="OnUnchecked">
<ThicknessAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Margin)"
Storyboard.TargetName="path">
<EasingThicknessKeyFrame KeyTime="0" Value="0"/>
</ThicknessAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>
<Border x:Name="toggleBorder"
CornerRadius="13"
Background="Green"
Width="60"
Height="26"
BorderThickness="1"
BorderBrush="Black">
<Grid>
<Path x:Name="path">
<Path.Fill>
<LinearGradientBrush StartPoint="1,0" EndPoint="1,1">
<GradientStop Color="White"/>
</LinearGradientBrush>
</Path.Fill>
<Path.Data>
<GeometryGroup>
<GeometryGroup.Children>
<EllipseGeometry Center="6,12" RadiusX="9" RadiusY="9"/>
</GeometryGroup.Children>
</GeometryGroup>
</Path.Data>
</Path>
</Grid>
</Border>
加上 Trigger:
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="ToggleButton.Checked">
<BeginStoryboard Storyboard="{StaticResource OnChecked}"/>
</EventTrigger>
<EventTrigger RoutedEvent="ToggleButton.Unchecked">
<BeginStoryboard Storyboard="{StaticResource OnUnchecked}"/>
</EventTrigger>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="DimGray"/>
<Setter TargetName="toggleBorder" Property="Background" Value="Green"/>
<Setter TargetName="toggleBorder" Property="BorderBrush" Value="Green"/>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter TargetName="toggleBorder" Property="Background" Value="LightGray"/>
<Setter TargetName="path" Property="Fill">
<Setter.Value>
<LinearGradientBrush StartPoint="1,0" EndPoint="1,1">
<GradientStop Color="Gray"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter TargetName="path" Property="Data">
<Setter.Value>
<GeometryGroup>
<GeometryGroup.Children>
<EllipseGeometry Center="13,12" RadiusX="9" RadiusY="9"/>
</GeometryGroup.Children>
</GeometryGroup>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
到这儿自定义 ToggleButton 就基本完成了,下面是扩展部分。
=============================
扩展一:High Contrast
实际开发中,往往需要控件支持高对比度(High Contrast):
using System.ComponentModel;
using System.Windows;
using System.Windows.Media;
namespace ToggleButtonStyle
{
public class HighContrastHelper : DependencyObject
{
private HighContrastHelper()
{
SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged;
}
~HighContrastHelper()
{
SystemParameters.StaticPropertyChanged -= SystemParameters_StaticPropertyChanged;
}
private static HighContrastHelper instance;
public static HighContrastHelper Instance
{
get
{
if (instance == null)
instance = new HighContrastHelper();
return instance;
}
}
public void ApplyCurrentTheme()
{
if (SystemParameters.HighContrast)
{
SolidColorBrush windowbrush = SystemColors.WindowBrush;
if (windowbrush.Color.R == 255 && windowbrush.Color.G == 255 && windowbrush.Color.B == 255)
{
Instance.IsHighContrastWhite = true;
Instance.IsHighContrastBlack = false;
}
else if (windowbrush.Color.R == 0 && windowbrush.Color.G == 0 && windowbrush.Color.B == 0)
{
Instance.IsHighContrastWhite = false;
Instance.IsHighContrastBlack = true;
}
else
{
Instance.IsHighContrastWhite = false;
Instance.IsHighContrastBlack = false;
}
}
}
void SystemParameters_StaticPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "HighContrast")
{
ApplyCurrentTheme();
}
}
public static readonly DependencyProperty IsHighContrastWhiteProperty = DependencyProperty.Register(
"IsHighContrastWhite",
typeof(bool),
typeof(HighContrastHelper),
new PropertyMetadata(false));
public static readonly DependencyProperty IsHighContrastBlackProperty = DependencyProperty.Register(
"IsHighContrastBlack",
typeof(bool),
typeof(HighContrastHelper),
new PropertyMetadata(false));
public bool IsHighContrastWhite
{
get { return (bool)GetValue(IsHighContrastWhiteProperty); }
private set { SetValue(IsHighContrastWhiteProperty, value); }
}
public bool IsHighContrastBlack
{
get { return (bool)GetValue(IsHighContrastBlackProperty); }
private set { SetValue(IsHighContrastBlackProperty, value); }
}
}
}
注:系统事件在析构函数中最好取消订阅,以减小内存开销。
然后增加相应的 Trigger:
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=IsHighContrastWhite, Source={x:Static local:HighContrastHelper.Instance}}" Value="true" />
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=Tag}" Value="true"/>
<Condition Binding="{Binding ElementName=toggleButton, Path=IsChecked}" Value="true"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="toggleBorder" Property="Background" Value="Blue"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=IsHighContrastWhite, Source={x:Static local:HighContrastHelper.Instance}}" Value="true" />
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=Tag}" Value="true"/>
<Condition Binding="{Binding ElementName=toggleButton, Path=IsChecked}" Value="false"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="toggleBorder" Property="Background" Value="Black"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=IsHighContrastBlack, Source={x:Static local:HighContrastHelper.Instance}}" Value="true" />
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=Tag}" Value="true"/>
<Condition Binding="{Binding ElementName=toggleButton, Path=IsChecked}" Value="true"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="toggleBorder" Property="Background" Value="Aqua"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=IsHighContrastBlack, Source={x:Static local:HighContrastHelper.Instance}}" Value="true" />
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=Tag}" Value="true"/>
<Condition Binding="{Binding ElementName=toggleButton, Path=IsChecked}" Value="false"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="toggleBorder" Property="Background" Value="Yellow"/>
</MultiDataTrigger>
要控件支持高对比度,还需给控件设置 Tag 属性:
<Setter Property="Tag" Value="{DynamicResource {x:Static SystemParameters.HighContrastKey}}"/>
效果图:
High Contrast White On:
High Contrast White Off:
High Contrast Black On:
High Contrast Black Off:
扩展二:Accessibility
开发中往往还需要支持 Narrator 读取文本:
TabIndex="1"
AutomationProperties.Name="CustomizedToggle"
自动化测试 AutomationID:
AutomationProperties.AutomationId="CustomizedToggle"
需要注意的一点是当打开 Narrator,使用键盘的 Enter/Space 键切换 ToggleButton 的时候,其 Click 事件往往不能触发,这时候需要使用其 Checked/UnChecked 事件。
最后附上源码:WPF Toggle Button Style
That’s All, thx~
PS:原创不易,转载请注明出处。