0x01. 概要

WPF 自带的拖动条控件是 Slider, 其默认样式为:

这种风格一般很难和实际的APP匹配, UI肯定会给一种自己的APP风格的拖动条. 最简单的莫过于修改滑块图案, 滑轨颜色等等. 如:

0x02. Slider组成

根据微软官方的文档, 一个Slider如下组成:

从上图我们可以看出, Slider的简单组成为: Track 和 TickBar, 其中Track包括:

  • Thumb : 滑块
  • RepeatButton : 重复的按钮, 即滑轨. 分为两段, 增量部分和减量部分.

TickBar为刻度标尺, 可选.

0x03. 自定义Slider风格

和其他WPF控件的自定义一样, 可以在 Window 标签的 Resource 中直接定义 Style, 也可以在 Slider 标签内自定义.

首先自定义 Thumb 部分, 把 Thumb 改为一个灰色边儿的白色圆形:

<Style TargetType="{x:Type Thumb}" x:Key="SliderThumbStyle">
    <Setter Property="SnapsToDevicePixels" Value="True"/>
    <Setter Property="OverridesDefaultStyle" Value="True"/>
    <Setter Property="Height" Value="15"/>
    <Setter Property="Width" Value="15"></Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Thumb}">
                <Border BorderBrush="#FFEBEBEB" BorderThickness="1" CornerRadius="7">
                    <Ellipse Width="14" Height="14" Fill="White"/>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

其次是定义 RepeatButton, 分为IncreaseRepeatButtonDecreaseRepeatButton, 这里我们将两种 Button 定义为一样的颜色和形状:

<Style TargetType="{x:Type RepeatButton}" x:Key="SliderIncreaseButtonStyle">
    <Setter Property="SnapsToDevicePixels" Value="True"/>
    <Setter Property="OverridesDefaultStyle" Value="True"/>
    <Setter Property="IsTabStop" Value="False"/>
    <Setter Property="Focusable" Value="False"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type RepeatButton}">
                <Border Width="4" Background="#FFEBEBEB" SnapsToDevicePixels="True"/>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<Style TargetType="{x:Type RepeatButton}" x:Key="SliderDecreaseButtonStyle">
    <Setter Property="SnapsToDevicePixels" Value="True"/>
    <Setter Property="OverridesDefaultStyle" Value="True"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type RepeatButton}">
                <Border Width="4" Background="#FFEBEBEB" SnapsToDevicePixels="True"/>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

接下来是组合上面的ThumbRepeatButton :

<Style x:Key="SliderStyle1" TargetType="{x:Type Slider}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Slider}">
                <Grid>
                    <Track >
                        <Track.DecreaseRepeatButton>
                            <RepeatButton
                                Style="{StaticResource SliderDecreaseButtonStyle}"/>
                        </Track.DecreaseRepeatButton>
                        <Track.IncreaseRepeatButton>
                            <RepeatButton 
                                Style="{StaticResource SliderIncreaseButtonStyle}"/>
                        </Track.IncreaseRepeatButton>
                        <Track.Thumb>
                            <Thumb  Focusable="False"
                                    Style="{StaticResource SliderThumbStyle}"
                                    VerticalAlignment="Top"/>
                        </Track.Thumb>
                    </Track>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

在 Track内部包括了 IncreaseRepeatButton, Thumb, DecreaseRepeatButton. 组合好以后应用到 Slider中:

<Grid>
    <Slider Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center"
            SnapsToDevicePixels="True" Maximum="10" MinHeight="100"
            Style="{DynamicResource SliderStyle1}">
    </Slider>
</Grid>

然后运行项目, 你会发现 Slider 已经和上面的图一样了:

但是, 当你拖动滑块的时候会发现拖不动!

这是为什么? 官网给出了 Slider 的自定义说明文档: Slider Styles and Templates | Microsoft Docs, 根据文档说明代码, 你会发现组合 Track的代码:

    <Track Grid.Column="1"
           x:Name="PART_Track">
      <Track.DecreaseRepeatButton>
        <RepeatButton Style="{StaticResource SliderButtonStyle}"
                      Command="Slider.DecreaseLarge" />
      </Track.DecreaseRepeatButton>
      <Track.Thumb>
        <Thumb Style="{StaticResource SliderThumbStyle}" />
      </Track.Thumb>
      <Track.IncreaseRepeatButton>
        <RepeatButton Style="{StaticResource SliderButtonStyle}"
                      Command="Slider.IncreaseLarge" />
      </Track.IncreaseRepeatButton>
    </Track>

经过一步步删除代码发现, 当删除x:Name=PART_Track 这部分的时候, 滑块就无法滑动!

所以自定义时一定要给 Track 添加 x:Name=PART_Track, 保证滑块可以滑动!!!
所以自定义时一定要给 Track 添加 x:Name=PART_Track, 保证滑块可以滑动!!!
所以自定义时一定要给 Track 添加 x:Name=PART_Track, 保证滑块可以滑动!!!

这是个很不起眼的坑, 请多加小心.

为什么没有这个名字就无法滑动呢?

我们打开 Slider 的源码查看:

namespace System.Windows.Controls
{
  /// <summary>Represents a control that lets the user select from a range of values by moving a <see cref="P:System.Windows.Controls.Primitives.Track.Thumb" /> control along a <see cref="T:System.Windows.Controls.Primitives.Track" />.</summary>
  [Localizability(LocalizationCategory.Ignore)]
  [DefaultEvent("ValueChanged")]
  [DefaultProperty("Value")]
  [TemplatePart(Name = "PART_Track", Type = typeof (Track))]
  [TemplatePart(Name = "PART_SelectionRange", Type = typeof (FrameworkElement))]
  public class Slider : RangeBase
  {
    //...其他代码
    private const string TrackName = "PART_Track";
    private const string SelectionRangeElementName = "PART_SelectionRange";
    //...其他代码

    public override void OnApplyTemplate()
    {
      base.OnApplyTemplate();
      this.SelectionRangeElement = this.GetTemplateChild("PART_SelectionRange") as FrameworkElement;
      this.Track = this.GetTemplateChild("PART_Track") as Track;
      if (this._autoToolTip == null)
        return;
      this._autoToolTip.PlacementTarget = this.Track != null ? (UIElement) this.Track.Thumb : (UIElement) null;
    }
  }

在其源码内部的是直接硬编码了 PART_Track 这个名称的, 所以如果不写名字或者修改名字都会导致无法滑动.