652 lines
26 KiB
C#
652 lines
26 KiB
C#
// Adapted from https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Slider.cs
|
|
|
|
using System;
|
|
using System.ComponentModel;
|
|
using System.Globalization;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Controls.Primitives;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using HandyControl.Data;
|
|
using HandyControl.Expression.Drawing;
|
|
using HandyControl.Tools;
|
|
|
|
namespace HandyControl.Controls
|
|
{
|
|
[DefaultEvent("ValueChanged"), DefaultProperty("Value")]
|
|
[TemplatePart(Name = ElementTrack, Type = typeof(Track))]
|
|
public class RangeSlider : TwoWayRangeBase
|
|
{
|
|
private const string ElementTrack = "PART_Track";
|
|
|
|
private RangeTrack _track;
|
|
|
|
private readonly ToolTip _autoToolTipStart = null;
|
|
|
|
private readonly ToolTip _autoToolTipEnd = null;
|
|
|
|
private RangeThumb _thumbCurrent;
|
|
|
|
private object _thumbOriginalToolTip;
|
|
|
|
private Point _originThumbPoint;
|
|
|
|
private Point _previousScreenCoordPosition;
|
|
|
|
static RangeSlider()
|
|
{
|
|
InitializeCommands();
|
|
|
|
MinimumProperty.OverrideMetadata(typeof(RangeSlider), new FrameworkPropertyMetadata(ValueBoxes.Double0Box, FrameworkPropertyMetadataOptions.AffectsMeasure));
|
|
MaximumProperty.OverrideMetadata(typeof(RangeSlider), new FrameworkPropertyMetadata(ValueBoxes.Double10Box, FrameworkPropertyMetadataOptions.AffectsMeasure));
|
|
ValueStartProperty.OverrideMetadata(typeof(RangeSlider), new FrameworkPropertyMetadata(ValueBoxes.Double0Box, FrameworkPropertyMetadataOptions.AffectsMeasure));
|
|
ValueEndProperty.OverrideMetadata(typeof(RangeSlider), new FrameworkPropertyMetadata(ValueBoxes.Double0Box, FrameworkPropertyMetadataOptions.AffectsMeasure));
|
|
|
|
// 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 RangeTrack;
|
|
|
|
if (_autoToolTipStart != null)
|
|
{
|
|
_autoToolTipStart.PlacementTarget = _track?.ThumbStart;
|
|
}
|
|
|
|
if (_autoToolTipEnd != null)
|
|
{
|
|
_autoToolTipEnd.PlacementTarget = _track?.ThumbEnd;
|
|
}
|
|
}
|
|
|
|
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
|
|
"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(
|
|
"IsDirectionReversed", typeof(bool), typeof(RangeSlider), new PropertyMetadata(ValueBoxes.FalseBox));
|
|
|
|
public bool IsDirectionReversed
|
|
{
|
|
get => (bool) GetValue(IsDirectionReversedProperty);
|
|
set => SetValue(IsDirectionReversedProperty, ValueBoxes.BooleanBox(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(
|
|
"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(
|
|
"AutoToolTipPrecision", typeof(int), typeof(RangeSlider), new PropertyMetadata(ValueBoxes.Int0Box),
|
|
ValidateHelper.IsInRangeOfPosIntIncludeZero);
|
|
|
|
public int AutoToolTipPrecision
|
|
{
|
|
get => (int) GetValue(AutoToolTipPrecisionProperty);
|
|
set => SetValue(AutoToolTipPrecisionProperty, value);
|
|
}
|
|
|
|
public static readonly DependencyProperty IsSnapToTickEnabledProperty = DependencyProperty.Register(
|
|
"IsSnapToTickEnabled", typeof(bool), typeof(RangeSlider), new PropertyMetadata(ValueBoxes.FalseBox));
|
|
|
|
public bool IsSnapToTickEnabled
|
|
{
|
|
get => (bool) GetValue(IsSnapToTickEnabledProperty);
|
|
set => SetValue(IsSnapToTickEnabledProperty, ValueBoxes.BooleanBox(value));
|
|
}
|
|
|
|
public static readonly DependencyProperty TickPlacementProperty = DependencyProperty.Register(
|
|
"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(
|
|
"TickFrequency", typeof(double), typeof(RangeSlider), new PropertyMetadata(ValueBoxes.Double1Box),
|
|
ValidateHelper.IsInRangeOfPosDoubleIncludeZero);
|
|
|
|
public double TickFrequency
|
|
{
|
|
get => (double) GetValue(TickFrequencyProperty);
|
|
set => SetValue(TickFrequencyProperty, value);
|
|
}
|
|
|
|
public static readonly DependencyProperty TicksProperty = DependencyProperty.Register(
|
|
"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(
|
|
"IsMoveToPointEnabled", typeof(bool), typeof(RangeSlider), new PropertyMetadata(ValueBoxes.FalseBox));
|
|
|
|
public bool IsMoveToPointEnabled
|
|
{
|
|
get => (bool) GetValue(IsMoveToPointEnabledProperty);
|
|
set => SetValue(IsMoveToPointEnabledProperty, ValueBoxes.BooleanBox(value));
|
|
}
|
|
|
|
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
|
|
{
|
|
if (IsMoveToPointEnabled && _track.ThumbStart is {IsMouseOver: false} && _track.ThumbEnd is {IsMouseOver: false})
|
|
{
|
|
// 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)
|
|
{
|
|
if (parameter == null)
|
|
{
|
|
var pt = Mouse.GetPosition(_track);
|
|
var newValue = _track.ValueFromPoint(pt);
|
|
if (ValidateHelper.IsInRangeOfDouble(newValue))
|
|
{
|
|
var dir = (ValueStart + ValueEnd) / 2 > newValue;
|
|
if (!dir)
|
|
{
|
|
isStart = false;
|
|
direction = -Math.Abs(direction);
|
|
}
|
|
else
|
|
{
|
|
isStart = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (parameter is bool parameterValue)
|
|
{
|
|
isStart = parameterValue;
|
|
}
|
|
}
|
|
|
|
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 is {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 is {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))
|
|
{
|
|
var isStart = (ValueStart + ValueEnd) / 2 > newValue;
|
|
UpdateValue(newValue, isStart);
|
|
}
|
|
}
|
|
|
|
private void UpdateValue(double value, bool isStart)
|
|
{
|
|
var snappedValue = SnapToTick(value);
|
|
|
|
if (isStart)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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 not RangeThumb thumb) return;
|
|
_thumbCurrent = thumb;
|
|
_originThumbPoint = Mouse.GetPosition(_thumbCurrent);
|
|
_thumbCurrent.StartDrag();
|
|
|
|
if (AutoToolTipPlacement == AutoToolTipPlacement.None)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var isStart = thumb.Equals(_track.ThumbStart);
|
|
if (!isStart)
|
|
{
|
|
if (!thumb.Equals(_track.ThumbEnd)) return;
|
|
}
|
|
|
|
// Save original tooltip
|
|
_thumbOriginalToolTip = thumb.ToolTip;
|
|
OnThumbDragStarted(isStart ? _autoToolTipStart : _autoToolTipEnd, isStart);
|
|
}
|
|
|
|
private void OnThumbDragStarted(ToolTip toolTip, bool isStart)
|
|
{
|
|
toolTip ??= new ToolTip
|
|
{
|
|
Placement = PlacementMode.Custom,
|
|
PlacementTarget = isStart ? _track.ThumbStart : _track.ThumbEnd,
|
|
CustomPopupPlacementCallback = AutoToolTipCustomPlacementCallback
|
|
};
|
|
|
|
if (isStart)
|
|
{
|
|
_track.ThumbStart.ToolTip = toolTip;
|
|
}
|
|
else
|
|
{
|
|
_track.ThumbEnd.ToolTip = toolTip;
|
|
}
|
|
toolTip.Content = GetAutoToolTipNumber(isStart);
|
|
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(bool isStart)
|
|
{
|
|
var format = (NumberFormatInfo) NumberFormatInfo.CurrentInfo.Clone();
|
|
format.NumberDecimalDigits = AutoToolTipPrecision;
|
|
return isStart ? ValueStart.ToString("N", format) : ValueEnd.ToString("N", format);
|
|
}
|
|
|
|
private static void OnThumbDragDelta(object sender, DragDeltaEventArgs e) => (sender as RangeSlider)?.OnThumbDragDelta(e);
|
|
|
|
protected virtual void OnThumbDragDelta(DragDeltaEventArgs e)
|
|
{
|
|
if (e.OriginalSource is not Thumb thumb) return;
|
|
var isStart = thumb.Equals(_track.ThumbStart);
|
|
if (!isStart)
|
|
{
|
|
if (!thumb.Equals(_track.ThumbEnd)) return;
|
|
}
|
|
|
|
// Convert to Track's co-ordinate
|
|
OnThumbDragDelta(_track, isStart, e);
|
|
}
|
|
|
|
private void OnThumbDragDelta(RangeTrack track, bool isStart, DragDeltaEventArgs e)
|
|
{
|
|
if (track == null || track.ThumbStart == null | _track.ThumbEnd == null) return;
|
|
|
|
var newValue = (isStart ? ValueStart : ValueEnd) + track.ValueFromDistance(e.HorizontalChange, e.VerticalChange);
|
|
if (ValidateHelper.IsInRangeOfDouble(newValue))
|
|
{
|
|
UpdateValue(newValue, isStart);
|
|
}
|
|
|
|
// Show AutoToolTip if needed
|
|
if (AutoToolTipPlacement != AutoToolTipPlacement.None)
|
|
{
|
|
var toolTip = (isStart ? _autoToolTipStart : _autoToolTipEnd) ?? new ToolTip();
|
|
|
|
toolTip.Content = GetAutoToolTipNumber(isStart);
|
|
|
|
var thumb = isStart ? _track.ThumbStart : _track.ThumbEnd;
|
|
|
|
if (!Equals(thumb.ToolTip, toolTip))
|
|
{
|
|
thumb.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 not Thumb thumb || AutoToolTipPlacement == AutoToolTipPlacement.None)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var isStart = thumb.Equals(_track.ThumbStart);
|
|
if (!isStart)
|
|
{
|
|
if (!thumb.Equals(_track.ThumbEnd)) return;
|
|
}
|
|
|
|
var toolTip = isStart ? _autoToolTipStart : _autoToolTipEnd;
|
|
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.ThumbEnd.IsMouseOver)
|
|
{
|
|
slider._track.ThumbEnd.StartDrag();
|
|
slider._thumbCurrent = slider._track.ThumbEnd;
|
|
}
|
|
}
|
|
|
|
protected override void OnMouseMove(MouseEventArgs e)
|
|
{
|
|
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;
|
|
_thumbCurrent.RaiseEvent(new DragDeltaEventArgs(thumbCoordPosition.X - _originThumbPoint.X,
|
|
thumbCoordPosition.Y - _originThumbPoint.Y));
|
|
}
|
|
}
|
|
|
|
protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
|
|
{
|
|
base.OnPreviewMouseLeftButtonUp(e);
|
|
|
|
_thumbCurrent = null;
|
|
}
|
|
}
|
|
}
|