summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Platform.UAP/StepperControl.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Xamarin.Forms.Platform.UAP/StepperControl.cs')
-rw-r--r--Xamarin.Forms.Platform.UAP/StepperControl.cs227
1 files changed, 227 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.UAP/StepperControl.cs b/Xamarin.Forms.Platform.UAP/StepperControl.cs
new file mode 100644
index 00000000..0de09318
--- /dev/null
+++ b/Xamarin.Forms.Platform.UAP/StepperControl.cs
@@ -0,0 +1,227 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Media;
+
+namespace Xamarin.Forms.Platform.UWP
+{
+ public sealed class StepperControl : Control
+ {
+ public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(StepperControl), new PropertyMetadata(default(double), OnValueChanged));
+
+ public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register("Maximum", typeof(double), typeof(StepperControl), new PropertyMetadata(default(double), OnMaxMinChanged));
+
+ public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register("Minimum", typeof(double), typeof(StepperControl), new PropertyMetadata(default(double), OnMaxMinChanged));
+
+ public static readonly DependencyProperty IncrementProperty = DependencyProperty.Register("Increment", typeof(double), typeof(StepperControl),
+ new PropertyMetadata(default(double), OnIncrementChanged));
+
+ Windows.UI.Xaml.Controls.Button _plus;
+ Windows.UI.Xaml.Controls.Button _minus;
+ VisualStateCache _plusStateCache;
+ VisualStateCache _minusStateCache;
+
+ public StepperControl()
+ {
+ DefaultStyleKey = typeof(StepperControl);
+ }
+
+ public double Increment
+ {
+ get { return (double)GetValue(IncrementProperty); }
+ set { SetValue(IncrementProperty, 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); }
+ }
+
+ public double Value
+ {
+ get { return (double)GetValue(ValueProperty); }
+ set { SetValue(ValueProperty, value); }
+ }
+
+ public event EventHandler ValueChanged;
+
+ protected override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+
+ _plus = GetTemplateChild("Plus") as Windows.UI.Xaml.Controls.Button;
+ if (_plus != null)
+ _plus.Click += OnPlusClicked;
+
+ _minus = GetTemplateChild("Minus") as Windows.UI.Xaml.Controls.Button;
+ if (_minus != null)
+ _minus.Click += OnMinusClicked;
+
+ UpdateEnabled(Value);
+ }
+
+ static void OnIncrementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var stepper = (StepperControl)d;
+ stepper.UpdateEnabled(stepper.Value);
+ }
+
+ static void OnMaxMinChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var stepper = (StepperControl)d;
+ stepper.UpdateEnabled(stepper.Value);
+ }
+
+ void OnMinusClicked(object sender, RoutedEventArgs e)
+ {
+ UpdateValue(-Increment);
+ }
+
+ void OnPlusClicked(object sender, RoutedEventArgs e)
+ {
+ UpdateValue(+Increment);
+ }
+
+ static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var stepper = (StepperControl)d;
+ stepper.UpdateEnabled((double)e.NewValue);
+
+ EventHandler changed = stepper.ValueChanged;
+ if (changed != null)
+ changed(d, EventArgs.Empty);
+ }
+
+ VisualStateCache PseudoDisable(Control control)
+ {
+ if (VisualTreeHelper.GetChildrenCount(control) == 0)
+ control.ApplyTemplate();
+
+ VisualStateManager.GoToState(control, "Disabled", true);
+
+ var rootElement = (FrameworkElement)VisualTreeHelper.GetChild(control, 0);
+
+ var cache = new VisualStateCache();
+ IList<VisualStateGroup> groups = VisualStateManager.GetVisualStateGroups(rootElement);
+
+ VisualStateGroup common = null;
+ foreach (var group in groups)
+ {
+ if (group.Name == "CommonStates")
+ common = group;
+ else if (group.Name == "FocusStates")
+ cache.FocusStates = group;
+ else if (cache.FocusStates != null && common != null)
+ break;
+ }
+
+ if (cache.FocusStates != null)
+ groups.Remove(cache.FocusStates);
+
+ if (common != null)
+ {
+ foreach (VisualState state in common.States)
+ {
+ if (state.Name == "Normal")
+ cache.Normal = state;
+ else if (state.Name == "Pressed")
+ cache.Pressed = state;
+ else if (state.Name == "PointerOver")
+ cache.PointerOver = state;
+ }
+
+ if (cache.Normal != null)
+ common.States.Remove(cache.Normal);
+ if (cache.Pressed != null)
+ common.States.Remove(cache.Pressed);
+ if (cache.PointerOver != null)
+ common.States.Remove(cache.PointerOver);
+ }
+
+ return cache;
+ }
+
+ /*
+ The below serves as a way to disable the button visually, rather than using IsEnabled. It's a hack
+ but should remain stable as long as the user doesn't change the WinRT Button template too much.
+
+ The reason we're not using IsEnabled is that the buttons have a click radius that overlap about 40%
+ of the next button. This doesn't cause a problem until one button becomes disabled, then if you think
+ you're still hitting + (haven't noticed its disabled), you will actually hit -. This hack doesn't
+ completely solve the problem, but it drops the overlap to something like 20%. I haven't found the root
+ cause, so this will have to suffice for now.
+ */
+
+ void PsuedoEnable(Control control, ref VisualStateCache cache)
+ {
+ if (cache == null || VisualTreeHelper.GetChildrenCount(control) == 0)
+ return;
+
+ var rootElement = (FrameworkElement)VisualTreeHelper.GetChild(control, 0);
+
+ IList<VisualStateGroup> groups = VisualStateManager.GetVisualStateGroups(rootElement);
+
+ if (cache.FocusStates != null)
+ groups.Add(cache.FocusStates);
+
+ var commonStates = groups.FirstOrDefault(g => g.Name == "CommonStates");
+ if (commonStates == null)
+ return;
+
+ if (cache.Normal != null)
+ commonStates.States.Insert(0, cache.Normal); // defensive
+ if (cache.Pressed != null)
+ commonStates.States.Add(cache.Pressed);
+ if (cache.PointerOver != null)
+ commonStates.States.Add(cache.PointerOver);
+
+ VisualStateManager.GoToState(control, "Normal", true);
+
+ cache = null;
+ }
+
+ void UpdateEnabled(double value)
+ {
+ double increment = Increment;
+ if (_plus != null)
+ {
+ if (value + increment > Maximum)
+ _plusStateCache = PseudoDisable(_plus);
+ else
+ PsuedoEnable(_plus, ref _plusStateCache);
+ }
+
+ if (_minus != null)
+ {
+ if (value - increment < Minimum)
+ _minusStateCache = PseudoDisable(_minus);
+ else
+ PsuedoEnable(_minus, ref _minusStateCache);
+ }
+ }
+
+ void UpdateValue(double delta)
+ {
+ double newValue = Value + delta;
+ if (newValue > Maximum || newValue < Minimum)
+ return;
+
+ Value = newValue;
+ }
+
+ class VisualStateCache
+ {
+ public VisualStateGroup FocusStates;
+ public VisualState Normal, PointerOver, Pressed;
+ }
+ }
+} \ No newline at end of file