自定义 WPF 滑块按钮






4.95/5 (7投票s)
本文介绍了一个使用依赖属性和模板实现的简单滑块按钮。
引言
本文介绍了一个简单的 WPF 两状态滑块按钮。每个状态显示的文本以及滑块按钮的大小都可以通过 XAML 进行配置。
滑块按钮是通过依赖属性和模板资源组合配置的。示例代码包含三个模板资源,可用于创建以下内容:
- 一个带圆角、圆形按钮和左侧标签的滑块控件
- 一个带圆角、圆形按钮和滑块轨道上标签的滑块控件
- 一个带方角、方形按钮和滑块轨道上标签的滑块控件
背景
我们的产品需要一个简单的滑块按钮,由于微软不提供标准控件,我不得不编写一个自定义控件。网上有很多示例,但大多数(如果不是全部)都倾向于硬编码外观。因此,颜色是固定的,文本是固定的,大小也是固定的。更灵活的通常会比较复杂。我们想要一些更灵活的东西,其外观(包括大小,宽度和高度)由 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 日:初版