带有两个拇指的 WPF 滑块 [英] WPF slider with two thumbs

查看:31
本文介绍了带有两个拇指的 WPF 滑块的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to create a slider with two thumbs for my app, to use as a range slider, but am running into issues. The basic requirement for me is to get a single slider with tick marks and two thumbs, which are both set with IsSnapToTickEnabled="true".

我在搜索帮助时发现了一些范围滑块的示例(例如 这个) 但我无法修改它以添加刻度线并强制拇指对齐刻度.不过,为链接中的范围滑块获取刻度线和捕捉将是理想的选择.

I found some samples of range sliders when searching for help (for example this one) but I was not able to modify it to add tick marks and force the thumbs to snap to ticks. Getting tick marks and snapping working for the range slider in the link would be ideal though.

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

I tried modifying a slider's template and adding another thumb to it but then I do not know how to get the value of the selected thumb.

是否有人提供带有两个拇指、刻度线和对齐刻度的滑块示例?我发现的所有范围滑块示例都使用两个相互重叠的滑块,并且没有一个允许刻度线或对齐刻度线.

Does anyone have a sample of a slider with two thumbs, tick marks, and snap to tick enabled? All of the range slider samples I have found use two sliders on top of each other and none of them allow for tick marks or snapping to tick marks.

谢谢.

推荐答案

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

I realize that this question is over three years old. However, I've been using the example of a slider with more than one thumb as an exercise to learn more about WPF, and came across this question when I was trying to figure out how to do this. Unfortunately, the linked example appears to no longer exist (a good example of why StackOverflow questions and answers should not use links for any detail that is critical for the question or answer).

我查看了大量关于该主题的示例和文章,虽然我没有找到专门启用刻度线的示例和文章,但那里有足够的信息让我弄清楚.我发现一篇文章特别好,因为它相当清晰和中肯,同时揭示了一些非常有用的技术,这些技术是完成这项任务的关键.

I've looked at a large number of samples and articles on the topic, and while I did not find one that specifically enabled ticks, there was enough information there for me to figure it out. I found one article particularly good, in that it was reasonably clear and to the point, and at the same time revealed a couple of really useful techniques that are key in accomplishing this task.

我的最终结果是这样的:

My end result looks like this:

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

So for the benefit of others who may want to either do the same thing, or who would simply like to understand the general techniques better, here's how you make a two-thumb slider control that supports the various tick features of the basic slider…


起点是 UserControl 类本身.在 Visual Studio 中,向项目添加一个新的 UserControl 类.现在,添加您想要支持的所有属性.不幸的是,我还没有找到一种机制,可以简单地将属性委托给 UserControl 中适当的滑块实例,因此这意味着为每个滑块实例编写新的属性.


The starting point is the UserControl class itself. In Visual Studio, add a new UserControl class to the project. Now, add all the properties that you want to support. Unfortunately, I have not found a mechanism that would allow simply delegating the properties to the appropriate slider instance(s) in the UserControl, so this means writing new properties for each one.

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

Working from the prerequisites (i.e. the members required by the other members to be declared), one of the features I wanted was to limit the travel of each slider so it could not be dragged past the other. I decided to implement this using CoerceValueCallback for the properties, so I needed the callback methods:

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);
}

就我而言,我只需要 MinimumMaximumIsSnapToTickEnabledTickFrequencyTickPlacement 和来自底层滑块的 Ticks,以及两个映射到各个滑块值的新属性,LowerValueHigherValue.首先,我必须声明 DependencyProperty 对象:

In my case, I only needed Minimum, Maximum, IsSnapToTickEnabled, TickFrequency, TickPlacement, and Ticks from the underlying sliders, and two new properties to map to the individual slider values, LowerValue and HigherValue. First, I had to declare the DependencyProperty objects:

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));

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

That done, I could now write the properties themselves:

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); }
}

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

Now, these need to be hooked up to the underlying Slider controls that will make up the UserControl. So I added the two Slider controls, with bindings for the properties to the appropriate properties in my 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>

请注意,在这里,我给我的 UserControl 命名为root",并在 Binding 声明中引用了它.大多数属性直接指向 UserControl 中的相同属性,但当然每个 Slider 控件的各个 Value 属性都映射到UserControl 的适当 LowerValueUpperValue 属性.

Note that here, I've given my UserControl the name "root", and referenced that in the Binding declarations. Most of the properties go directly to the identical property in the UserControl, but of course the individual Value properties for each Slider control is mapped to the appropriate LowerValue and UpperValue property of the UserControl.

现在,这是最棘手的部分.如果你只是停在这里,你会得到如下所示的内容:第二个 Slider 对象完全位于第一个对象的顶部,导致其轨道覆盖第一个 Slider 拇指.这也不仅仅是视觉问题;位于顶部的第二个 Slider 对象接收所有鼠标点击,完全防止第一个 Slider 被调整.

Now, here's the trickiest part. If you just stop here, you'll get something that looks like this: The second Slider object is entirely on top of the first, causing its track to cover up the first Slider thumb. It's not just a visual problem either; the second Slider object, being on top, receives all of the mouse clicks, preventing the first Slider from being adjusted at all.

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

To fix this, I edited the style for the second slider to remove those visual elements that get in the way. I left them for the first slider, to provide the actual track visuals for the control. Unfortunately, I could not figure out a way to declaratively override just the parts I needed to change. But using Visual Studio, you can create a whole copy of the existing style, which can then be edited as needed:

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

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

It's that easy. :) This will add a Style attribute to the Slider declaration in your UserControl XAML, referencing the new style you just created.

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

The Slider control actually has two main control templates, one for the horizontal orientation and one for the vertical. I'll describe the changes to the horizontal template here; I assume it will be obvious how to make similar changes to the vertical template.

我使用 Visual Studio 的转到定义"功能快速到达我需要的模板部分:在 Slider 中找到 Style 属性>UserControl,单击样式名称并按F12.这将带您到主要的 Style 对象,在那里您将找到用于水平模板的 Setter(垂直模板由 Setter> 在基于 Orientation 值的 Trigger 中).单击水平模板的名称(我这样做时它是SliderHorizo​​ntal",但我想它可能会改变,当然其他类型的控件会有所不同).

I use Visual Studio's "Go To Definition" feature to quickly get to the part of the template I need: find the Style attribute in the Slider of your UserControl, click on the name of the style and press F12. That will take you to the main Style object, where you'll find a Setter for the horizontal template (the vertical template is controlled by a Setter in a Trigger based on the Orientation value). Click on the name of the horizontal template (it was "SliderHorizontal" when I did this, but I guess it could change, and of course would be different for other types of controls).

进入ControlTemplate 后,从不应使用的元素中删除所有视觉属性.这意味着删除一些元素,并从元素中删除 BackgroundBorderBrushBorderThicknessFill 等.不能完全去除.就我而言,我完全删除了 RepeatButton ,并修改了我需要的其他元素,以便它们不显示或占用任何空间(因此它们不会收到鼠标点击).我结束了这个:

Once you get to the ControlTemplate, remove all of the visual attributes from the elements that should not be used. This means removing some elements, and removing Background, BorderBrush, BorderThickness, Fill, etc. from the elements you can't remove entirely. In my case, I removed the RepeatButtons entirely, and modified the other elements I needed to so that they didn't show up or take up any space (so they wouldn't receive mouse clicks). I wound up with this:

<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>

这就是全部.:)

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

One last thing: the above assumes that the stock style for a Slider won't change. I.e. the second slider's style was copied and hard-coded into the program, but that hard-coded style still depends on the layout of the stock style from which it was copied. If that stock style changes, then the layout of the first slider could change in a way that makes the second slider no longer line up or otherwise look correct.

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

If that is a concern, then you can approach the templating somewhat differently: instead of modifying the SliderHorizontal template, make a copy of it and the Style that references it, changing the names of both, and changing the copy of the Style so that it references the copied template instead of the original. Then you just modify the copy, and set the style of the first Slider to the unmodified Style, and the style of the second Slider to the modified one.

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

Beyond the techniques demonstrated here, others may want to do things slightly differently. For example, I discarded the repeat buttons altogether, which means you can only drag the thumb. Clicking in the track outside of the thumb doesn't affect it. Also, the thumbs still work as they do in the basic Slider control, with the middle of the thumb being the indicator for where the thumb value is. This means that when you drag one thumb to the other, they wind up with the second thumb on top of the first (i.e. the first thumb won't be draggable until you move the second thumb enough to see the first).

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

Changing these behaviors should not be too hard, but it does involve extra work. You can add a margin to the thumbs to keep them from overlaying each other (but then you'll want to also change the thumb shape when the ticks are shown, as well as also adjust the track margin, so that everything still lines up). Instead of removing the repeat buttons, you could leave them in but adjust their positions so that they operated the way you want with the two thumbs.

我将这些任务留给读者作为练习.:)

I leave those tasks as an exercise for the reader. :)

这篇关于带有两个拇指的 WPF 滑块的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆