我想使用RangeSlider,而不使用extendToolkit包。 所以我做了一个 RangeSlider 并开始测试。
NormalSlider 在没有 Bindingmode.Twoway 的情况下也能正常工作,所以我没有向任何绑定添加twoway。
当我拖动普通滑块时,这个值会改变。
当我拖动rangeSlider时,这个值会改变。
我再次拖动normalSlider,这个值就改变了;
当我拖动普通滑块时,这个值应该改变。
当我拖动 rangeSlider 时,这个值应该改变。
<Window Name="window" x:Class="CommonTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CommonTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Background="Gray" DataContext="{Binding ElementName=window}">
<StackPanel>
<Slider Name="normalSlider" Value="{Binding TTTTTT.LowValue}" Maximum="100" Minimum="0"/>
<TextBlock Text="Binding Element"/>
<TextBlock Text="{Binding Value,ElementName=normalSlider}"/>
<TextBlock Text="Binding TTTTTT"/>
<TextBlock Text="{Binding TTTTTT.LowValue}"/>
<local:RangeSlider Name="rangeSlider" ValueChanged="rangeSlider_ValueChanged" IsRangeVisiable="True" LowValue="{Binding TTTTTT.LowValue}" HighValue="{Binding TTTTTT.HighValue}" Minimum="0" Maximum="100"/>
<TextBlock Text="Binding Element"/>
<TextBlock Text="{Binding LowValue,ElementName=rangeSlider}"/>
<TextBlock Text="Binding TTTTTT"/>
<TextBlock Text="{Binding TTTTTT.LowValue}"/>
</StackPanel>
</Grid>
</Window>
这里的背后代码
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public bool Set<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(field, value))
return false;
field = value;
RaiseOnPropertyChanged(propertyName);
return true;
}
private void RaiseOnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
private MyTestValues tTTTTT;
public MyTestValues TTTTTT { get => tTTTTT; set => Set(ref tTTTTT, value); }
public MainWindow()
{
TTTTTT = new MyTestValues { HighValue = 100, LowValue = 0 };
InitializeComponent();
}
private void rangeSlider_ValueChanged(SliderValueType arg1, double arg2)
{
Console.WriteLine("Range Slider Value is Changed");
}
}
public class MyTestValues : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public bool Set<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(field, value))
return false;
field = value;
RaiseOnPropertyChanged(propertyName);
return true;
}
private void RaiseOnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
private double lowValue;
private double highValue;
public double LowValue { get => lowValue; set => Set(ref lowValue, value); }
public double HighValue { get => highValue; set => Set(ref highValue, value); }
}
这是我的 RangeSlider 代码
<UserControl Name="userControl" x:Class="Common.Control.RangeSlider"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:cvt="clr-namespace:Common.Control.Converters"
xmlns:local="clr-namespace:Common.Control"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<ResourceDictionary>
<cvt:BoolToVisibilityConverter x:Key="cvtBoolToVisibility"/>
<Style TargetType="Thumb">
<Setter Property="Background" Value="Red"/>
<Setter Property="Width" Value="30"/>
<Setter Property="Height" Value="20"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Border Background="{Binding Background, RelativeSource={RelativeSource TemplatedParent}}" CornerRadius="10"/>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Green"/>
</Trigger>
<Trigger Property="IsDragging" Value="True">
<Setter Property="Background" Value="Blue"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<Grid PreviewMouseDown="rangeSliderArea_PreviewMouseDown" Margin="10" DataContext="{Binding ElementName=userControl}">
<Border Name="backGroundArea" Height="5"/>
<Border Name="rangeArea" Height="5" Background="White" Visibility="{Binding IsRangeVisiable,Converter={StaticResource cvtBoolToVisibility}}"/>
<Grid Name="rangeSliderArea">
<Thumb Name="lowThumb" Thumb.DragStarted="DragStarted" Thumb.DragCompleted="DragCompleted" Thumb.DragDelta="DragDelta">
<Thumb.RenderTransform>
<TranslateTransform X="{Binding LowPosX}"/>
</Thumb.RenderTransform>
</Thumb>
<Thumb Name="highThumb" Thumb.DragStarted="DragStarted" Thumb.DragCompleted="DragCompleted" Thumb.DragDelta="DragDelta">
<Thumb.RenderTransform>
<TranslateTransform X="{Binding HighPosX}"/>
</Thumb.RenderTransform>
</Thumb>
</Grid>
</Grid>
</UserControl>
namespace Common.Control
{
public enum SliderValueType
{
Low,
High
}
public partial class RangeSlider : UserControl, INotifyPropertyChanged
{
#region DP
public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(RangeSlider), new PropertyMetadata(false));
public static readonly DependencyProperty IsRangeVisiableProperty = DependencyProperty.Register("IsRangeVisiable", typeof(bool), typeof(RangeSlider), new PropertyMetadata(true));
public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register("Maximum", typeof(double), typeof(RangeSlider), new PropertyMetadata(100.0, MaximumPropertyChanged));
public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register("Minimum", typeof(double), typeof(RangeSlider), new PropertyMetadata(0.0, MinimumPropertyChanged));
public static readonly DependencyProperty LowValueProperty = DependencyProperty.Register("LowValue", typeof(double), typeof(RangeSlider), new PropertyMetadata(0.0, LowValuePropertyChanged));
public static readonly DependencyProperty HighValueProperty = DependencyProperty.Register("HighValue", typeof(double), typeof(RangeSlider), new PropertyMetadata(0.0, HighValuePropertyChanged));
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register("Orientation", typeof(Orientation), typeof(RangeSlider), new PropertyMetadata(Orientation.Horizontal, OrientationPropertyChanged));
public bool IsReadOnly { get => (bool)GetValue(IsReadOnlyProperty); set => SetValue(IsReadOnlyProperty, value); }
public bool IsRangeVisiable { get => (bool)GetValue(IsRangeVisiableProperty); set => SetValue(IsRangeVisiableProperty, value); }
public double Maximum { get => (double)GetValue(MaximumProperty); set => SetValue(MaximumProperty, value); }
public double Minimum { get => (double)GetValue(MinimumProperty); set => SetValue(MinimumProperty, value); }
public double LowValue { get => (double)GetValue(LowValueProperty); set => SetValue(LowValueProperty, value); }
public double HighValue { get => (double)GetValue(HighValueProperty); set => SetValue(HighValueProperty, value); }
public Orientation Orientation { get => (Orientation)GetValue(OrientationProperty); set => SetValue(OrientationProperty, value); }
private static void MaximumPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var sender = d as RangeSlider;
sender.UpdateView(SliderValueType.Low);
sender.UpdateView(SliderValueType.High);
}
private static void MinimumPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var sender = d as RangeSlider;
sender.UpdateView(SliderValueType.Low);
sender.UpdateView(SliderValueType.High);
}
private static void LowValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var sender = d as RangeSlider;
sender.CheckInRange(SliderValueType.Low);
sender.UpdateView(SliderValueType.Low);
sender.ValueChanged?.Invoke(SliderValueType.Low, sender.LowValue);
}
private static void HighValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var sender = d as RangeSlider;
sender.CheckInRange(SliderValueType.High);
sender.UpdateView(SliderValueType.High);
sender.ValueChanged?.Invoke(SliderValueType.High, sender.HighValue);
}
private static void OrientationPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var sender = d as RangeSlider;
}
#endregion
#region Variables
private double lowPosX;
private double highPosX;
#endregion
#region Events
public event Action<SliderValueType, double> ValueChanged;
public event PropertyChangedEventHandler PropertyChanged;
public bool Set<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(field, value))
return false;
field = value;
RaiseOnPropertyChanged(propertyName);
return true;
}
private void RaiseOnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
#endregion
#region Properties
private double Range_value => Maximum - Minimum;
private double SliderWidth_Pixel => rangeSliderArea.ActualWidth - lowThumb.Width;
private double ValuePerPixel => SliderWidth_Pixel / Range_value;
public double LowPosX { get => lowPosX; set => Set(ref lowPosX, value); }
public double HighPosX { get => highPosX; set => Set(ref highPosX, value); }
#endregion
#region Constructor
public RangeSlider()
{
InitializeComponent();
Loaded += RangeSlider_Loaded;
SizeChanged += RangeSlider_SizeChanged;
}
#endregion
#region Finalizer
~RangeSlider()
{
}
#endregion
#region Functions
private void UpdateView(SliderValueType valueType)
{
switch (valueType)
{
case SliderValueType.Low:
if (Orientation == Orientation.Horizontal)
{
var distanceFromLeft_pixel = (LowValue - Minimum) * ValuePerPixel;
var distanceHighLow = (HighValue - LowValue) * ValuePerPixel;
rangeArea.Margin = new Thickness(distanceFromLeft_pixel, 0, SliderWidth_Pixel - distanceFromLeft_pixel - distanceHighLow, 0);
LowPosX = distanceFromLeft_pixel;
}
else
{
}
break;
case SliderValueType.High:
if (Orientation == Orientation.Horizontal)
{
var distanceFromLeft_pixel = (HighValue - Minimum) * ValuePerPixel;
var distanceHighLow = (HighValue - LowValue) * ValuePerPixel;
rangeArea.Margin = new Thickness(distanceFromLeft_pixel - distanceHighLow, 0, SliderWidth_Pixel - distanceFromLeft_pixel, 0);
HighPosX = distanceFromLeft_pixel;
}
else
{
}
break;
}
}
private void CheckInRange(SliderValueType sliderValue)
{
switch (sliderValue)
{
case SliderValueType.Low:
if (LowValue > HighValue) LowValue = HighValue;
if (LowValue < Minimum) LowValue = Minimum;
break;
case SliderValueType.High:
if (HighValue < LowValue) HighValue = LowValue;
if (HighValue > Maximum) HighValue = Maximum;
break;
}
}
private void MoveBlockTo(Point point, SliderValueType block)
{
double position;
if (Orientation == Orientation.Horizontal)
{
position = point.X;
}
else
{
position = point.Y;
}
var value = Math.Min(Maximum, Minimum + (position / SliderWidth_Pixel) * (Maximum - Minimum));
if (block == SliderValueType.Low)
{
LowValue = value;
}
else if (block == SliderValueType.High)
{
HighValue = value;
}
}
#region Event_Functions
private void RangeSlider_Loaded(object sender, RoutedEventArgs e)
{
UpdateView(SliderValueType.Low);
UpdateView(SliderValueType.High);
}
private void RangeSlider_SizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateView(SliderValueType.Low);
UpdateView(SliderValueType.High);
}
private void rangeSliderArea_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (!IsReadOnly)
{
if ((lowThumb != null && lowThumb.IsMouseOver) || (highThumb != null && highThumb.IsMouseOver))
return;
var point = e.GetPosition(rangeSliderArea);
if (e.ChangedButton == MouseButton.Left)
{
MoveBlockTo(point, SliderValueType.Low);
}
else if (e.ChangedButton == MouseButton.Right)
{
MoveBlockTo(point, SliderValueType.High);
}
e.Handled = true;
}
}
private void DragStarted(object sender, DragStartedEventArgs e)
{
}
private void DragCompleted(object sender, DragCompletedEventArgs e)
{
}
private void DragDelta(object sender, DragDeltaEventArgs e)
{
if (!IsReadOnly && e.OriginalSource is Thumb thumb && rangeSliderArea != null)
{
double changedValue = 0.0;
if (Orientation == Orientation.Horizontal)
{
var currentChange_PixelWidth = e.HorizontalChange;
changedValue = currentChange_PixelWidth / ValuePerPixel;
}
if (thumb == lowThumb)
{
LowValue += changedValue;
}
else if (thumb == highThumb)
{
HighValue += changedValue;
}
}
}
#endregion
#endregion
}
}
您是否知道为什么默认 Slider 的 Value 继承了 RangeBase.Value 依赖属性在 TwoWay 模式下工作而无需在 xaml 中显式设置?这是因为该依赖属性是通过指定
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault
来定义的。请参阅来源。
如果您想让 LowValue 依赖属性工作而无需在 xaml 中显式设置 TwoWay,则需要按照如下所示的相同方式定义它。
public static readonly DependencyProperty LowValueProperty = DependencyProperty.Register(
"LowValue",
typeof(double),
typeof(RangeSlider),
new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, LowValuePropertyChanged));
顺便说明一下,对于继承 DependencyObject 的视图元素的数据绑定,依赖属性比添加 INotifyPropertyChanged 更合适。