我意识到这个问题了三岁。不过,我一直使用带有多个拇指的滑块作为练习来学习更多关于WPF的例子,并且在我试图弄清楚如何做到这一点时遇到了这个问题。不幸的是,这个链接的例子似乎不再存在(一个很好的例子说明为什么StackOverflow的问题和答案不应该使用链接来提供对问题或答案至关重要的任何细节)。
我看了大量关于该主题的样本和文章,虽然我没有找到一个专门启用蜱,有足够的信息,有我的数字出来。我发现one article特别好,因为它非常清楚并且重点突出,同时还揭示了一些非常有用的技术,这些技术是完成这项任务的关键。
我的最终结果是这样的:
所以对于谁可能想要么做同样的事情其他人的利益,或谁只是想了解一般的技术比较好,这里是如何您制作了一个双拇指滑块控件,该控件支持基本滑块&hellip的各种打勾功能;
起点是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);
}
在,我只需要我的情况Minimum
,Maximum
,IsSnapToTickEnabled
,TickFrequency
,TickPlacement
和Ticks
从底层滑块和两个新属性映射到各个滑块值LowerValue
和HigherValue
。首先,我不得不宣布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
命名为“根”,并引用即在Binding
声明中。大多数属性直接转到UserControl
中的相同属性,但当然每个Slider
控件的个人属性都映射到UserControl
的适当LowerValue
和UpperValue
属性。
现在,这是最棘手的部分。如果你只是停在这里,你会得到的东西看起来是这样的: 第二Slider
对象是完全在第一的顶部,导致其轨道掩盖第一Slider
大拇指。这不仅仅是一个视觉问题;顶部的第二个对象接收所有的鼠标点击,从而防止第一个被调整。
为了解决这个问题,我编辑了第二个滑块的样式,以去除妨碍这些视觉元素的操作。我将它们留给第一个滑块,以提供控件的实际轨道视觉效果。不幸的是,我找不到一种方法来声明性地覆盖我需要改变的部分。但是,使用Visual Studio,您可以创建现有的样式,根据需要,然后可以编辑的整个副本:
- 切换到WPF设计“设计”模式,您
UserControl
- 右击然后从弹出式菜单中选择“编辑模板/编辑副本...”
就这么简单。 :)这将在您的UserControl
XAML中的Slider
声明中添加一个Style
属性,引用您刚创建的新样式。
Slider
控件实际上有两个主要的控件模板,一个用于水平方向,一个用于垂直方向。我将在这里描述对水平模板的更改;我认为如何对垂直模板进行类似的更改将是显而易见的。
我使用Visual Studio的“转到定义”功能,迅速得到我所需要的模板的一部分:找到你的UserControl
的Slider
的Style
属性,单击样式的名称,然后按F12。这将带您到主对象Style
,您将在其中找到水平模板的Setter
(根据Orientation
的值,垂直模板由Setter
以Trigger
控制)。点击水平模板的名称(当我这样做的时候是“SliderHorizontal”,但我猜它可能会改变,当然对于其他类型的控件会有所不同)。
一旦你到达ControlTemplate
,从不应该使用的元素中删除所有的视觉属性。这意味着删除一些元素,并从无法完全删除的元素中删除Background
,BorderBrush
,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
,并将第二个Slider
的样式设置为修改的样式。
除了这里展示的技术外,其他人可能希望做的事情略有不同。例如,我完全放弃了重复按钮,这意味着您只能拖动拇指。点击拇指外的轨道不会影响它。此外,大拇指仍然像在基本控制中那样工作,拇指的中间是拇指值的指示符。这意味着,当您将一个拇指拖到另一个拇指时,他们会将第二个拇指放在第一个拇指的上方(即,第一个拇指不可拖动,直到您移动第二个拇指才能看到第一个拇指)。
改变这些行为不应该太难,但确实需要额外的工作。您可以在大拇指上添加一个边距,以防止它们彼此重叠(但是,您还需要在显示刻度时更改拇指形状,以及调整轨道边距,以使所有内容都排成一行) 。您可以将它们放入,但不要移除重复按钮,而是调整其位置,以便按照您想要的方式使用两个拇指进行操作。
我将这些任务作为练习给读者。 :)
我想要做同样的事情。你介意分享结果吗? – Lukas 2011-07-26 22:02:29
@Lukas:我希望毕竟这一次,你能找到一个适合你的解决方案。但为了以防万一,请注意,我已经为这个问题添加了一个答案,它提供了所有必要的细节,并在这里提供了一个独立的答案。 – 2014-12-04 06:00:23