感谢HandyControl:欢迎使用HandyControl | HandyOrg
经常用到双滑块控件,但是,始终找不到一个特别满意的,然后就基于handycontrol从中提取出来了一个,给自己当个备份同时也帮助一些有需要的朋友,当然我只实现了水平方向的样式,垂直方向的样式改改也能出来,相信你们,都写自定义控件了,多写个样式应该不是啥难事吧,hhh
简单点,先看效果图。
xaml:
<SolidColorBrush x:Key="ThumbColor" Color="#40568d" Opacity=".8" />
<SolidColorBrush x:Key="SelectedRegionColorBrush" Color="#ccd5f0" Opacity=".7" />
<Color x:Key="EffectShadowColor">#88000000</Color>
<Geometry x:Key="RangeSliderThumbGeometry">M333.00313873 32m184.61538469 0l0 0q184.61538469 0 184.61538469 184.61538469l0 590.76923062q0 184.61538469-184.61538469 184.61538469l0 0q-184.61538469 0-184.61538469-184.61538469l0-590.76923062q0-184.61538469 184.61538469-184.61538469ZM499.15698467 308.92307656v443.07692344h36.92307656v-443.07692344zM406.84929186 403.66769188v250.63384687h36.9230775v-250.63384688zM591.46467655 403.66769188v250.63384687h36.9230775v-250.63384688z</Geometry>
<DropShadowEffect x:Key="EffectShadow1" BlurRadius="5" ShadowDepth="1" Direction="270" Color="{StaticResource EffectShadowColor}" Opacity=".2" RenderingBias="Performance" />
<DropShadowEffect x:Key="EffectShadow2" BlurRadius="8" ShadowDepth="1.5" Direction="270" Color="{StaticResource EffectShadowColor}" Opacity=".2" RenderingBias="Performance" />
<DropShadowEffect x:Key="EffectShadow3" BlurRadius="14" ShadowDepth="4.5" Direction="270" Color="{StaticResource EffectShadowColor}" Opacity=".2" RenderingBias="Performance" />
<DropShadowEffect x:Key="EffectShadow4" BlurRadius="25" ShadowDepth="8" Direction="270" Color="{StaticResource EffectShadowColor}" Opacity=".2" RenderingBias="Performance" />
<DropShadowEffect x:Key="EffectShadow5" BlurRadius="35" ShadowDepth="13" Direction="270" Color="{StaticResource EffectShadowColor}" Opacity=".2" RenderingBias="Performance" />
<!--ThumbStyle-->
<ControlTemplate x:Key="RangeSliderThumbBaseTemplate" TargetType="{x:Type cc:RangeSliderThumb}">
<Border Margin="-13,0" Name="BorderDot" BorderThickness="0" Effect="{StaticResource EffectShadow1}" BorderBrush="{DynamicResource PrimaryBrush}" >
<Border Width="20" BorderThickness="0" Background="Transparent">
<Path Fill="{StaticResource ThumbColor}" Stretch="Uniform" Data="{StaticResource RangeSliderThumbGeometry}" Effect="{StaticResource EffectShadow1}" />
</Border>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Effect" Value="{StaticResource EffectShadow2}"/>
</Trigger>
<EventTrigger RoutedEvent="PreviewMouseLeftButtonDown">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation To="2" Duration="0:0:.1" Storyboard.TargetName="BorderDot" Storyboard.TargetProperty="BorderThickness"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="PreviewMouseLeftButtonUp">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation To="0" Duration="0:0:.1" Storyboard.TargetName="BorderDot" Storyboard.TargetProperty="BorderThickness"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<ControlTemplate x:Key="RangeSliderThumbCenterTemplate" TargetType="{x:Type cc:RangeSliderThumb}">
<Border Name="BorderDot" Background="{TemplateBinding Background}" BorderThickness="0" BorderBrush="{DynamicResource PrimaryBrush}" />
</ControlTemplate>
<!--Repeat Button Base Style-->
<Style x:Key="RangeSliderRepeatBaseStyle" TargetType="{x:Type RepeatButton}">
<Setter Property="FrameworkElement.OverridesDefaultStyle" Value="true" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Focusable" Value="false" />
<Setter Property="IsTabStop" Value="false" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Border Background="{TemplateBinding Background}" Height="{TemplateBinding Height}" Width="{TemplateBinding Width}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="RangeSliderBaseTemplate" TargetType="{x:Type cc:RangeSlider}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" MinHeight="{TemplateBinding MinHeight}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TickBar x:Name="TopTick" Width="{Binding ActualWidth,ElementName=TrackBackground}" Ticks="{TemplateBinding Ticks}" TickFrequency="{TemplateBinding TickFrequency}" Minimum="{TemplateBinding Minimum}" Maximum="{TemplateBinding Maximum}" IsDirectionReversed="{TemplateBinding IsDirectionReversed}" Fill="{TemplateBinding Foreground}" Height="4" Margin="0,0,0,2" Placement="Top" Grid.Row="0" Visibility="Collapsed"/>
<TickBar x:Name="BottomTick" Width="{Binding ActualWidth,ElementName=TrackBackground}" Ticks="{TemplateBinding Ticks}" TickFrequency="{TemplateBinding TickFrequency}" Minimum="{TemplateBinding Minimum}" Maximum="{TemplateBinding Maximum}" IsDirectionReversed="{TemplateBinding IsDirectionReversed}" Fill="{TemplateBinding Foreground}" Height="4" Margin="0,2,0,0" Placement="Bottom" Grid.Row="2" Visibility="Collapsed"/>
<Border x:Name="TrackBackground" Effect="{StaticResource EffectShadow1}" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1" CornerRadius="5" Margin="9,0" Grid.Row="1" />
<cc:RangeSliderTrack IsDirectionReversed="{TemplateBinding IsDirectionReversed}" Orientation="{TemplateBinding Orientation}" ValueStart="{TemplateBinding ValueStart}" ValueEnd="{TemplateBinding ValueEnd}" Minimum="{TemplateBinding Minimum}" Maximum="{TemplateBinding Maximum}" Margin="9,0" x:Name="PART_Track" Grid.Row="1">
<cc:RangeSliderTrack.DecreaseRepeatButton>
<RepeatButton Interval="{TemplateBinding Interval}" Delay="{TemplateBinding Delay}" Command="{x:Static cc:RangeSlider.DecreaseLarge}" Style="{StaticResource RangeSliderRepeatBaseStyle}"/>
</cc:RangeSliderTrack.DecreaseRepeatButton>
<cc:RangeSliderTrack.IncreaseRepeatButton>
<RepeatButton Interval="{TemplateBinding Interval}" Delay="{TemplateBinding Delay}" Command="{x:Static cc:RangeSlider.IncreaseLarge}" Style="{StaticResource RangeSliderRepeatBaseStyle}"/>
</cc:RangeSliderTrack.IncreaseRepeatButton>
<cc:RangeSliderTrack.ThumbCenter>
<cc:RangeSliderThumb Margin="1" Background="{StaticResource SelectedRegionColorBrush}" ClipToBounds="False" Height="{TemplateBinding ActualHeight}" x:Name="ThumbCenter" Focusable="False" OverridesDefaultStyle="True" Template="{StaticResource RangeSliderThumbCenterTemplate}" />
</cc:RangeSliderTrack.ThumbCenter>
<cc:RangeSliderTrack.ThumbStart>
<cc:RangeSliderThumb Content="{TemplateBinding ValueStart}" ClipToBounds="False" Width="18" Margin="-9,0" Height="{TemplateBinding ActualHeight}" x:Name="ThumbStart" Focusable="False" OverridesDefaultStyle="True" Template="{StaticResource RangeSliderThumbBaseTemplate}" VerticalAlignment="Center"/>
</cc:RangeSliderTrack.ThumbStart>
<cc:RangeSliderTrack.ThumbEnd>
<cc:RangeSliderThumb Content="{TemplateBinding ValueEnd}" ClipToBounds="False" Width="18" Margin="-9,0" Height="{TemplateBinding ActualHeight}" x:Name="ThumbEnd" Focusable="False" OverridesDefaultStyle="True" Template="{StaticResource RangeSliderThumbBaseTemplate}" VerticalAlignment="Center"/>
</cc:RangeSliderTrack.ThumbEnd>
</cc:RangeSliderTrack>
</Grid>
</ControlTemplate>
<Style x:Key="RangeSliderBaseStyle" TargetType="{x:Type cc:RangeSlider}">
<Setter Property="Stylus.IsPressAndHoldEnabled" Value="false"/>
<Setter Property="Template" Value="{StaticResource RangeSliderBaseTemplate}"/>
<Style.Triggers>
<Trigger Property="Orientation" Value="Vertical">
<Setter Property="Template" Value="{StaticResource RangeSliderBaseTemplate}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value=".4"/>
</Trigger>
</Style.Triggers>
</Style>
cs:
public enum ThumbType
{
Start,
Center,
End
}
public struct Range<T>
{
public T Start { get; set; }
public T End { get; set; }
}
[DefaultEvent("ValueChanged"), DefaultProperty("Value")]
[TemplatePart(Name = ElementTrack, Type = typeof(Track))]
public class RangeSlider : Control
{
public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register(
nameof(Minimum), typeof(double), typeof(RangeSlider),
new PropertyMetadata(.0, OnMinimumChanged), ValidateHelper.IsInRangeOfDouble);
private static void OnMinimumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctrl = (RangeSlider)d;
ctrl.CoerceValue(MaximumProperty);
ctrl.CoerceValue(ValueStartProperty);
ctrl.CoerceValue(ValueEndProperty);
ctrl.OnMinimumChanged((double)e.OldValue, (double)e.NewValue);
}
protected virtual void OnMinimumChanged(double oldMinimum, double newMinimum)
{
}
public double Minimum
{
get => (double)GetValue(MinimumProperty);
set => SetValue(MinimumProperty, value);
}
public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register(
nameof(Maximum), typeof(double), typeof(RangeSlider),
new PropertyMetadata(10.0, OnMaximumChanged, CoerceMaximum),
ValidateHelper.IsInRangeOfDouble);
private static object CoerceMaximum(DependencyObject d, object basevalue)
{
var ctrl = (RangeSlider)d;
var min = ctrl.Minimum;
if ((double)basevalue < min)
{
return min;
}
return basevalue;
}
private static void OnMaximumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctrl = (RangeSlider)d;
ctrl.CoerceValue(ValueStartProperty);
ctrl.CoerceValue(ValueEndProperty);
ctrl.OnMaximumChanged((double)e.OldValue, (double)e.NewValue);
}
protected virtual void OnMaximumChanged(double oldMaximum, double newMaximum)
{
}
public double Maximum
{
get => (double)GetValue(MaximumProperty);
set => SetValue(MaximumProperty, value);
}
public static readonly DependencyProperty ValueStartProperty = DependencyProperty.Register(
nameof(ValueStart), typeof(double), typeof(RangeSlider),
new FrameworkPropertyMetadata(.0,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal,
OnValueStartChanged, ConstrainToRange), ValidateHelper.IsInRangeOfDouble);
private static void OnValueStartChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctrl = (RangeSlider)d;
ctrl.OnValueChanged(new Range<double>
{
Start = (double)e.OldValue,
End = ctrl.ValueEnd
}, new Range<double>
{
Start = (double)e.NewValue,
End = ctrl.ValueEnd
});
}
protected virtual void OnValueChanged(Range<double> oldValue, Range<double> newValue) => RaiseEvent(
new RoutedPropertyChangedEventArgs<Range<double>>(oldValue, newValue) { RoutedEvent = ValueChangedEvent });
public double ValueStart
{
get => (double)GetValue(ValueStartProperty);
set => SetValue(ValueStartProperty, value);
}
public static readonly DependencyProperty ValueEndProperty = DependencyProperty.Register(
nameof(ValueEnd), typeof(double), typeof(RangeSlider),
new FrameworkPropertyMetadata(.0,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal,
OnValueEndChanged, ConstrainToRange), ValidateHelper.IsInRangeOfDouble);
private static void OnValueEndChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctrl = (RangeSlider)d;
ctrl.OnValueChanged(new Range<double>
{
Start = ctrl.ValueStart,
End = (double)e.OldValue
}, new Range<double>
{
Start = ctrl.ValueStart,
End = (double)e.NewValue
});
}
public double ValueEnd
{
get => (double)GetValue(ValueEndProperty);
set => SetValue(ValueEndProperty, value);
}
internal static object ConstrainToRange(DependencyObject d, object value)
{
var ctrl = (RangeSlider)d;
var min = ctrl.Minimum;
var v = (double)value;
if (v < min)
{
return min;
}
var max = ctrl.Maximum;
if (v > max)
{
return max;
}
return value;
}
public static readonly DependencyProperty LargeChangeProperty = DependencyProperty.Register(
nameof(LargeChange), typeof(double), typeof(RangeSlider), new PropertyMetadata(1.0),
ValidateHelper.IsInRangeOfPosDoubleIncludeZero);
public double LargeChange
{
get => (double)GetValue(LargeChangeProperty);
set => SetValue(LargeChangeProperty, value);
}
public static readonly DependencyProperty SmallChangeProperty = DependencyProperty.Register(
nameof(SmallChange), typeof(double), typeof(RangeSlider), new PropertyMetadata(0.1),
ValidateHelper.IsInRangeOfPosDoubleIncludeZero);
public double SmallChange
{
get => (double)GetValue(SmallChangeProperty);
set => SetValue(SmallChangeProperty, value);
}
public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent("ValueChanged",
RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<Range<double>>), typeof(RangeSlider));
public event RoutedPropertyChangedEventHandler<Range<double>> ValueChanged
{
add => AddHandler(ValueChangedEvent, value);
remove => RemoveHandler(ValueChangedEvent, value);
}
private const string ElementTrack = "PART_Track";
private RangeSliderTrack _track;
private readonly ToolTip _autoToolTipStart = null;
private readonly ToolTip _autoToolTipCenter = null;
private readonly ToolTip _autoToolTipEnd = null;
private RangeSliderThumb _thumbCurrent;
private object _thumbOriginalToolTip;
private Point _originThumbPoint;
private Point _previousScreenCoordPosition;
public event EventHandler<RangeSliderValueChangedEvent> StartValueChanged;
public event EventHandler<RangeSliderValueChangedEvent> PreviewCenterValueChanged;
public event EventHandler<RangeSliderValueChangedEvent> EndValueChanged;
static RangeSlider()
{
InitializeCommands();
// Register Event Handler for the Thumb
EventManager.RegisterClassHandler(typeof(RangeSlider), Thumb.DragStartedEvent, new DragStartedEventHandler(OnThumbDragStarted));
EventManager.RegisterClassHandler(typeof(RangeSlider), Thumb.DragDeltaEvent, new DragDeltaEventHandler(OnThumbDragDelta));
EventManager.RegisterClassHandler(typeof(RangeSlider), Thumb.DragCompletedEvent, new DragCompletedEventHandler(OnThumbDragCompleted));
// Listen to MouseLeftButtonDown event to determine if slide should move focus to itself
EventManager.RegisterClassHandler(typeof(RangeSlider), Mouse.MouseDownEvent, new MouseButtonEventHandler(OnMouseLeftButtonDown), true);
}
public RangeSlider()
{
CommandBindings.Add(new CommandBinding(IncreaseLarge, OnIncreaseLarge));
CommandBindings.Add(new CommandBinding(IncreaseSmall, OnIncreaseSmall));
CommandBindings.Add(new CommandBinding(DecreaseLarge, OnDecreaseLarge));
CommandBindings.Add(new CommandBinding(DecreaseSmall, OnDecreaseSmall));
CommandBindings.Add(new CommandBinding(CenterLarge, OnCenterLarge));
CommandBindings.Add(new CommandBinding(CenterSmall, OnCenterSmall));
}
private void OnIncreaseLarge(object sender, ExecutedRoutedEventArgs e) => (sender as RangeSlider)?.OnIncreaseLarge();
private void OnIncreaseSmall(object sender, ExecutedRoutedEventArgs e) => (sender as RangeSlider)?.OnIncreaseSmall();
private void OnDecreaseLarge(object sender, ExecutedRoutedEventArgs e) => (sender as RangeSlider)?.OnDecreaseLarge();
private void OnDecreaseSmall(object sender, ExecutedRoutedEventArgs e) => (sender as RangeSlider)?.OnDecreaseSmall();
private void OnCenterLarge(object sender, ExecutedRoutedEventArgs e) => (sender as RangeSlider)?.OnCenterLarge(e.Parameter);
private void OnCenterSmall(object sender, ExecutedRoutedEventArgs e) => (sender as RangeSlider)?.OnCenterSmall(e.Parameter);
protected virtual void OnIncreaseLarge() => MoveToNextTick(LargeChange, false);
protected virtual void OnIncreaseSmall() => MoveToNextTick(SmallChange, false);
protected virtual void OnDecreaseLarge() => MoveToNextTick(-LargeChange, true);
protected virtual void OnDecreaseSmall() => MoveToNextTick(-SmallChange, true);
protected virtual void OnCenterLarge(object parameter) => MoveToNextTick(LargeChange, false, true);
protected virtual void OnCenterSmall(object parameter) => MoveToNextTick(SmallChange, false, true);
public static RoutedCommand IncreaseLarge { get; private set; }
public static RoutedCommand IncreaseSmall { get; private set; }
public static RoutedCommand DecreaseLarge { get; private set; }
public static RoutedCommand DecreaseSmall { get; private set; }
public static RoutedCommand CenterLarge { get; private set; }
public static RoutedCommand CenterSmall { get; private set; }
private static void InitializeCommands()
{
IncreaseLarge = new RoutedCommand(nameof(IncreaseLarge), typeof(RangeSlider));
IncreaseSmall = new RoutedCommand(nameof(IncreaseSmall), typeof(RangeSlider));
DecreaseLarge = new RoutedCommand(nameof(DecreaseLarge), typeof(RangeSlider));
DecreaseSmall = new RoutedCommand(nameof(DecreaseSmall), typeof(RangeSlider));
CenterLarge = new RoutedCommand(nameof(CenterLarge), typeof(RangeSlider));
CenterSmall = new RoutedCommand(nameof(CenterSmall), typeof(RangeSlider));
}
public override void OnApplyTemplate()
{
_thumbCurrent = null;
base.OnApplyTemplate();
_track = GetTemplateChild(ElementTrack) as RangeSliderTrack;
if (_autoToolTipStart != null)
{
_autoToolTipStart.PlacementTarget = _track?.ThumbStart;
}
if (_autoToolTipCenter != null)
{
_autoToolTipCenter.PlacementTarget = _track?.ThumbCenter;
}
if (_autoToolTipEnd != null)
{
_autoToolTipEnd.PlacementTarget = _track?.ThumbEnd;
}
}
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
nameof(Orientation), typeof(Orientation), typeof(RangeSlider), new PropertyMetadata(default(Orientation)));
public Orientation Orientation
{
get => (Orientation)GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
public static readonly DependencyProperty IsDirectionReversedProperty = DependencyProperty.Register(
nameof(IsDirectionReversed), typeof(bool), typeof(RangeSlider), new PropertyMetadata(false));
public bool IsDirectionReversed
{
get => (bool)GetValue(IsDirectionReversedProperty);
set => SetValue(IsDirectionReversedProperty, value);
}
public static readonly DependencyProperty DelayProperty = RepeatButton.DelayProperty.AddOwner(typeof(RangeSlider), new FrameworkPropertyMetadata(GetKeyboardDelay()));
public int Delay
{
get => (int)GetValue(DelayProperty);
set => SetValue(DelayProperty, value);
}
internal static int GetKeyboardDelay()
{
var delay = SystemParameters.KeyboardDelay;
if (delay < 0 || delay > 3)
{
delay = 0;
}
return (delay + 1) * 250;
}
public static readonly DependencyProperty IntervalProperty = RepeatButton.IntervalProperty.AddOwner(typeof(RangeSlider), new FrameworkPropertyMetadata(GetKeyboardSpeed()));
public int Interval
{
get => (int)GetValue(IntervalProperty);
set => SetValue(IntervalProperty, value);
}
internal static int GetKeyboardSpeed()
{
var speed = SystemParameters.KeyboardSpeed;
if (speed < 0 || speed > 31)
{
speed = 31;
}
return ((31 - speed) * (400 - (1000 / 30)) / 31) + (1000 / 30);
}
public static readonly DependencyProperty AutoToolTipPlacementProperty = DependencyProperty.Register(
nameof(AutoToolTipPlacement), typeof(AutoToolTipPlacement), typeof(RangeSlider), new PropertyMetadata(default(AutoToolTipPlacement)));
public AutoToolTipPlacement AutoToolTipPlacement
{
get => (AutoToolTipPlacement)GetValue(AutoToolTipPlacementProperty);
set => SetValue(AutoToolTipPlacementProperty, value);
}
public static readonly DependencyProperty AutoToolTipPrecisionProperty = DependencyProperty.Register(
nameof(AutoToolTipPrecision), typeof(int), typeof(RangeSlider), new PropertyMetadata(0),
ValidateHelper.IsInRangeOfPosIntIncludeZero);
public int AutoToolTipPrecision
{
get => (int)GetValue(AutoToolTipPrecisionProperty);
set => SetValue(AutoToolTipPrecisionProperty, value);
}
public static readonly DependencyProperty IsSnapToTickEnabledProperty = DependencyProperty.Register(
nameof(IsSnapToTickEnabled), typeof(bool), typeof(RangeSlider), new PropertyMetadata(false));
public bool IsSnapToTickEnabled
{
get => (bool)GetValue(IsSnapToTickEnabledProperty);
set => SetValue(IsSnapToTickEnabledProperty, value);
}
public static readonly DependencyProperty TickPlacementProperty = DependencyProperty.Register(
nameof(TickPlacement), typeof(TickPlacement), typeof(RangeSlider), new PropertyMetadata(default(TickPlacement)));
public TickPlacement TickPlacement
{
get => (TickPlacement)GetValue(TickPlacementProperty);
set => SetValue(TickPlacementProperty, value);
}
public static readonly DependencyProperty TickFrequencyProperty = DependencyProperty.Register(
nameof(TickFrequency), typeof(double), typeof(RangeSlider), new PropertyMetadata(1.0),
ValidateHelper.IsInRangeOfPosDoubleIncludeZero);
public double TickFrequency
{
get => (double)GetValue(TickFrequencyProperty);
set => SetValue(TickFrequencyProperty, value);
}
public static readonly DependencyProperty TicksProperty = DependencyProperty.Register(
nameof(Ticks), typeof(DoubleCollection), typeof(RangeSlider), new PropertyMetadata(new DoubleCollection()));
public DoubleCollection Ticks
{
get => (DoubleCollection)GetValue(TicksProperty);
set => SetValue(TicksProperty, value);
}
public static readonly DependencyProperty IsMoveToPointEnabledProperty = DependencyProperty.Register(
nameof(IsMoveToPointEnabled), typeof(bool), typeof(RangeSlider), new PropertyMetadata(false));
public bool IsMoveToPointEnabled
{
get => (bool)GetValue(IsMoveToPointEnabledProperty);
set => SetValue(IsMoveToPointEnabledProperty, value);
}
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (IsMoveToPointEnabled && !_track.ThumbStart.IsMouseOver && !_track.ThumbCenter.IsMouseOver && !_track.ThumbEnd.IsMouseOver)
{
// Here we need to determine whether it's closer to the starting point or the end point.
var pt = e.MouseDevice.GetPosition(_track);
UpdateValue(pt);
e.Handled = true;
}
base.OnPreviewMouseLeftButtonDown(e);
}
private void MoveToNextTick(double direction, bool isStart, bool isCenter = false, object parameter = null)
{
if (MathHelper.AreClose(direction, 0))
{
return;
}
if (isCenter)
{
return;
}
var value = isStart ? ValueStart : ValueEnd;
var next = SnapToTick(Math.Max(Minimum, Math.Min(Maximum, value + direction)));
var greaterThan = direction > 0;
// If the snapping brought us back to value, find the next tick point
if (MathHelper.AreClose(next, value) &&
!(greaterThan && MathHelper.AreClose(value, Maximum)) &&
!(!greaterThan && MathHelper.AreClose(value, Minimum)))
{
// If ticks collection is available, use it.
// Note that ticks may be unsorted.
if (Ticks.Count > 0)
{
foreach (var tick in Ticks)
{
// Find the smallest tick greater than value or the largest tick less than value
if ((greaterThan && MathHelper.GreaterThan(tick, value) && (MathHelper.LessThan(tick, next) || MathHelper.AreClose(next, value)))
|| (!greaterThan && MathHelper.LessThan(tick, value) && (MathHelper.GreaterThan(tick, next) || MathHelper.AreClose(next, value))))
{
next = tick;
}
}
}
else if (MathHelper.GreaterThan(TickFrequency, 0.0))
{
// Find the current tick we are at
var tickNumber = Math.Round((value - Minimum) / TickFrequency);
if (greaterThan)
{
tickNumber += 1.0;
}
else
{
tickNumber -= 1.0;
}
next = Minimum + (tickNumber * TickFrequency);
}
}
// Update if we've found a better value
if (!MathHelper.AreClose(next, value))
{
SetCurrentValue(isStart ? ValueStartProperty : ValueEndProperty, next);
}
}
private double SnapToTick(double value)
{
if (!IsSnapToTickEnabled)
{
return value;
}
var previous = Minimum;
var next = Maximum;
if (Ticks.Count > 0)
{
foreach (var tick in Ticks)
{
if (MathHelper.AreClose(tick, value))
{
return value;
}
if (MathHelper.LessThan(tick, value) && MathHelper.GreaterThan(tick, previous))
{
previous = tick;
}
else if (MathHelper.GreaterThan(tick, value) && MathHelper.LessThan(tick, next))
{
next = tick;
}
}
}
else if (MathHelper.GreaterThan(TickFrequency, 0.0))
{
previous = Minimum + (Math.Round((value - Minimum) / TickFrequency) * TickFrequency);
next = Math.Min(Maximum, previous + TickFrequency);
}
return MathHelper.GreaterThanOrClose(value, (previous + next) * 0.5) ? next : previous;
}
private void UpdateValue(Point point)
{
var newValue = _track.ValueFromPoint(point);
if (ValidateHelper.IsInRangeOfDouble(newValue))
{
ThumbType tt = ThumbType.Center;
if (newValue < ValueStart)
{
tt = ThumbType.Start;
}
else if (newValue > ValueEnd)
{
tt = ThumbType.End;
}
UpdateValue(newValue, tt);
}
}
private void UpdateValue(double value, ThumbType thumbType)
{
var snappedValue = SnapToTick(value);
if (thumbType == ThumbType.Start)
{
if (!MathHelper.AreClose(snappedValue, ValueStart))
{
var start = Math.Max(Minimum, Math.Min(Maximum, snappedValue));
if (start > ValueEnd)
{
SetCurrentValue(ValueStartProperty, ValueEnd);
SetCurrentValue(ValueEndProperty, start);
_track.ThumbStart.CancelDrag();
_track.ThumbEnd.StartDrag();
_thumbCurrent = _track.ThumbEnd;
}
else
{
SetCurrentValue(ValueStartProperty, start);
}
}
}
if (thumbType == ThumbType.End)
{
if (!MathHelper.AreClose(snappedValue, ValueEnd))
{
var end = Math.Max(Minimum, Math.Min(Maximum, snappedValue));
if (end < ValueStart)
{
SetCurrentValue(ValueEndProperty, ValueStart);
SetCurrentValue(ValueStartProperty, end);
_track.ThumbEnd.CancelDrag();
_track.ThumbStart.StartDrag();
_thumbCurrent = _track.ThumbStart;
}
else
{
SetCurrentValue(ValueEndProperty, end);
}
}
}
}
private static void OnThumbDragStarted(object sender, DragStartedEventArgs e) => (sender as RangeSlider)?.OnThumbDragStarted(e);
protected virtual void OnThumbDragStarted(DragStartedEventArgs e)
{
// Show AutoToolTip if needed.
if (!(e.OriginalSource is RangeSliderThumb thumb))
{
return;
}
_thumbCurrent = thumb;
_originThumbPoint = Mouse.GetPosition(_thumbCurrent);
_thumbCurrent.StartDrag();
if (AutoToolTipPlacement == AutoToolTipPlacement.None)
{
return;
}
ThumbType tt = ThumbType.Center;
if (thumb.Equals(_track.ThumbStart))
{
tt = ThumbType.Start;
}
else if (thumb.Equals(_track.ThumbStart))
{
tt = ThumbType.End;
}
// Save original tooltip
_thumbOriginalToolTip = thumb.ToolTip;
switch (tt)
{
case ThumbType.Start:
OnThumbDragStarted(_autoToolTipStart, tt);
break;
case ThumbType.Center:
OnThumbDragStarted(_autoToolTipCenter, tt);
break;
case ThumbType.End:
OnThumbDragStarted(_autoToolTipEnd, tt);
break;
}
}
private void OnThumbDragStarted(ToolTip toolTip, ThumbType thumb)
{
if (toolTip == null)
{
toolTip = new ToolTip
{
Placement = PlacementMode.Custom,
CustomPopupPlacementCallback = AutoToolTipCustomPlacementCallback
};
switch (thumb)
{
case ThumbType.Start:
toolTip.PlacementTarget = _track.ThumbStart;
break;
case ThumbType.Center:
toolTip.PlacementTarget = _track.ThumbCenter;
break;
case ThumbType.End:
toolTip.PlacementTarget = _track.ThumbEnd;
break;
}
}
switch (thumb)
{
case ThumbType.Start:
_track.ThumbStart.ToolTip = toolTip;
break;
case ThumbType.Center:
_track.ThumbCenter.ToolTip = toolTip;
break;
case ThumbType.End:
_track.ThumbEnd.ToolTip = toolTip;
break;
}
toolTip.Content = GetAutoToolTipNumber(thumb);
toolTip.IsOpen = true;
}
private CustomPopupPlacement[] AutoToolTipCustomPlacementCallback(Size popupSize, Size targetSize, Point offset)
{
switch (AutoToolTipPlacement)
{
case AutoToolTipPlacement.TopLeft:
if (Orientation == Orientation.Horizontal)
{
// Place popup at top of thumb
return new[]{new CustomPopupPlacement(
new Point((targetSize.Width - popupSize.Width) * 0.5, -popupSize.Height),
PopupPrimaryAxis.Horizontal)
};
}
else
{
// Place popup at left of thumb
return new[] {
new CustomPopupPlacement(
new Point(-popupSize.Width, (targetSize.Height - popupSize.Height) * 0.5),
PopupPrimaryAxis.Vertical)
};
}
case AutoToolTipPlacement.BottomRight:
if (Orientation == Orientation.Horizontal)
{
// Place popup at bottom of thumb
return new[] {
new CustomPopupPlacement(
new Point((targetSize.Width - popupSize.Width) * 0.5, targetSize.Height) ,
PopupPrimaryAxis.Horizontal)
};
}
else
{
// Place popup at right of thumb
return new[] {
new CustomPopupPlacement(
new Point(targetSize.Width, (targetSize.Height - popupSize.Height) * 0.5),
PopupPrimaryAxis.Vertical)
};
}
default:
return new CustomPopupPlacement[] { };
}
}
private string GetAutoToolTipNumber(ThumbType thumbType)
{
var format = (NumberFormatInfo)NumberFormatInfo.CurrentInfo.Clone();
format.NumberDecimalDigits = AutoToolTipPrecision;
switch (thumbType)
{
case ThumbType.Start:
return ValueStart.ToString("N", format);
case ThumbType.End:
return ValueEnd.ToString("N", format);
default:
return default;
}
}
private static void OnThumbDragDelta(object sender, DragDeltaEventArgs e) => (sender as RangeSlider)?.OnThumbDragDelta(e);
protected virtual void OnThumbDragDelta(DragDeltaEventArgs e)
{
if (!(e.OriginalSource is Thumb thumb))
{
return;
}
ThumbType tt = ThumbType.Center;
if (thumb.Equals(_track.ThumbStart))
{
tt = ThumbType.Start;
}
else if (thumb.Equals(_track.ThumbEnd))
{
tt = ThumbType.End;
}
// Convert to Track's co-ordinate
OnThumbDragDelta(_track, tt, e);
}
private void OnThumbDragDelta(RangeSliderTrack track, ThumbType thumbType, DragDeltaEventArgs e)
{
if (track == null || track.ThumbStart == null | track.ThumbCenter == null | _track.ThumbEnd == null)
{
return;
}
double newValue = double.NaN;
double offsetValue = track.ValueFromDistance(e.HorizontalChange, e.VerticalChange);
if (thumbType == ThumbType.Start)
{
double source = ValueStart;
newValue = ValueStart + offsetValue;
if (ValidateHelper.IsInRangeOfDouble(newValue))
{
newValue = Math.Max(newValue, Minimum);
UpdateValue(newValue, ThumbType.Start);
StartValueChanged?.Invoke(this, new RangeSliderValueChangedEvent(source, offsetValue, newValue, thumbType));
}
}
else if (thumbType == ThumbType.End)
{
double source = ValueEnd;
newValue = ValueEnd + offsetValue;
if (ValidateHelper.IsInRangeOfDouble(newValue))
{
newValue = Math.Min(newValue, Maximum);
UpdateValue(newValue, ThumbType.End);
EndValueChanged?.Invoke(this, new RangeSliderValueChangedEvent(source, offsetValue, newValue, thumbType));
}
}
else if (thumbType == ThumbType.Center)
{
var args = new RangeSliderValueChangedEvent(double.NaN, offsetValue, newValue, thumbType);
PreviewCenterValueChanged?.Invoke(this, args);
if (!args.Handled)
{
double startSource = ValueStart;
double endSource = ValueEnd;
double newStartValue = ValueStart + offsetValue;
double newEndValue = ValueEnd + offsetValue;
if (ValidateHelper.IsInRangeOfDouble(newStartValue) && ValidateHelper.IsInRangeOfDouble(newEndValue))
{
if (newStartValue < Minimum)
{
newEndValue += Minimum - newStartValue;
newStartValue = Minimum;
}
if (newEndValue > Maximum)
{
newStartValue += Maximum - newEndValue;
newEndValue = Maximum;
}
UpdateValue(newStartValue, ThumbType.Start);
UpdateValue(newEndValue, ThumbType.End);
StartValueChanged?.Invoke(this, new RangeSliderValueChangedEvent(startSource, offsetValue, newStartValue, thumbType));
EndValueChanged?.Invoke(this, new RangeSliderValueChangedEvent(endSource, offsetValue, newEndValue, thumbType));
}
}
}
// Show AutoToolTip if needed
if (AutoToolTipPlacement != AutoToolTipPlacement.None)
{
ToolTip toolTip;
switch (thumbType)
{
case ThumbType.Start:
toolTip = _autoToolTipStart;
break;
case ThumbType.End:
toolTip = _autoToolTipEnd;
break;
default:
toolTip = new ToolTip();
break;
}
toolTip.Content = GetAutoToolTipNumber(thumbType);
if (thumbType == ThumbType.Start || thumbType == ThumbType.Center)
{
if (!Equals(_track.ThumbStart?.ToolTip, toolTip))
{
_track.ThumbStart.ToolTip = toolTip;
}
}
if (thumbType == ThumbType.End || thumbType == ThumbType.Center)
{
if (!Equals(_track.ThumbEnd?.ToolTip, toolTip))
{
_track.ThumbEnd.ToolTip = toolTip;
}
}
if (!toolTip.IsOpen)
{
toolTip.IsOpen = true;
}
}
}
private static void OnThumbDragCompleted(object sender, DragCompletedEventArgs e) => (sender as RangeSlider)?.OnThumbDragCompleted(e);
protected virtual void OnThumbDragCompleted(DragCompletedEventArgs e)
{
// Show AutoToolTip if needed.
if (!(e.OriginalSource is Thumb thumb) || AutoToolTipPlacement == AutoToolTipPlacement.None)
{
return;
}
ThumbType tt = ThumbType.Center;
if (thumb.Equals(_track.ThumbStart))
{
tt = ThumbType.Start;
}
else if (thumb.Equals(_track.ThumbStart))
{
tt = ThumbType.End;
}
ToolTip toolTip = null;
switch (tt)
{
case ThumbType.Start:
toolTip = _autoToolTipStart;
break;
case ThumbType.Center:
toolTip = _autoToolTipCenter;
break;
case ThumbType.End:
toolTip = _autoToolTipEnd;
break;
}
if (toolTip != null)
{
toolTip.IsOpen = false;
}
thumb.ToolTip = _thumbOriginalToolTip;
}
private static void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton != MouseButton.Left)
{
return;
}
var slider = (RangeSlider)sender;
// When someone click on the Slider's part, and it's not focusable
// Slider need to take the focus in order to process keyboard correctly
if (!slider.IsKeyboardFocusWithin)
{
e.Handled = slider.Focus() || e.Handled;
}
if (slider._track.ThumbStart.IsMouseOver)
{
slider._track.ThumbStart.StartDrag();
slider._thumbCurrent = slider._track.ThumbStart;
}
if (slider._track.ThumbCenter.IsMouseOver)
{
slider._track.ThumbCenter.StartDrag();
slider._thumbCurrent = slider._track.ThumbCenter;
}
if (slider._track.ThumbEnd.IsMouseOver)
{
slider._track.ThumbEnd.StartDrag();
slider._thumbCurrent = slider._track.ThumbEnd;
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (e.Handled)
{
return;
}
if (_thumbCurrent == null)
{
return;
}
if (e.MouseDevice.LeftButton != MouseButtonState.Pressed)
{
return;
}
if (!_thumbCurrent.IsDragging)
{
return;
}
var thumbCoordPosition = e.GetPosition(_thumbCurrent);
var screenCoordPosition = PointFromScreen(thumbCoordPosition);
if (screenCoordPosition != _previousScreenCoordPosition)
{
_previousScreenCoordPosition = screenCoordPosition;
Point offset = new Point(thumbCoordPosition.X - _originThumbPoint.X, thumbCoordPosition.Y - _originThumbPoint.Y);
_thumbCurrent.RaiseEvent(new DragDeltaEventArgs(offset.X, offset.Y));
}
}
protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnPreviewMouseLeftButtonUp(e);
_thumbCurrent?.CancelDrag();
_thumbCurrent = null;
}
}
public class RangeSliderThumb : Thumb
{
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(
nameof(Content), typeof(object), typeof(RangeSliderThumb), new PropertyMetadata(default(object)));
public object Content
{
get => GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
protected override void OnMouseMove(MouseEventArgs e)
{
}
public void StartDrag()
{
IsDragging = true;
Focus();
CaptureMouse();
RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, MouseButton.Left)
{
RoutedEvent = PreviewMouseLeftButtonDownEvent,
Source = this
});
}
public new void CancelDrag()
{
base.CancelDrag();
RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, MouseButton.Left)
{
RoutedEvent = PreviewMouseLeftButtonUpEvent
});
}
}
public class RangeSliderTrack : Control
{
private RepeatButton _increaseButton;
private RepeatButton _decreaseButton;
private RangeSliderThumb _thumbStart;
private RangeSliderThumb _thumbCenter;
private RangeSliderThumb _thumbEnd;
private Visual[] _visualChildren;
private double _density { get; set; } = double.NaN;
public RepeatButton DecreaseRepeatButton
{
get => _decreaseButton;
set
{
if (Equals(_increaseButton, value) || Equals(_thumbCenter, value))
{
throw new NotSupportedException("SameButtons");
}
UpdateComponent(_decreaseButton, value);
_decreaseButton = value;
if (_decreaseButton != null)
{
CommandManager.InvalidateRequerySuggested(); // Should post an idle queue item to update IsEnabled on button
}
}
}
public RepeatButton IncreaseRepeatButton
{
get => _increaseButton;
set
{
if (Equals(_decreaseButton, value) || Equals(_thumbCenter, value))
{
throw new NotSupportedException("SameButtons");
}
UpdateComponent(_increaseButton, value);
_increaseButton = value;
if (_increaseButton != null)
{
CommandManager.InvalidateRequerySuggested(); // Should post an idle queue item to update IsEnabled on button
}
}
}
public RangeSliderThumb ThumbStart
{
get => _thumbStart;
set
{
UpdateComponent(_thumbStart, value);
_thumbStart = value;
}
}
public RangeSliderThumb ThumbCenter
{
get => _thumbCenter;
set
{
UpdateComponent(_thumbCenter, value);
_thumbCenter = value;
}
}
public RangeSliderThumb ThumbEnd
{
get => _thumbEnd;
set
{
UpdateComponent(_thumbEnd, value);
_thumbEnd = value;
}
}
static RangeSliderTrack()
{
IsEnabledProperty.OverrideMetadata(typeof(RangeSliderTrack), new UIPropertyMetadata(OnIsEnabledChanged));
}
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
nameof(Orientation), typeof(Orientation), typeof(RangeSliderTrack),
new FrameworkPropertyMetadata(default(Orientation), FrameworkPropertyMetadataOptions.AffectsMeasure));
public Orientation Orientation
{
get => (Orientation)GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register(
nameof(Minimum), typeof(double), typeof(RangeSliderTrack),
new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsArrange));
public double Minimum
{
get => (double)GetValue(MinimumProperty);
set => SetValue(MinimumProperty, value);
}
public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register(
nameof(Maximum), typeof(double), typeof(RangeSliderTrack),
new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsArrange));
public double Maximum
{
get => (double)GetValue(MaximumProperty);
set => SetValue(MaximumProperty, value);
}
public static readonly DependencyProperty ValueStartProperty = DependencyProperty.Register(
nameof(ValueStart), typeof(double), typeof(RangeSliderTrack),
new FrameworkPropertyMetadata(.0,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
FrameworkPropertyMetadataOptions.AffectsArrange));
public double ValueStart
{
get => (double)GetValue(ValueStartProperty);
set => SetValue(ValueStartProperty, value);
}
public static readonly DependencyProperty ValueEndProperty = DependencyProperty.Register(
nameof(ValueEnd), typeof(double), typeof(RangeSliderTrack),
new FrameworkPropertyMetadata(.0,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
FrameworkPropertyMetadataOptions.AffectsArrange));
public double ValueEnd
{
get => (double)GetValue(ValueEndProperty);
set => SetValue(ValueEndProperty, value);
}
public static readonly DependencyProperty IsDirectionReversedProperty = DependencyProperty.Register(
nameof(IsDirectionReversed), typeof(bool), typeof(RangeSliderTrack), new PropertyMetadata(false));
public bool IsDirectionReversed
{
get => (bool)GetValue(IsDirectionReversedProperty);
set => SetValue(IsDirectionReversedProperty, value);
}
protected override Visual GetVisualChild(int index)
{
if (_visualChildren?[index] == null)
{
throw new ArgumentOutOfRangeException("index", index, "ArgumentOutOfRange");
}
return _visualChildren[index];
}
protected override Size MeasureOverride(Size availableSize)
{
var desiredSize = new Size();
if (_thumbStart != null)
{
_thumbStart.Measure(availableSize);
desiredSize = _thumbStart.DesiredSize;
}
if (_thumbEnd != null)
{
_thumbEnd.Measure(availableSize);
desiredSize = new Size(Math.Max(_thumbEnd.DesiredSize.Width, desiredSize.Width),
Math.Max(_thumbEnd.DesiredSize.Height, desiredSize.Height));
}
return desiredSize;
}
private static void CoerceLength(ref double componentLength, double trackLength)
{
if (componentLength < 0)
{
componentLength = 0;
}
else if (componentLength > trackLength || double.IsNaN(componentLength))
{
componentLength = trackLength;
}
}
protected override Size ArrangeOverride(Size arrangeSize)
{
var isVertical = Orientation == Orientation.Vertical;
ComputeLengths(arrangeSize, isVertical, out var decreaseButtonLength, out var centerButtonLength,
out var increaseButtonLength, out var thumbStartLength, out var thumbEndLength);
var offset = new Point();
var pieceSize = arrangeSize;
var isDirectionReversed = IsDirectionReversed;
if (isVertical)
{
CoerceLength(ref decreaseButtonLength, arrangeSize.Height);
CoerceLength(ref centerButtonLength, arrangeSize.Height);
CoerceLength(ref increaseButtonLength, arrangeSize.Height);
CoerceLength(ref thumbStartLength, arrangeSize.Height);
CoerceLength(ref thumbEndLength, arrangeSize.Height);
offset.Y = isDirectionReversed ? decreaseButtonLength + thumbEndLength + centerButtonLength + thumbStartLength : 0;
pieceSize.Height = increaseButtonLength;
IncreaseRepeatButton?.Arrange(new Rect(offset, pieceSize));
offset.Y = isDirectionReversed ? decreaseButtonLength + thumbEndLength : increaseButtonLength + thumbStartLength;
pieceSize.Height = centerButtonLength;
ThumbCenter?.Arrange(new Rect(offset, pieceSize));
offset.Y = isDirectionReversed ? 0 : increaseButtonLength + thumbStartLength + centerButtonLength + thumbEndLength;
pieceSize.Height = decreaseButtonLength;
DecreaseRepeatButton?.Arrange(new Rect(offset, pieceSize));
offset.Y = isDirectionReversed
? decreaseButtonLength + thumbEndLength + centerButtonLength
: increaseButtonLength + thumbStartLength + centerButtonLength;
pieceSize.Height = thumbStartLength;
ArrangeThumb(isDirectionReversed, false, offset, pieceSize);
offset.Y = isDirectionReversed ? decreaseButtonLength : increaseButtonLength;
pieceSize.Height = thumbEndLength;
ArrangeThumb(isDirectionReversed, true, offset, pieceSize);
}
else
{
CoerceLength(ref decreaseButtonLength, arrangeSize.Width);
CoerceLength(ref centerButtonLength, arrangeSize.Width);
CoerceLength(ref increaseButtonLength, arrangeSize.Width);
CoerceLength(ref thumbStartLength, arrangeSize.Width);
CoerceLength(ref thumbEndLength, arrangeSize.Width);
offset.X = isDirectionReversed ? 0 : decreaseButtonLength + thumbEndLength + centerButtonLength + thumbStartLength;
pieceSize.Width = increaseButtonLength;
IncreaseRepeatButton?.Arrange(new Rect(offset, pieceSize));
offset.X = isDirectionReversed ? increaseButtonLength + thumbStartLength : decreaseButtonLength + thumbEndLength;
pieceSize.Width = centerButtonLength;
ThumbCenter?.Arrange(new Rect(offset, pieceSize));
offset.X = isDirectionReversed ? increaseButtonLength + thumbStartLength + centerButtonLength + thumbEndLength : 0;
pieceSize.Width = decreaseButtonLength;
DecreaseRepeatButton?.Arrange(new Rect(offset, pieceSize));
offset.X = isDirectionReversed ? increaseButtonLength : decreaseButtonLength;
pieceSize.Width = thumbStartLength;
ArrangeThumb(isDirectionReversed, false, offset, pieceSize);
offset.X = isDirectionReversed
? increaseButtonLength + thumbStartLength + centerButtonLength
: decreaseButtonLength + thumbEndLength + centerButtonLength;
pieceSize.Width = thumbEndLength;
ArrangeThumb(isDirectionReversed, true, offset, pieceSize);
}
return arrangeSize;
}
private void ArrangeThumb(bool isDirectionReversed, bool isStart, Point offset, Size pieceSize)
{
if (isStart)
{
if (isDirectionReversed)
{
ThumbStart?.Arrange(new Rect(offset, pieceSize));
}
else
{
ThumbEnd?.Arrange(new Rect(offset, pieceSize));
}
}
else
{
if (isDirectionReversed)
{
ThumbEnd?.Arrange(new Rect(offset, pieceSize));
}
else
{
ThumbStart?.Arrange(new Rect(offset, pieceSize));
}
}
}
private void ComputeLengths(Size arrangeSize, bool isVertical, out double decreaseButtonLength,
out double centerButtonLength, out double increaseButtonLength, out double thumbStartLength,
out double thumbEndLength)
{
var min = Minimum;
var range = Math.Max(0.0, Maximum - min);
var offsetStart = Math.Min(range, ValueStart - min);
var offsetEnd = Math.Min(range, ValueEnd - min);
double trackLength;
// Compute thumb size
if (isVertical)
{
trackLength = arrangeSize.Height;
thumbStartLength = _thumbStart?.DesiredSize.Height ?? 0;
thumbEndLength = _thumbEnd?.DesiredSize.Height ?? 0;
}
else
{
trackLength = arrangeSize.Width;
thumbStartLength = _thumbStart?.DesiredSize.Width ?? 0;
thumbEndLength = _thumbEnd?.DesiredSize.Width ?? 0;
}
CoerceLength(ref thumbStartLength, trackLength);
CoerceLength(ref thumbEndLength, trackLength);
var remainingTrackLength = trackLength - thumbStartLength - thumbEndLength;
decreaseButtonLength = remainingTrackLength * offsetStart / range;
CoerceLength(ref decreaseButtonLength, remainingTrackLength);
centerButtonLength = (remainingTrackLength * offsetEnd / range) - decreaseButtonLength;
CoerceLength(ref centerButtonLength, remainingTrackLength);
increaseButtonLength = remainingTrackLength - decreaseButtonLength - centerButtonLength;
CoerceLength(ref increaseButtonLength, remainingTrackLength);
_density = range / remainingTrackLength;
}
protected override int VisualChildrenCount
{
get
{
if (_visualChildren == null)
{
return 0;
}
for (var i = 0; i < _visualChildren.Length; i++)
{
if (_visualChildren[i] == null)
{
return i;
}
}
return _visualChildren.Length;
}
}
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
Mouse.Synchronize();
}
}
public virtual double ValueFromPoint(Point pt)
{
return Orientation == Orientation.Horizontal
? !IsDirectionReversed
? pt.X / RenderSize.Width * Maximum
: (1 - (pt.X / RenderSize.Width)) * Maximum
: !IsDirectionReversed
? pt.Y / RenderSize.Height * Maximum
: (1 - (pt.X / RenderSize.Height)) * Maximum;
}
public virtual double ValueFromDistance(double horizontal, double vertical)
{
double scale = IsDirectionReversed ? -1 : 1;
return Orientation == Orientation.Horizontal
? scale * horizontal * _density
: -1 * scale * vertical * _density;
}
private void UpdateComponent(Control oldValue, Control newValue)
{
if (oldValue != newValue)
{
if (_visualChildren == null)
{
_visualChildren = new Visual[5];
}
if (oldValue != null)
{
// notify the visual layer that the old component has been removed.
RemoveVisualChild(oldValue);
}
// Remove the old value from our z index list and add new value to end
int i = 0;
while (i < 5)
{
// Array isn't full, break
if (_visualChildren[i] == null)
{
break;
}
// found the old value
if (_visualChildren[i] == oldValue)
{
// Move values down until end of array or a null element
while (i < 4 && _visualChildren[i + 1] != null)
{
_visualChildren[i] = _visualChildren[i + 1];
i++;
}
}
else
{
i++;
}
}
// Add newValue at end of z-order
_visualChildren[i] = newValue;
AddVisualChild(newValue);
InvalidateMeasure();
InvalidateArrange();
}
}
}
public class RangeSliderValueChangedEvent : EventArgs
{
public bool Handled = false;
public double Source;
public double Offset;
public double NewValue;
public object ValueType;
public RangeSliderValueChangedEvent(double source, double offset, double newValue, object valueType)
{
Source = source;
Offset = offset;
NewValue = newValue;
ValueType = valueType;
}
}
file static class MathHelper
{
internal const double DBL_EPSILON = 2.2204460492503131e-016;
public static bool AreClose(double value1, double value2) =>
// ReSharper disable once CompareOfFloatsByEqualityOperator
value1 == value2 || IsVerySmall(value1 - value2);
public static double Lerp(double x, double y, double alpha) => (x * (1.0 - alpha)) + (y * alpha);
public static bool IsVerySmall(double value) => Math.Abs(value) < 1E-06;
public static bool IsZero(double value) => Math.Abs(value) < 10.0 * DBL_EPSILON;
public static bool IsFiniteDouble(double x) => !double.IsInfinity(x) && !double.IsNaN(x);
public static double DoubleFromMantissaAndExponent(double x, int exp) => x * Math.Pow(2.0, exp);
public static bool GreaterThan(double value1, double value2) => value1 > value2 && !AreClose(value1, value2);
public static bool GreaterThanOrClose(double value1, double value2)
{
if (value1 <= value2)
{
return AreClose(value1, value2);
}
return true;
}
public static double Hypotenuse(double x, double y) => Math.Sqrt((x * x) + (y * y));
public static bool LessThan(double value1, double value2) => value1 < value2 && !AreClose(value1, value2);
public static bool LessThanOrClose(double value1, double value2)
{
if (value1 >= value2)
{
return AreClose(value1, value2);
}
return true;
}
public static double EnsureRange(double value, double? min, double? max)
{
if (min.HasValue && value < min.Value)
{
return min.Value;
}
if (max.HasValue && value > max.Value)
{
return max.Value;
}
return value;
}
public static double SafeDivide(double lhs, double rhs, double fallback)
{
if (!IsVerySmall(rhs))
{
return lhs / rhs;
}
return fallback;
}
}
/// <summary>
/// 验证帮助类
/// </summary>
file class ValidateHelper
{
/// <summary>
/// 是否在浮点数范围内
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static bool IsInRangeOfDouble(object value)
{
var v = (double)value;
return !(double.IsNaN(v) || double.IsInfinity(v));
}
/// <summary>
/// 是否在正浮点数范围内
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static bool IsInRangeOfPosDouble(object value)
{
var v = (double)value;
return !(double.IsNaN(v) || double.IsInfinity(v)) && v > 0;
}
/// <summary>
/// 是否在正浮点数范围内(包括0)
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static bool IsInRangeOfPosDoubleIncludeZero(object value)
{
var v = (double)value;
return !(double.IsNaN(v) || double.IsInfinity(v)) && v >= 0;
}
/// <summary>
/// 是否在负浮点数范围内
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static bool IsInRangeOfNegDouble(object value)
{
var v = (double)value;
return !(double.IsNaN(v) || double.IsInfinity(v)) && v < 0;
}
/// <summary>
/// 是否在负浮点数范围内(包括0)
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static bool IsInRangeOfNegDoubleIncludeZero(object value)
{
var v = (double)value;
return !(double.IsNaN(v) || double.IsInfinity(v)) && v <= 0;
}
/// <summary>
/// 是否在正整数范围内
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static bool IsInRangeOfPosInt(object value)
{
var v = (int)value;
return v > 0;
}
/// <summary>
/// 是否在正整数范围内(包括0)
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static bool IsInRangeOfPosIntIncludeZero(object value)
{
var v = (int)value;
return v >= 0;
}
/// <summary>
/// 是否在负整数范围内
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static bool IsInRangeOfNegInt(object value)
{
var v = (int)value;
return v < 0;
}
/// <summary>
/// 是否在负整数范围内(包括0)
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static bool IsInRangeOfNegIntIncludeZero(object value)
{
var v = (int)value;
return v <= 0;
}
}