44

WPF自定义Slider样式采坑记录

 4 years ago
source link: http://blog.devwiki.net/index.php/2019/12/30/wpf-custom-slider-style-note.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

0x01. 概要

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

vq2UJnU.png!web

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

qIBriuJ.png!web

0x02. Slider组成

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

ueQf6za.png!web

从上图我们可以看出, 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 已经和上面的图一样了:

qIBriuJ.png!web

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

这是为什么? 官网给出了 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 这个名称的, 所以如果不写名字或者修改名字都会导致无法滑动.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK