Number Stepper in WPF

WPF has been around since 2006 so I figured it might be a good idea to start learning it. Most people make the move from desktop to web but I seem to be heading in reverse, oh well. The current goal is to make a number stepper user control in WPF. After a bit of googling, it seems that a lot of people have tutorials out there explaining how to build a very basic number stepper control, which doesn’t really help me because I want to make a fancy one.

Example of the number stepper I'm creating
Example of the number stepper I’m creating

I started with creating a new UserControl in my project called NumberStepper.  I decided to go the Canvas route so I could absolutely position my buttons on the left and right of the control while masking the numbers that animate underneath it.  The numbers are dynamically generated in the code behind respecting the MaxValue and MinValue properties I’ve created for this control; more on that later.

<UserControl x:Class="TestApp.UI.Controls.NumberStepper"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" Background="White" Width="55" Height="150">
	<UserControl.Resources>
		<Style x:Key="NumberStepperBorder" TargetType="Border">
			<Setter Property="BorderThickness" Value="1"/>
			<Setter Property="BorderBrush" Value="{StaticResource DefaultBorder}"/>
			<Setter Property="Background" Value="White"/>
			<Setter Property="Effect">
				<Setter.Value>
					<DropShadowEffect Color="#999999" Direction="270" BlurRadius="10" ShadowDepth="3"/>
				</Setter.Value>
			</Setter>
		</Style>

		<Style x:Key="NumberStepperButton" TargetType="Button" BasedOn="{StaticResource Button}">
			<Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
			<Setter Property="Width" Value="50"/>
			<Setter Property="Height" Value="55"/>
			<Setter Property="Template">
				<Setter.Value>
					<ControlTemplate TargetType="{x:Type Button}">
						<Grid x:Name="grid">
							<Border x:Name="border" CornerRadius="0" BorderThickness="1" BorderBrush="{StaticResource DefaultBorder}" Background="{StaticResource DefaultBackground}">
								<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" TextElement.FontSize="50" />
							</Border>
						</Grid>
					</ControlTemplate>
				</Setter.Value>
			</Setter>
		</Style>
	</UserControl.Resources>
	<Border Style="{StaticResource NumberStepperBorder}">
		<Canvas x:Name="cvsMain" ClipToBounds="True" Margin="0">
			<StackPanel x:Name="pnlSlider" Canvas.Top="50" Canvas.Left="0" Orientation="Horizontal">
			</StackPanel>
			<Button Canvas.Top="0" x:Name="btnUp" Click="btnUp_Click" Style="{StaticResource NumberStepperButton}">+</Button>
			<Button Canvas.Bottom="0" FontWeight="Bold" x:Name="btnDown" Click="btnDown_Click" Style="{StaticResource NumberStepperButton}">-</Button>
		</Canvas>
	</Border>
</UserControl>

This gives me the basic structure of the control. I define two styles at the top to use for the buttons and the border wrapping the whole thing. The button style overrides the template property to put a fancy rounded border around the up and down buttons. Notice how the pnlSlider StackPanel doesn’t have any items inside of it initially. This is because this section is generated dynamically in the back end and is dependent on the MinValue and MaxValue properties.

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.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace TestApp.UI.Controls
{
	
	/// <summary>
	/// Interaction logic for NumberStepper.xaml
	/// </summary>
	public partial class NumberStepper : UserControl, INotifyPropertyChanged
	{


		private int _value = 0;
		private int _minValue = 0;
		private int _maxValue = 99;
		private bool _rollover = true;

		/// <summary>
		/// Gets or sets a value indicating whether this <see cref="NumberStepper"/> should rollover when reaching the min or max numbers.
		/// </summary>
		/// <value><c>true</c> if this number stepper should rollover; otherwise, <c>false</c>.</value>
		public bool Rollover 
		{
			get { return _rollover; }
			set 
			{
				_rollover = value;
				OnPropertyChanged("Rollover");
			}
		}

		/// <summary>
		/// Gets or sets the value of this number stepper.
		/// </summary>
		public int Value 
		{
			get { return _value; }
			set
			{
				var offset = 52;
				double current = (offset * _value) * -1;

				if (value >= _minValue && value <= _maxValue)
				{
					_value = value;
				}
				else if(Rollover)
				{
					if (value > _maxValue)
					{
						_value = MinValue;
					}
					else if (value < _minValue)
					{
						_value = MaxValue;
					}
				}
				var distance = (offset * _value) * -1;
				OnPropertyChanged("Value");
				AnimateSlider(current, distance, 200);
			}
		}

		/// <summary>
		/// Gets or sets the minimum value.
		/// </summary>
		public int MinValue 
		{
			get { return _minValue; }
			set
			{
				_minValue = value;
				OnPropertyChanged("MinValue");
			}
		}

		/// <summary>
		/// Gets or sets the maximum value.
		/// </summary>
		public int MaxValue 
		{
			get { return _maxValue; }
			set
			{
				_maxValue = value;
				OnPropertyChanged("MaxValue");
			}
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="NumberStepper"/> class.
		/// </summary>
		public NumberStepper()
		{
			InitializeComponent();
			RedrawUI();
		}

		/// <summary>
		/// Redraws the UI.
		/// </summary>
		protected void RedrawUI()
		{
			pnlSlider.Children.Clear();
			for (var i = MinValue; i <= MaxValue; i++)
			{
				var textBlock = new TextBlock() { Text = i.ToString(), FontSize = 22, Width = _numberWidth, Height = _numberHeight, TextAlignment = TextAlignment.Center, Padding = new Thickness(15) };
				pnlSlider.Children.Add(textBlock);
			}
		}

		/// <summary>
		/// Handles the Click event of the btnDown control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="RoutedEventArgs"/> instance containing the event data.</param>
		private void btnDown_Click(object sender, RoutedEventArgs e)
		{
			Value--;
		}

		/// <summary>
		/// Handles the Click event of the btnUp control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="RoutedEventArgs"/> instance containing the event data.</param>
		private void btnUp_Click(object sender, RoutedEventArgs e)
		{
			Value++;
		}

		/// <summary>
		/// Animates the slider.
		/// </summary>
		/// <param name="start">The start.</param>
		/// <param name="end">The end.</param>
		/// <param name="speed">The speed.</param>
		private void AnimateSlider(double start, double end, int speed)
		{
			var translate = new TranslateTransform();
			pnlSlider.RenderTransform = translate;
			var animation = new DoubleAnimation(start, end, TimeSpan.FromMilliseconds(speed));
			translate.BeginAnimation(TranslateTransform.XProperty, animation);
		}

		/// <summary>
		/// Called when a property is changed.
		/// </summary>
		/// <param name="name">The name of the property that was changed.</param>
		protected void OnPropertyChanged(string name)
		{
			var handler = PropertyChanged;
			if (handler != null)
				handler(this, new PropertyChangedEventArgs(name));
			if(name == "MinValue" || name == "MaxValue")
				RedrawUI();
		}

		/// <summary>
		/// Occurs when a property value changes.
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;

	}

}

The code is pretty heavily documented but the basic idea is generating the TextBlock number elements dynamically based around MinValue and MaxValue properties as the high and low ends. The RedrawUI() method starts a loop from low to high and dynamically creates the TextBlock elements to add to the pnlSlider control.

The click event handlers for the up and down buttons basically just increment / decrement the Value property. The setter of the Value property kicks off the animation via the AnimateSlider() method. This makes it so the animation will still happen, even if you’re manipulating the Value property from outside this UserControl.

Possible additions to add to this control could be the ability to configure it for both horizontal and vertical layouts. It also would be nice to allow it to support dragging the numbers to select a value instead of having to always push the up and down buttons. Maybe I’ll revisit this in another post.

Read More

Debugging the dreaded Uncaught object in AngularJS

I’ve been doing a lot of AngularJS development recently, mainly exploring all of the 3rd party libraries available from the community. One problem I kept running into is the “Uncaught object” error that AngularJS throws sometimes. What’s really difficult about this error is that there is no other information in the console about it. This error is thrown then you misspell or forget to include a reference to a module’s dependency when bootstrapping your AngularJS app.

Although I cant find much more information about it, it seems that the nightly build of Chrome, Canary, has extra information relating to AngularJS errors that pop up in the console window. When AngularJS is being lame and not outputting informative error messages, I now switch to Canary to get more information.

Uncaught Error message in Chome
Uncaught Error message in Chome
Uncaught Error message in Canary
Uncaught Error message in Canary

Read More