65.9K
CodeProject 正在变化。 阅读更多。
Home

自定义 WPF 滑块按钮

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (7投票s)

2019 年 4 月 24 日

CPOL

4分钟阅读

viewsIcon

40254

downloadIcon

2362

本文介绍了一个使用依赖属性和模板实现的简单滑块按钮。

引言

本文介绍了一个简单的 WPF 两状态滑块按钮。每个状态显示的文本以及滑块按钮的大小都可以通过 XAML 进行配置。

滑块按钮是通过依赖属性和模板资源组合配置的。示例代码包含三个模板资源,可用于创建以下内容:

  1. 一个带圆角、圆形按钮和左侧标签的滑块控件
  2. 一个带圆角、圆形按钮和滑块轨道上标签的滑块控件
  3. 一个带方角、方形按钮和滑块轨道上标签的滑块控件

背景

我们的产品需要一个简单的滑块按钮,由于微软不提供标准控件,我不得不编写一个自定义控件。网上有很多示例,但大多数(如果不是全部)都倾向于硬编码外观。因此,颜色是固定的,文本是固定的,大小也是固定的。更灵活的通常会比较复杂。我们想要一些更灵活的东西,其外观(包括大小,宽度和高度)由 XAML 代码中的属性控制。一个关键的要求是代码尽可能简单,这样才能易于理解,并在需要时轻松扩展以满足自己的需求。我本可以使其更加灵活,但当前版本已经满足了我所有的需求。

理想情况下,您应该熟悉依赖属性、资源字典和控件模板的基础知识。但是,即使您的知识有限,也应该能够弄清楚如何使用该代码,并能够进行简单的修改以满足自己的需求。

屏幕截图

演示应用程序包含了所有三种样式的示例

Using the Code

SliderControl 继承自标准的 WPF ToggleButton 类。它添加了三个依赖属性:

ButtonWidth 滑块按钮的宽度
OnLabel 按钮处于开启状态时显示的文本
OffLabel 按钮处于关闭状态时显示的文本

按钮的整体宽度由基类控件的 Width 属性控制。高度由 ButtonWidth 自定义属性控制。

有三种样式,定义了三种形式的滑块按钮:

styleSliderButtonSideLabel 带左侧标签的滑块按钮
styleSliderButton 带圆形按钮的滑块按钮
styleSliderButtonRectangular 带方形按钮的滑块按钮

每种滑块按钮样式都定义了一个覆盖控件外观的模板。每个滑块按钮都由椭圆、边框和标签控件组合而成,并使用网格控件进行对齐。触发器用于在按钮被选中和取消选中时显示和隐藏组件控件。

SliderButton 类

SliderButton 类继承自标准的 ToggleButton 类,并添加了三个依赖属性:

namespace WpfSliderButtonDemo.View
{
    public class SliderButton : System.Windows.Controls.Primitives.ToggleButton
    {
        public double ButtonWidth
        {
            get
            {
                return (double)GetValue(ButtonWidthProperty);
            }
            set
            {
                SetValue(ButtonWidthProperty, value);
            }
        }

        public static readonly System.Windows.DependencyProperty ButtonWidthProperty = 
               System.Windows.DependencyProperty.Register("ButtonWidth", typeof(double), 
               typeof(SliderButton), new System.Windows.PropertyMetadata(0.0));

        public string OnLabel
        {
            get
            {
                return (string)GetValue(OnLabelProperty);
            }
            set
            {
                SetValue(OnLabelProperty, value);
            }
        }

        public static readonly System.Windows.DependencyProperty 
               OnLabelProperty = System.Windows.DependencyProperty.Register
               ("OnLabel", typeof(string), typeof(SliderButton), 
               new System.Windows.PropertyMetadata(""));

        public string OffLabel
        {
            get
            {
                return (string)GetValue(OffLabelProperty);
            }
            set
            {
                SetValue(OffLabelProperty, value);
            }
        }

        public static readonly System.Windows.DependencyProperty OffLabelProperty = 
               System.Windows.DependencyProperty.Register("OffLabel", typeof(string), 
               typeof(SliderButton), new System.Windows.PropertyMetadata(""));
    }
}

styleSliderButton 样式

styleSliderButton 样式定义了一个带圆形按钮和滑块轨道上标签的滑块按钮。

    <Style x:Key="styleSliderButton" TargetType="{x:Type local:SliderButton}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:SliderButton}">
                    <Grid x:Name="mainGrid">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                        </Grid.ColumnDefinitions>
                        <Border Grid.Column="0" Name="_borderOn" 
                         Background="Transparent" Width="{TemplateBinding Width}">
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto"/>
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="*" />
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <Ellipse Grid.Row="0" Grid.RowSpan="1" 
                                 Grid.Column="0" Grid.ColumnSpan="2" 
                                 Width="{TemplateBinding ButtonWidth}" 
                                 Height="{TemplateBinding ButtonWidth}" 
                                 Style="{StaticResource styleEllipseGreyButton}" 
                                 Panel.ZIndex="3" />
                                <Border Grid.Row="0" Grid.RowSpan="1" Grid.Column="1" 
                                 Grid.ColumnSpan="3" Background="ForestGreen" 
                                 BorderBrush="Gray" BorderThickness="0,1,0,1" Panel.ZIndex="1"/>
                                <Label Grid.Row="0" Grid.RowSpan="1" Grid.Column="2" 
                                 Grid.ColumnSpan="3" Name="_labelOn" 
                                 Content="{TemplateBinding OnLabel}" 
                                 SPanel.ZIndextyle=
                                     "{StaticResource styleSliderButtonLabel}" ="2"/>
                                <Ellipse Grid.Row="0" Grid.RowSpan="1" Grid.Column="3" 
                                 Grid.ColumnSpan="2" Width="{TemplateBinding ButtonWidth}" 
                                 Height="{TemplateBinding ButtonWidth}" 
                                 Style="{StaticResource styleEllipseButton}" 
                                 Fill="ForestGreen" Panel.ZIndex="0"/>
                            </Grid>
                        </Border>

                        <Border Grid.Column="0" Name="_borderOff" 
                         Background="Transparent" Width="{TemplateBinding Width}">
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto"/>
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="*" />
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <Ellipse Grid.Row="0" Grid.RowSpan="1" Grid.Column="0" 
                                 Grid.ColumnSpan="2" Width="{TemplateBinding ButtonWidth}" 
                                 Height="{TemplateBinding ButtonWidth}"  
                                 VerticalAlignment="Stretch" Fill="Crimson" 
                                 Stroke="Gray" Panel.ZIndex="0"/>
                                <Label Grid.Row="0" Grid.RowSpan="1" Grid.Column="0" 
                                 Grid.ColumnSpan="3" Name="_labelOff" 
                                 Content="{TemplateBinding OffLabel}" 
                                 Style="{StaticResource styleSliderButtonLabel}" 
                                 Panel.ZIndex="2"/>
                                <Border Grid.Row="0" Grid.RowSpan="1" Grid.Column="1" 
                                 Grid.ColumnSpan="3" Background="Crimson" BorderBrush="Gray" 
                                 BorderThickness="0,1,0,1" Panel.ZIndex="1"/>
                                <Ellipse Grid.Row="0" Grid.RowSpan="1" 
                                 Grid.Column="3" Grid.ColumnSpan="2" 
                                 Width="{TemplateBinding ButtonWidth}" 
                                 Height="{TemplateBinding ButtonWidth}"  
                                 Style="{StaticResource styleEllipseGreyButton}" 
                                 Panel.ZIndex="3"/>
                            </Grid>
                        </Border>
                    </Grid>

                    <!-- triggers toggle visual appearance -->
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsChecked" Value="True">
                            <Setter TargetName="_borderOff" Property="Visibility" 
                             Value="Collapsed" />
                            <Setter TargetName="_borderOn" Property="Visibility" 
                             Value="Visible" />
                        </Trigger>
                        <Trigger Property="IsChecked" Value="False">
                            <Setter TargetName="_borderOff" Property="Visibility" 
                             Value="Visible" />
                            <Setter TargetName="_borderOn" Property="Visibility" 
                             Value="Collapsed" />
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

该样式覆盖了 Template 属性,该属性定义了控件的外观和行为。控件由一个网格组成,包含两个复合元素,一个在控件被选中时显示,另一个在控件未被选中时显示。每个复合元素由一个网格组成,其中包含椭圆、边框和标签子元素,它们共同创建了滑块控件的外观。

请注意 Panel.ZIndex 附加属性的使用,它允许滑块按钮的椭圆显示在轨道前面,而轨道显示在代表轨道末端的椭圆前面。

同时请注意 TemplateBinding 标记扩展的使用,它允许模板中的元素使用在模板化控件(即 SliderButton)上定义的属性,包括在 ToggleButton 控件类上定义的属性。

滑块按钮是使用带有 styleEllipseGreyButton 样式的椭圆元素创建的。

    <Style x:Key="styleEllipseGreyButton" TargetType="{x:Type Ellipse}" 
     BasedOn="{StaticResource styleEllipseButton}">
        <Setter Property="Fill">
            <Setter.Value>
                <LinearGradientBrush EndPoint="0.504,1.5" StartPoint="0.504,0.03">
                    <GradientStop Color="#FFFFFF" Offset="0"/>
                    <GradientStop Color="#BBBBBB" Offset="0.567"/>
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
    </Style>

标签文本的外观使用 styleSliderButtonLabel 样式定义。

    <Style x:Key="styleSliderButtonLabel" TargetType="{x:Type Label}">
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="Foreground" Value="White"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="HorizontalAlignment" Value="Center"/>
        <Setter Property="HorizontalContentAlignment" Value="Center"/>
        <Setter Property="Padding" Value="0"/>
    </Style>

使用控件

将自定义滑块控件添加到视图中非常容易。

<view:SliderButton Grid.Row="7" Grid.Column="1" OnLabel="On" OffLabel="Off" 
 Width="110" ButtonWidth="50" Style="{StaticResource styleSliderButton}" 
 HorizontalAlignment="Center" IsChecked="{Binding IsEnabled}" FontSize="30"/>

示例应用

我提供了一个简单的演示应用程序供您下载。它包含了滑块按钮的代码,以及一个包含使用提供样式的大量滑块按钮示例的主窗口。

进一步增强

未来的增强功能包括创建一个垂直滑块按钮,以及添加用于控制两种状态背景颜色的依赖属性。这些任务留给读者作为练习。

Bug

我无法使标签文本垂直居中。这并不明显,但理想情况下应该修复。

关注点

WPF 非常灵活,并且有很多方法可以使用标准控件来模拟一个切换按钮。

历史

  • 2019 年 4 月 24 日:初版
© . All rights reserved.