WPF 双滑块自定义控件(RangeSlider)

感谢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;
        }
    }

  • 20
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值