WPF滑块有两个拇指

Fla*_*ack 4 wpf templates slider wpf-controls

我正在尝试为我的应用创建一个带有两个拇指的滑块,用作范围滑块,但遇到了问题.我的基本要求是获得一个带有刻度线和两个拇指的滑块,它们都是用IsSnapToTickEnabled ="true"设置的.

我在搜索帮助时找到了一些范围滑块样本(例如这个),但我无法修改它以添加刻度线并强制拇指按住刻度线.获取刻度标记和捕捉链接中的范围滑块的工作将是理想的.

我尝试修改滑块的模板并添加另一个拇指,但后来我不知道如何获取所选拇指的值.

有没有人有一个滑块的样本,有两个拇指,刻度线和捕捉勾选启用?我发现的所有范围滑块样本都使用两个滑块在彼此的顶部,并且它们都不允许刻度线或捕捉刻度线.

谢谢.

Pet*_*iho 19

我意识到这个问题已经超过三年了.但是,我一直在使用带有多个拇指的滑块示例作为练习来了解有关WPF的更多信息,并在我试图弄清楚如何执行此操作时遇到了这个问题.不幸的是,链接的示例似乎不再存在(这是一个很好的例子,说明为什么StackOverflow问题和答案不应该使用链接来表示对问题或答案至关重要的任何细节).

我已经看了很多关于这个主题的样本和文章,虽然我没有找到一个专门启用滴答的文章,但是有足够的信息供我查明.我发现一篇文章特别好,因为它相当清楚,并且在某种程度上,并且同时揭示了一些非常有用的技术,这些技术是完成这项任务的关键.

我的最终结果如下:

正确的DoubleThumbSlider

因此,为了让其他人可能想要做同样的事情,或者只是想更好地理解一般技术的人的利益,这里是你如何制作支持基本滑块的各种刻度功能的双拇指滑块控件...


起点是UserControl班级本身.在Visual Studio中,UserControl向项目添加新类.现在,添加您要支持的所有属性.不幸的是,我还没有找到一种机制,只允许将属性委托给相应的滑块实例UserControl,因此这意味着为每个属性编写新的属性.

根据先决条件(即要声明的其他成员所需的成员),我想要的一个功能是限制每个滑块的行程,使其不能被拖过另一个.我决定使用CoerceValueCallback属性实现这个,所以我需要回调方法:

private static object LowerValueCoerceValueCallback(DependencyObject target, object valueObject)
{
    DoubleThumbSlider targetSlider = (DoubleThumbSlider)target;
    double value = (double)valueObject;

    return Math.Min(value, targetSlider.UpperValue);
}

private static object UpperValueCoerceValueCallback(DependencyObject target, object valueObject)
{
    DoubleThumbSlider targetSlider = (DoubleThumbSlider)target;
    double value = (double)valueObject;

    return Math.Max(value, targetSlider.LowerValue);
}
Run Code Online (Sandbox Code Playgroud)

就我而言,我只需要Minimum,Maximum,IsSnapToTickEnabled,TickFrequency,TickPlacement,并Ticks从底层滑块和两个新的属性映射到单个滑值,LowerValueHigherValue.首先,我必须声明DependencyProperty对象:

public static readonly DependencyProperty MinimumProperty =
    DependencyProperty.Register("Minimum", typeof(double), typeof(DoubleThumbSlider), new UIPropertyMetadata(0d));
public static readonly DependencyProperty LowerValueProperty =
    DependencyProperty.Register("LowerValue", typeof(double), typeof(DoubleThumbSlider), new UIPropertyMetadata(0d, null, LowerValueCoerceValueCallback));
public static readonly DependencyProperty UpperValueProperty =
    DependencyProperty.Register("UpperValue", typeof(double), typeof(DoubleThumbSlider), new UIPropertyMetadata(1d, null, UpperValueCoerceValueCallback));
public static readonly DependencyProperty MaximumProperty =
    DependencyProperty.Register("Maximum", typeof(double), typeof(DoubleThumbSlider), new UIPropertyMetadata(1d));
public static readonly DependencyProperty IsSnapToTickEnabledProperty =
    DependencyProperty.Register("IsSnapToTickEnabled", typeof(bool), typeof(DoubleThumbSlider), new UIPropertyMetadata(false));
public static readonly DependencyProperty TickFrequencyProperty =
    DependencyProperty.Register("TickFrequency", typeof(double), typeof(DoubleThumbSlider), new UIPropertyMetadata(0.1d));
public static readonly DependencyProperty TickPlacementProperty =
    DependencyProperty.Register("TickPlacement", typeof(TickPlacement), typeof(DoubleThumbSlider), new UIPropertyMetadata(TickPlacement.None));
public static readonly DependencyProperty TicksProperty =
    DependencyProperty.Register("Ticks", typeof(DoubleCollection), typeof(DoubleThumbSlider), new UIPropertyMetadata(null));
Run Code Online (Sandbox Code Playgroud)

完成后,我现在可以自己编写属性:

public double Minimum
{
    get { return (double)GetValue(MinimumProperty); }
    set { SetValue(MinimumProperty, value); }
}

public double LowerValue
{
    get { return (double)GetValue(LowerValueProperty); }
    set { SetValue(LowerValueProperty, value); }
}

public double UpperValue
{
    get { return (double)GetValue(UpperValueProperty); }
    set { SetValue(UpperValueProperty, value); }
}

public double Maximum
{
    get { return (double)GetValue(MaximumProperty); }
    set { SetValue(MaximumProperty, value); }
}

public bool IsSnapToTickEnabled
{
    get { return (bool)GetValue(IsSnapToTickEnabledProperty); }
    set { SetValue(IsSnapToTickEnabledProperty, value); }
}

public double TickFrequency
{
    get { return (double)GetValue(TickFrequencyProperty); }
    set { SetValue(TickFrequencyProperty, value); }
}

public TickPlacement TickPlacement
{
    get { return (TickPlacement)GetValue(TickPlacementProperty); }
    set { SetValue(TickPlacementProperty, value); }
}

public DoubleCollection Ticks
{
    get { return (DoubleCollection)GetValue(TicksProperty); }
    set { SetValue(TicksProperty, value); }
}
Run Code Online (Sandbox Code Playgroud)

现在,这些需要连接到将组成的底层Slider控件UserControl.所以我添加了两个Slider控件,将属性绑定到我的相应属性UserControl:

<Grid>
  <Slider x:Name="lowerSlider"
          VerticalAlignment="Center"
          Minimum="{Binding ElementName=root, Path=Minimum}"
          Maximum="{Binding ElementName=root, Path=Maximum}"
          Value="{Binding ElementName=root, Path=LowerValue, Mode=TwoWay}"
          IsSnapToTickEnabled="{Binding ElementName=root, Path=IsSnapToTickEnabled}"
          TickFrequency="{Binding ElementName=root, Path=TickFrequency}"
          TickPlacement="{Binding ElementName=root, Path=TickPlacement}"
          Ticks="{Binding ElementName=root, Path=Ticks}"
          />
  <Slider x:Name="upperSlider"
          VerticalAlignment="Center"
          Minimum="{Binding ElementName=root, Path=Minimum}"
          Maximum="{Binding ElementName=root, Path=Maximum}"
          Value="{Binding ElementName=root, Path=UpperValue, Mode=TwoWay}"
          IsSnapToTickEnabled="{Binding ElementName=root, Path=IsSnapToTickEnabled}"
          TickFrequency="{Binding ElementName=root, Path=TickFrequency}"
          TickPlacement="{Binding ElementName=root, Path=TickPlacement}"
          Ticks="{Binding ElementName=root, Path=Ticks}"
          />
</Grid>
Run Code Online (Sandbox Code Playgroud)

请注意,在这里,我给了我UserControl的名字"root",并在Binding声明中引用了它.大部分属性直接到在相同的属性UserControl,但是当然的个体Value对每个属性Slider控制被映射到适当的LowerValueUpperValue的特性UserControl.

现在,这是最棘手的部分.如果你停在这里,你会得到这样的东西: 不正确的DoubleThumbSlider 第二个Slider物体完全位于第一个物体的顶部,导致其轨迹覆盖第一个Slider拇指.这不仅仅是一个视觉问题; 第二个Slider对象位于顶部,接收所有鼠标点击,防止第一个Slider被调整.

为了解决这个问题,我编辑了第二个滑块的样式,以删除那些阻碍的视觉元素.我将它们留给第一个滑块,为控件提供实际的轨迹视觉效果.不幸的是,我无法想出一种声明性地覆盖我需要改变的部分的方法.但是使用Visual Studio,您可以创建现有样式的完整副本,然后可以根据需要进行编辑:

  1. 切换到WPF设计器中的"设计"模式 UserControl
  2. 右键单击滑块,然后从弹出菜单中选择"编辑模板/编辑副本..."

就这么简单.:)这将在XAML中StyleSlider声明添加一个属性UserControl,引用刚刚创建的新样式.

Slider控件实际上有两个主要的控制模板,一个用于水平方向,另一个用于垂直方向.我将在这里描述对水平模板的更改; 我假设如何对垂直模板进行类似的更改将是显而易见的.

我使用Visual Studio的"转到定义"功能,迅速得到我所需要的模板的一部分:找到Style的属性Slider你的UserControl,单击风格和新闻界的名称F12.这会将您带到主Style对象,您可以在其中找到Setter水平模板(垂直模板由基于值的Settera 控制).单击水平模板的名称(当我这样做时它是"SliderHorizo​​ntal",但我想它可能会改变,当然对于其他类型的控件也会有所不同).TriggerOrientation

到达后ControlTemplate,从不应使用的元素中删除所有可视属性.这意味着,除去一些元素,并移除Background,BorderBrush,BorderThickness,Fill从你不能完全移除元素等.在我的情况下,我RepeatButton完全删除了s,并修改了我需要的其他元素,以便它们不显示或占用任何空间(因此它们不会接收鼠标点击).我结束了这个:

<ControlTemplate x:Key="SliderHorizontal" TargetType="{x:Type Slider}">
  <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True">
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto" MinHeight="{TemplateBinding MinHeight}"/>
        <RowDefinition Height="Auto"/>
      </Grid.RowDefinitions>
      <TickBar x:Name="TopTick" Fill="{TemplateBinding Foreground}" Height="4" Margin="0,0,0,2" Placement="Top" Grid.Row="0" Visibility="Collapsed"/>
      <TickBar x:Name="BottomTick" Fill="{TemplateBinding Foreground}" Height="4" Margin="0,2,0,0" Placement="Bottom" Grid.Row="2" Visibility="Collapsed"/>
      <Border x:Name="TrackBackground" Grid.Row="1" VerticalAlignment="center">
        <Canvas>
          <Rectangle x:Name="PART_SelectionRange" />
        </Canvas>
      </Border>
      <Track x:Name="PART_Track" Grid.Row="1">
        <Track.Thumb>
          <Thumb x:Name="Thumb" Focusable="False" Height="18" OverridesDefaultStyle="True" Template="{StaticResource SliderThumbHorizontalDefault}" VerticalAlignment="Center" Width="11"/>
        </Track.Thumb>
      </Track>
    </Grid>
  </Border>
  <!-- I left the ControlTemplate.Triggers element just as it was, no changes -->
</ControlTemplate>
Run Code Online (Sandbox Code Playgroud)

这就是它的全部内容.:)

最后一件事:上面假设a的股票风格Slider不会改变.即第二个滑块的样式被复制并硬编码到程序中,但该硬编码样式仍取决于复制它的库存样式的布局.如果该库存样式发生更改,则第一个滑块的布局可能会以使第二个滑块不再对齐或看起来正确的方式发生变化.

如果这是一个问题,那么你可以稍微不同地处理模板:而不是修改SliderHorizontal模板,制作它的副本,并Style引用它,更改两者的名称,并更改它的副本,Style以便它引用复制模板而不是原始模板.然后,您只需修改副本,并将第一个样式设置为Slider未修改Style的样式,将第二个样式Slider设置为修改后的样式.

除了这里展示的技术之外,其他人可能希望以稍微不同的方式做事.例如,我完全丢弃了重复按钮,这意味着您只能拖动拇指.单击拇指外的轨道不会影响它.此外,拇指仍然像在基本Slider控制中一样工作,拇指的中间是拇指值的指示器.这意味着当你将一个拇指拖到另一个拇指时,它们会在第一个拇指的顶部向上移动(即第一个拇指不会被拖动,直到你移动第二个拇指足以看到第一个拇指).

改变这些行为不应该太难,但它确实涉及额外的工作.您可以为拇指添加边距以防止它们相互重叠(但是当您显示刻度线时,您还需要更改拇指形状,同时还要调整轨迹边距,以便所有内容仍然排列) .您可以将它们留在但不要移除重复按钮,而是调整它们的位置,以便它们以两个拇指的方式操作.

我把这些任务留给读者练习.:)

  • @ chick3n0x07CC:如果没有该绑定,则绑定将引用当前数据上下文中的任何内容,通常为空,或者为包含特定于程序的属性的视图模型对象,这些属性可能被命名也可能与这些绑定所引用的名称相同。在这种情况下,我们确实希望这些元素专门引用父“ UserControl”属性,该属性向客户端代码提供该接口,而不是依赖于客户端代码来提供具有正确值的任意数据上下文对象。 (2认同)