Created
September 4, 2014 17:56
-
-
Save azchohfi/1f8bacc7654753bde58a to your computer and use it in GitHub Desktop.
SuperSlider for Windows Phone 8.x with Upper and Lower Values.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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