using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; namespace smx_config { // A slider with two handles, and a handle connecting them. Dragging the handle drags both // of the sliders. class DoubleSlider: Control { public delegate void ValueChangedDelegate(DoubleSlider slider); public event ValueChangedDelegate ValueChanged; // The minimum value for either knob. public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register("Minimum", typeof(double), typeof(DoubleSlider), new FrameworkPropertyMetadata(0.0)); public double Minimum { get { return (double) GetValue(MinimumProperty); } set { SetValue(MinimumProperty, value); } } // The maximum value for either knob. public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register("Maximum", typeof(double), typeof(DoubleSlider), new FrameworkPropertyMetadata(20.0)); public double Maximum { get { return (double) GetValue(MaximumProperty); } set { SetValue(MaximumProperty, value); } } // The minimum distance between the two values. public static readonly DependencyProperty MinimumDistanceProperty = DependencyProperty.Register("MinimumDistance", typeof(double), typeof(DoubleSlider), new FrameworkPropertyMetadata(0.0)); public double MinimumDistance { get { return (double) GetValue(MinimumDistanceProperty); } set { SetValue(MinimumDistanceProperty, value); } } // Clamp value between minimum and maximum. private double CoerceValueToLimits(double value) { return Math.Min(Math.Max(value, Minimum), Maximum); } // Note that we only clamp LowerValue and UpperValue to the min/max values. We don't // clamp them to each other or to MinimumDistance here, since that complicates setting // properties a lot. We only clamp to those when the user manipulates the control, not // when we set values directly. private static object LowerValueCoerceValueCallback(DependencyObject target, object valueObject) { DoubleSlider slider = target as DoubleSlider; double value = (double)valueObject; value = slider.CoerceValueToLimits(value); return value; } private static object UpperValueCoerceValueCallback(DependencyObject target, object valueObject) { DoubleSlider slider = target as DoubleSlider; double value = (double)valueObject; value = slider.CoerceValueToLimits(value); return value; } private static void SliderValueChangedCallback(DependencyObject target, DependencyPropertyChangedEventArgs args) { DoubleSlider slider = target as DoubleSlider; if(slider.ValueChanged != null) slider.ValueChanged.Invoke(slider); } public static readonly DependencyProperty LowerValueProperty = DependencyProperty.Register("LowerValue", typeof(double), typeof(DoubleSlider), new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsArrange, SliderValueChangedCallback, LowerValueCoerceValueCallback)); public double LowerValue { get { return (double) GetValue(LowerValueProperty); } set { SetValue(LowerValueProperty, value); } } public static readonly DependencyProperty UpperValueProperty = DependencyProperty.Register("UpperValue", typeof(double), typeof(DoubleSlider), new FrameworkPropertyMetadata(15.0, FrameworkPropertyMetadataOptions.AffectsArrange, SliderValueChangedCallback, UpperValueCoerceValueCallback)); public double UpperValue { get { return (double) GetValue(UpperValueProperty); } set { SetValue(UpperValueProperty, value); } } private Thumb Middle; Thumb UpperThumb; Thumb LowerThumb; private RepeatButton DecreaseButton; private RepeatButton IncreaseButton; protected override Size ArrangeOverride(Size arrangeSize) { arrangeSize = base.ArrangeOverride(arrangeSize); // Figure out the X position of the upper and lower thumbs. Note that we have to provide // our width to GetValueToSize, since ActualWidth isn't available yet. double valueToSize = GetValueToSize(arrangeSize.Width); double UpperPointX = (UpperValue-Minimum) * valueToSize; double LowerPointX = (LowerValue-Minimum) * valueToSize; // Move the upper and lower handles out by this much, and extend this middle. This // makes the middle handle bigger. double OffsetOutwards = 5; Middle.Arrange(new Rect(LowerPointX-OffsetOutwards-1, 0, Math.Max(1, UpperPointX-LowerPointX+OffsetOutwards*2+2), arrangeSize.Height)); // Right-align the lower thumb and left-align the upper thumb. LowerThumb.Arrange(new Rect(LowerPointX-LowerThumb.Width-OffsetOutwards, 0, LowerThumb.Width, arrangeSize.Height)); UpperThumb.Arrange(new Rect(UpperPointX +OffsetOutwards, 0, UpperThumb.Width, arrangeSize.Height)); DecreaseButton.Arrange(new Rect(0, 0, Math.Max(1, LowerPointX), Math.Max(1, arrangeSize.Height))); IncreaseButton.Arrange(new Rect(UpperPointX, 0, Math.Max(1, arrangeSize.Width - UpperPointX), arrangeSize.Height)); return arrangeSize; } private void MoveValue(double delta) { if(delta > 0) { // If this increase will be clamped when changing the upper value, reduce it // so it clamps the lower value too. This way, the distance between the upper // and lower value stays the same. delta = Math.Min(delta, Maximum - UpperValue); UpperValue += delta; LowerValue += delta; } else { delta *= -1; delta = Math.Min(delta, LowerValue - Minimum); LowerValue -= delta; UpperValue -= delta; } } private double GetValueToSize() { return GetValueToSize(this.ActualWidth); } private double GetValueToSize(double width) { double Range = Maximum - Minimum; return Math.Max(0.0, (width - UpperThumb.RenderSize.Width) / Range); } public override void OnApplyTemplate() { base.OnApplyTemplate(); LowerThumb = GetTemplateChild("PART_LowerThumb") as Thumb; UpperThumb = GetTemplateChild("PART_UpperThumb") as Thumb; Middle = GetTemplateChild("PART_Middle") as Thumb; DecreaseButton = GetTemplateChild("PART_DecreaseButton") as RepeatButton; IncreaseButton = GetTemplateChild("PART_IncreaseButton") as RepeatButton; DecreaseButton.Click += delegate(object sender, RoutedEventArgs e) { MoveValue(-1); }; IncreaseButton.Click += delegate(object sender, RoutedEventArgs e) { MoveValue(+1); }; LowerThumb.DragDelta += delegate(object sender, DragDeltaEventArgs e) { double sizeToValue = 1 / GetValueToSize(); double NewValue = LowerValue + e.HorizontalChange * sizeToValue; NewValue = Math.Min(NewValue, UpperValue - MinimumDistance); LowerValue = NewValue; }; UpperThumb.DragDelta += delegate(object sender, DragDeltaEventArgs e) { double sizeToValue = 1 / GetValueToSize(); double NewValue = UpperValue + e.HorizontalChange * sizeToValue; NewValue = Math.Max(NewValue, LowerValue + MinimumDistance); UpperValue = NewValue; }; Middle.DragDelta += delegate(object sender, DragDeltaEventArgs e) { // Convert the pixel delta to a value change. double sizeToValue = 1 / GetValueToSize(); Console.WriteLine("drag: " + e.HorizontalChange + ", " + sizeToValue + ", " + e.HorizontalChange * sizeToValue); MoveValue(e.HorizontalChange * sizeToValue); }; InvalidateArrange(); } } class DoubleSliderThumb: Thumb { public static readonly DependencyProperty ShowUpArrowProperty = DependencyProperty.Register("ShowUpArrow", typeof(bool), typeof(DoubleSliderThumb)); public bool ShowUpArrow { get { return (bool) this.GetValue(ShowUpArrowProperty); } set { this.SetValue(ShowUpArrowProperty, value); } } public static readonly DependencyProperty ShowDownArrowProperty = DependencyProperty.Register("ShowDownArrow", typeof(bool), typeof(DoubleSliderThumb)); public bool ShowDownArrow { get { return (bool) this.GetValue(ShowDownArrowProperty); } set { this.SetValue(ShowDownArrowProperty, value); } } } }