如果您想避免使用 UserControl 实现自定义滑块所带来的所有麻烦,您可以直接使用带有自定义控件模板的 Slider 控件。
控件模板
自定义控件模板将定义呈现自定义滑块所需的所有 UI 元素;像两个标签、一个“内部”滑块控件(UI 中显示的实际滑块)以及任何其他所需/必需的 UI 元素。
这样一个简单形式的控件模板的外观(不完整)说明:
<Slider
TickPlacement="Both"
TickFrequency="0.5"
BorderBrush="Red"
BorderThickness="3" >
<Slider.Template>
<ControlTemplate TargetType="Slider">
<Border
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="Left label" Grid.Column="0" Grid.Row="0" />
<TextBlock Text="Right label" Grid.Column="1" Grid.Row="0" HorizontalAlignment="Right" />
<Slider Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1"
BorderThickness="0"
Value="{Binding Value, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
TickFrequency="{TemplateBinding TickFrequency}
TickPlacement="{TemplateBinding TickPlacement}"
...
/>
</Grid>
</Border>
</ControlTemplate>
</Slider.Template>
</Slider>
请注意,应该传递给“内部”滑块的“外部”滑块的所有属性都应该具有对应于“内部”滑块的 TemplateBindings(如 Value 所示,TickFrequency 和 TickPlacement 属性)。
注意Value属性的模板绑定。对于任何需要双向的模板绑定,速记形式{TemplateName SourceProperty} 不会因为它是单向的。对于双向模板绑定,应将绑定声明为{Binding SourceProperty, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}。
您可能不希望通过“内部”滑块传递“外部”滑块的某些属性。也许您希望边框(滑块控件支持边框)与文本标签一起围绕滑块。对于这种情况,我上面的示例控件模板具有围绕“内部”滑块和文本标签的 Border 元素。请注意,使用控件模板的“外部”滑块控件中与边框相关的属性被模板绑定到此 Border 元素,而不是“内部”滑块。
但是,如何处理边框仍然存在问题。如果您(或其他人)有时为包含边框的滑块定义了默认样式,则此默认样式的相应边框参数也将应用于“内部”滑块——这是不受欢迎的。为了防止这种情况发生,我的示例控件模板为“内部”滑块显式设置了 BorderThickness 的值。
对控件模板中您不希望受其各自默认样式影响的 UI 元素的任何其他属性,以类似方式执行此操作。
标签文本的附加属性
如果您希望能够通过绑定更改标签的文本,则需要为它们引入一些属性。实现这一点的一种方法是将它们实现为附加属性。它们的实现可能相当简单:
public static class SliderExtensions
{
public static string GetLeftLabel(DependencyObject obj)
{
return (string)obj.GetValue(LeftLabelProperty);
}
public static void SetLeftLabel(DependencyObject obj, string value)
{
obj.SetValue(LeftLabelProperty, value);
}
public static readonly DependencyProperty LeftLabelProperty = DependencyProperty.RegisterAttached(
"LeftLabel",
typeof(string),
typeof(SliderExtensions)
);
public static string GetRightLabel(DependencyObject obj)
{
return (string)obj.GetValue(RightLabelProperty);
}
public static void SetRightLabel(DependencyObject obj, string value)
{
obj.SetValue(RightLabelProperty, value);
}
public static readonly DependencyProperty RightLabelProperty = DependencyProperty.RegisterAttached(
"RightLabel",
typeof(string),
typeof(SliderExtensions)
);
}
上面的代码提供了两个附加属性(类型为string),称为“SliderExtensions.LeftLabel”和“SliderExtensions.RightLabel”。然后,您可以按如下方式使用它们:
<Slider
local:SliderExtensions.LeftLabel="Port"
local:SliderExtensions.RightLabel="Starboard"
TickPlacement="Both"
TickFrequency="0.5"
BorderBrush="Red"
BorderThickness="3" >
<Slider.Template>
<ControlTemplate TargetType="Slider">
...
<TextBlock Text="{TemplateBinding local:SliderExtensions.LeftLabel}" Grid.Column="0" Grid.Row="0" />
<TextBlock Text="{TemplateBinding local:SliderExtensions.RightLabel}" Grid.Column="1" Grid.Row="0" HorizontalAlignment="Right" />
...
...
具有标签文本依赖属性的自定义滑块类
如果您不想使用附加属性,您还可以从标准 Slider 类派生自己的自定义滑块控件,并将 LeftLabel 和 RightLabel 实现为依赖属性这个自定义滑块类。
public class MyCustomSlider : Slider
{
public string LeftLabel
{
get { return (string)GetValue(LeftLabelProperty); }
set { SetValue(LeftLabelProperty, value); }
}
public static readonly DependencyProperty LeftLabelProperty = DependencyProperty.Register(
nameof(LeftLabel),
typeof(string),
typeof(MyCustomSlider)
);
public string RightLabel
{
get { return (string)GetValue(RightLabelProperty); }
set { SetValue(RightLabelProperty, value); }
}
public static readonly DependencyProperty RightLabelProperty = DependencyProperty.Register(
nameof(RightLabel),
typeof(string),
typeof(MyCustomSlider)
);
}
这种方法还有一个额外的好处,就是您可以专门为您的 MyCustomSlider 类型定义默认样式。有关该主题的更多信息,请参阅this answer。
如果你更喜欢这样做,那么别忘了调整控件模板的目标类型:
<local:MyCustomSlider
LeftLabel="Port"
RightLabel="Starboard"
TickPlacement="Both"
TickFrequency="0.5"
BorderBrush="Red"
BorderThickness="3" >
<local:MyCustomSlider.Template>
<ControlTemplate TargetType="TargetType="local:MyCustomSlider"">
...
<TextBlock Text="{TemplateBinding LeftLabel}" Grid.Column="0" Grid.Row="0" />
<TextBlock Text="{TemplateBinding RightLabel}" Grid.Column="1" Grid.Row="0" HorizontalAlignment="Right" />
...
...
您不必为标签使用字符串属性和 TextBlocks。您可以使用ContentPresenter 代替TextBlock 并制作object 类型的LeftLabel 和RightLabel 属性。这样您仍然可以使用文本标签,但如果需要的话,您也可以使用任何其他内容(例如图像)。
旁注:我的回答是基于标准的 WPF 滑块控件。如果您使用的是 MahApps.Metro 提供的滑块控件(我对此知之甚少),某些细节(例如名称、类型、属性的存在或不存在)可能与我的回答显示的不同。