WPF滑块有两个拇指

时间:2011-03-22 18:21:42

标签: wpf templates wpf-controls slider

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

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

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

是否有人有一个滑块样本,其中包含两个拇指,刻度线和捕捉到勾选功能?我发现的所有范围滑块样本都使用两个滑块相互叠加,没有一个允许刻度线或捕捉刻度线。

感谢。

2 个答案:

答案 0 :(得分:18)

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

我已经查看了大量关于该主题的样本和文章,虽然我没有找到一个专门启用滴答的文章,但是有足够的信息供我查明。我发现one article特别好,因为它相当清晰,而且同时也揭示了一些非常有用的技术,这些技术是完成这项任务的关键。

我的最终结果如下:

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

就我而言,我只需要来自基础滑块的MinimumMaximumIsSnapToTickEnabledTickFrequencyTickPlacementTicks ,以及两个要映射到各个滑块值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));

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

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

现在,需要将这些内容连接到构成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>

请注意,在这里,我已经为UserControl提供了名称&#34; root&#34;,并在Binding声明中引用了该名称。大多数属性直接转到UserControl中的相同属性,但当然每个Value控件的各个Slider属性都会映射到相应的LowerValue和{{ 1}} UpperValue的属性。

现在,这是最棘手的部分。如果你只是停在这里,你会得到这样的东西: incorrect DoubleThumbSlider 第二个UserControl对象完全位于第一个对象之上,导致其轨迹覆盖第一个Slider拇指。它不仅仅是一个视觉问题;位于顶部的第二个Slider对象会收到所有鼠标点击,从而阻止首先调整Slider

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

  1. 切换到&#34;设计&#34;您的Slider
  2. 的WPF设计器中的模式
  3. 右键单击滑块并选择&#34;编辑模板/编辑副本...&#34;从弹出菜单
  4. 这很容易。 :)这将为UserControl XAML中的Style声明添加Slider属性,引用您刚刚创建的新样式。

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

    我使用Visual Studio&#34; Go To Definition&#34;功能快速到达我需要的模板部分:在Slider的{​​{1}}中找到Style属性,点击样式名称,然后按 F12 < / KBD>。这会将您带到主Slider对象,您可以在其中找到水平模板的UserControl(垂直模板由Style中的Setter控制}基于Setter值)。单击水平模板的名称(它是&#34; SliderHorizo​​ntal&#34;当我这样做时,但我想它可能会改变,当然对于其他类型的控件也会有所不同。)

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

    Fill

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

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

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

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

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

    我将这些任务留给读者练习。 :)

答案 1 :(得分:2)

我最后修改了原帖中链接中的那个。我将它与TickBar结合起来并自己进行计算,以便提供快照功能。