WPF 自定义 ToggleButton 样式

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:原创不易,转载请注明出处。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值