Skip to content

Instantly share code, notes, and snippets.

@azchohfi
Created September 4, 2014 17:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save azchohfi/1f8bacc7654753bde58a to your computer and use it in GitHub Desktop.
Save azchohfi/1f8bacc7654753bde58a to your computer and use it in GitHub Desktop.
SuperSlider for Windows Phone 8.x with Upper and Lower Values.
<Style TargetType="controls:SuperRangeSlider">
<Setter Property="Background" Value="{StaticResource PhoneChromeBrush}" />
<Setter Property="Foreground" Value="{StaticResource PhoneAccentBrush}" />
<Setter Property="Padding" Value="0, 3, 0, 36" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:SuperRangeSlider">
<Grid Name="Body">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetProperty="Fill"
Storyboard.TargetName="ProgressRectangle">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneDisabledBrush}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetProperty="Fill"
Storyboard.TargetName="BackgroundRectangle">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneChromeBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid Grid.Row="1">
<Border Margin="{TemplateBinding Padding}">
<Grid Name="BarBody">
<Rectangle
Grid.Row="1"
Name="BackgroundRectangle"
Fill="{TemplateBinding Background}" />
<StackPanel Grid.Row="1" Orientation="Horizontal">
<Rectangle
Name="ProgressInitialDifRectangle" />
<Rectangle
Name="ProgressRectangle"
Fill="{TemplateBinding Foreground}" />
</StackPanel>
<ContentPresenter
Grid.Row="1"
Content="{TemplateBinding Thumb}" />
</Grid>
</Border>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
namespace Coding4Fun.Toolkit.Controls
{
[TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
public class SuperRangeSlider : Control
{
#region Constants
private const string BackgroundRectangleName = "BackgroundRectangle";
private const string BarBodyName = "BarBody";
private const string BodyName = "Body";
private const string ProgressRectangleName = "ProgressRectangle";
private const string ProgressInitialDifRectangleName = "ProgressInitialDifRectangle";
#endregion
#region Static Fields
// Using a DependencyProperty as the backing store for BarHeight. This enables animation, styling, binding, etc...Thumb
public static readonly DependencyProperty BarHeightProperty =
DependencyProperty.Register("BarHeight", typeof(double), typeof(SuperRangeSlider), new PropertyMetadata(24d, OnLayoutChanged));
// Using a DependencyProperty as the backing store for BarHeight. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BarWidthProperty =
DependencyProperty.Register("BarWidth", typeof(double), typeof(SuperRangeSlider), new PropertyMetadata(24d, OnLayoutChanged));
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(double), typeof(SuperRangeSlider), new PropertyMetadata(10d));
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register("Minimum", typeof(double), typeof(SuperRangeSlider), new PropertyMetadata(0d));
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register("Orientation", typeof(Orientation), typeof(SuperRangeSlider), new PropertyMetadata(Orientation.Horizontal, OnLayoutChanged));
public static readonly DependencyProperty StepProperty =
DependencyProperty.Register("StepFrequency", typeof(double), typeof(SuperRangeSlider), new PropertyMetadata(0d));
public static readonly DependencyProperty ThumbProperty =
DependencyProperty.Register("Thumb", typeof(object), typeof(SuperRangeSlider), new PropertyMetadata(OnLayoutChanged));
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(SuperRangeSlider), new PropertyMetadata(string.Empty));
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LowerValueProperty =
DependencyProperty.Register("LowerValue", typeof(double), typeof(SuperRangeSlider), new PropertyMetadata(0d, OnLowerValueChanged));
public static readonly DependencyProperty UpperValueProperty =
DependencyProperty.Register("UpperValue", typeof(double), typeof(SuperRangeSlider), new PropertyMetadata(0d, OnUpperValueChanged));
#endregion
#region Fields
protected Rectangle BackgroundRectangle;
protected Rectangle ProgressRectangle;
protected Rectangle ProgressInitialDifRectangle;
private bool _isLayoutInit;
private MovementMonitor _monitor;
#endregion
#region Constructors and Destructors
public SuperRangeSlider()
{
DefaultStyleKey = typeof(SuperRangeSlider);
PreventScrollBinding.SetIsEnabled(this, true);
IsEnabledChanged += SuperRangeSlider_IsEnabledChanged;
Loaded += SuperRangeSlider_Loaded;
SizeChanged += SuperRangeSlider_SizeChanged;
}
#endregion
#region Public Events
public event RoutedPropertyChangedEventHandler<double> LowerValueChanged;
public event RoutedPropertyChangedEventHandler<double> UpperValueChanged;
#endregion
#region Public Properties
public double BarHeight
{
get
{
return (double)GetValue(BarHeightProperty);
}
set
{
SetValue(BarHeightProperty, value);
}
}
public double BarWidth
{
get
{
return (double)GetValue(BarWidthProperty);
}
set
{
SetValue(BarWidthProperty, value);
}
}
public double Maximum
{
get
{
return (double)GetValue(MaximumProperty);
}
set
{
SetValue(MaximumProperty, value);
}
}
public double Minimum
{
get
{
return (double)GetValue(MinimumProperty);
}
set
{
SetValue(MinimumProperty, value);
}
}
// Using a DependencyProperty as the backing store for Maximum. This enables animation, styling, binding, etc...
public Orientation Orientation
{
get
{
return (Orientation)GetValue(OrientationProperty);
}
set
{
SetValue(OrientationProperty, value);
}
}
public double StepFrequency
{
get
{
return (double)GetValue(StepProperty);
}
set
{
SetValue(StepProperty, value);
}
}
public object Thumb
{
get
{
return GetValue(ThumbProperty);
}
set
{
SetValue(ThumbProperty, value);
}
}
public string Title
{
get
{
return (string)GetValue(TitleProperty);
}
set
{
SetValue(TitleProperty, value);
}
}
public double LowerValue
{
get
{
return (double)GetValue(LowerValueProperty);
}
set
{
SetValue(LowerValueProperty, value);
}
}
public double UpperValue
{
get
{
return (double)GetValue(UpperValueProperty);
}
set
{
SetValue(UpperValueProperty, value);
}
}
#endregion
#region Public Methods and Operators
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
BackgroundRectangle = GetTemplateChild(BackgroundRectangleName) as Rectangle;
ProgressRectangle = GetTemplateChild(ProgressRectangleName) as Rectangle;
ProgressInitialDifRectangle = GetTemplateChild(ProgressInitialDifRectangleName) as Rectangle;
var body = GetTemplateChild(BodyName) as Grid;
if(body != null)
{
_monitor = new MovementMonitor();
_monitor.Movement += Monitor_Movement;
_monitor.MonitorControl(body);
}
// stuff isn't set enough but if this isn't done, there will an initial flash
AdjustLayout();
}
#endregion
// Using a DependencyProperty as the backing store for Orientation. This enables animation, styling, binding, etc...
#region Methods
private static void OnLayoutChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var sender = o as SuperRangeSlider;
if(sender != null && e.NewValue != e.OldValue)
{
sender.AdjustAndUpdateLayout();
}
}
private static void OnLowerValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var sender = o as SuperRangeSlider;
if(sender != null && e.NewValue != e.OldValue)
{
sender.SyncLowerValueAndPosition((double)e.NewValue, (double)e.OldValue);
}
}
private static void OnUpperValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var sender = o as SuperRangeSlider;
if (sender != null && e.NewValue != e.OldValue)
{
sender.SyncUpperValueAndPosition((double)e.NewValue, (double)e.OldValue);
}
}
private static void SetAlignment(FrameworkElement control, bool isVert)
{
if(control == null)
{
return;
}
control.HorizontalAlignment = isVert ? HorizontalAlignment.Stretch : HorizontalAlignment.Left;
control.VerticalAlignment = isVert ? VerticalAlignment.Bottom : VerticalAlignment.Stretch;
}
private static void SetSizeBasedOnOrientation(FrameworkElement controlInitialValue, FrameworkElement control, bool isVert, double initialValue, double offset)
{
if (control == null || controlInitialValue == null)
{
return;
}
if(isVert)
{
controlInitialValue.Height = initialValue;
control.Height = offset;
}
else
{
controlInitialValue.Width = initialValue;
control.Width = offset;
}
}
private void AdjustAndUpdateLayout()
{
AdjustLayout();
UpdateUserInterface();
}
private void AdjustLayout()
{
if (ProgressRectangle == null || BackgroundRectangle == null || ProgressInitialDifRectangle == null)
{
return;
}
var isVert = IsVertical();
var bar = GetTemplateChild(BarBodyName) as Grid;
if(bar != null)
{
if(isVert)
{
// var widthBinding = new System.Windows.Data.Binding
// {
// Source = bar,
// Path = new PropertyPath("Width"),
// Mode = BindingMode.TwoWay
// };
// SetBinding(BarWidthProperty, widthBinding);
bar.Width = BarWidth;
bar.Height = double.NaN;
}
else
{
// var heightBinding = new System.Windows.Data.Binding
// {
// Source = bar,
// Path = new PropertyPath("Height"),
// Mode = BindingMode.TwoWay
// };
// SetBinding(BarHeightProperty, heightBinding);
bar.Width = double.NaN;
bar.Height = BarHeight;
}
}
SetAlignment(ProgressRectangle, isVert);
SetAlignment(ProgressInitialDifRectangle, isVert);
ProgressRectangle.Width = double.NaN;
ProgressRectangle.Height = double.NaN;
ProgressInitialDifRectangle.Width = double.NaN;
ProgressInitialDifRectangle.Height = double.NaN;
BackgroundRectangle.Width = double.NaN;
BackgroundRectangle.Height = double.NaN;
if(Thumb != null)
{
SetAlignment(Thumb as FrameworkElement, isVert);
}
}
private double GetControlMax()
{
return IsVertical() ? ActualHeight : ActualWidth;
}
private void IsEnabledVisualStateUpdate()
{
VisualStateManager.GoToState(this, IsEnabled ? "Normal" : "Disabled", true);
}
private bool IsVertical()
{
return Orientation == Orientation.Vertical;
}
private void SuperRangeSlider_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
IsEnabledVisualStateUpdate();
}
private void SuperRangeSlider_Loaded(object sender, RoutedEventArgs e)
{
_isLayoutInit = true;
AdjustAndUpdateLayout();
IsEnabledVisualStateUpdate();
}
private void SuperRangeSlider_SizeChanged(object sender, SizeChangedEventArgs e)
{
AdjustAndUpdateLayout();
}
private void SyncLowerValueAndPosition(double newValue, double oldValue)
{
if(!_isLayoutInit)
{
return;
}
_isLayoutInit = true;
if(StepFrequency > 0)
{
var stepDiff = newValue % StepFrequency;
var floor = Math.Floor(newValue - stepDiff);
newValue = stepDiff < (StepFrequency / 2d) ? floor : floor + StepFrequency;
}
newValue = newValue.CheckBound(Minimum, Maximum);
newValue = newValue.CheckBound(Minimum, UpperValue);
if(oldValue.AlmostEquals(newValue))
{
return;
}
LowerValue = newValue;
UpdateUserInterface();
if (LowerValueChanged != null)
{
LowerValueChanged(this, new RoutedPropertyChangedEventArgs<double>(oldValue, LowerValue));
}
}
private void SyncUpperValueAndPosition(double newValue, double oldValue)
{
if (!_isLayoutInit)
{
return;
}
_isLayoutInit = true;
if (StepFrequency > 0)
{
var stepDiff = newValue % StepFrequency;
var floor = Math.Floor(newValue - stepDiff);
newValue = stepDiff < (StepFrequency / 2d) ? floor : floor + StepFrequency;
}
newValue = newValue.CheckBound(Minimum, Maximum);
newValue = newValue.CheckBound(LowerValue, Maximum);
if (oldValue.AlmostEquals(newValue))
{
return;
}
UpperValue = newValue;
UpdateUserInterface();
if (UpperValueChanged != null)
{
UpperValueChanged(this, new RoutedPropertyChangedEventArgs<double>(oldValue, UpperValue));
}
}
private void UpdateSampleBasedOnManipulation(double x, double y)
{
var controlMax = GetControlMax();
var offsetValue = IsVertical() ? controlMax - y : x;
var controlDist = offsetValue.CheckBound(controlMax);
var calculateValue = Minimum;
if(!controlMax.AlmostEquals(0.0))
{
calculateValue += (Maximum - Minimum) * (controlDist / controlMax);
}
var difLower = Math.Abs(calculateValue - LowerValue);
var difUpper = Math.Abs(calculateValue - UpperValue);
if(difLower < difUpper)
{
SyncLowerValueAndPosition(calculateValue, LowerValue);
}
else
{
SyncUpperValueAndPosition(calculateValue, UpperValue);
}
}
private void UpdateUserInterface()
{
var controlMax = GetControlMax();
var initialValue = ((LowerValue - Minimum) / (Maximum - Minimum)) * controlMax;
var offset = ((UpperValue - LowerValue) / (Maximum - Minimum)) * controlMax;
var isVert = IsVertical();
SetSizeBasedOnOrientation(ProgressInitialDifRectangle, ProgressRectangle, isVert, initialValue, offset);
var thumbItem = Thumb as FrameworkElement;
if(thumbItem != null)
{
var thumbItemSize = isVert ? thumbItem.ActualHeight : thumbItem.ActualWidth;
var marginOffset = (offset - (thumbItemSize / 2d)).CheckBound(controlMax - thumbItemSize);
thumbItem.Margin = isVert ? new Thickness(0, 0, 0, marginOffset) : new Thickness(marginOffset, 0, 0, 0);
}
}
private void Monitor_Movement(object sender, MovementMonitorEventArgs e)
{
UpdateSampleBasedOnManipulation(e.X, e.Y);
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment