Creating a Dial (knob) Control in Windows Phone 8

Today we are going to create a circular Dial (knob) for windows Phone 8, as we know that there isn’t much out there that is similar to a dial control for windows Phone, and sometimes we are in need of one, I believe after this tutorial, this would be resolved and developers would use them in the applications.
The sole purpose behind this tutorial is to have the ability to create immersive user experience, and eye-catching applications, and this might help in achieving that.

Open Visual studio and create a blank windows Phone Application, name the project and click OK

 

Right click on the Solution of the solution Explorer > Add > New Project

Select Windows Phone Class Library and Name it Dial

 

Once the other project is created, you are going to see a cs file by the name Class1. Select this and from the properties change the name to Dial

 

A Pop up is going to appear, asking to rename all the references in the Project, click YES

 

Right click the Dial project and select Add > New Folder, Name it Themes

 

Right click on the Themes Folder and select Add > New Item, Select Text File and Name it Generic.xaml

 

In The XAML pane, type the following Code

 <ResourceDictionary
 
 xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
 
 xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
 
 xmlns:local="clr-namespace:Dial">
 
 <Style TargetType="local:Dial">
 
 <Setter Property="Template">
 
 <Setter.Value>
 
 <ControlTemplate TargetType="local:Dial">
 
 <Grid x:Name="Knob">
 
 <ContentPresenter x:Name="DialFace" Content="{TemplateBinding Face}"/>
 
 <ContentPresenter x:Name="DialKnob" Content="{TemplateBinding Knob}" RenderTransformOrigin="0.5,0.5">
 
 <ContentPresenter.RenderTransform>
 
 <TransformGroup>
 
 <RotateTransform x:Name="DialValue" Angle="0"/>
 
 </TransformGroup>
 
 </ContentPresenter.RenderTransform>
 
 </ContentPresenter>
 
 </Grid>
 
 </ControlTemplate>
 
 </Setter.Value>
 
 </Setter>
 
 </Style>
 
 </ResourceDictionary>

 

Double click Dial.cs and add the following code in it.

 namespace Dial
 
 {
 
 public class Dial : Control
 
 {
 
 Grid _knob;
 
 RotateTransform _value;
 
 bool _hasCapture = false;
 
 
 
 public static readonly DependencyProperty ValueProperty =
 
 DependencyProperty.Register("Value", typeof(double),
 
 typeof(Dial), null);
 
 
 
 public static readonly DependencyProperty MinimumProperty =
 
 DependencyProperty.Register("Minimum", typeof(double),
 
 typeof(Dial), null);
 
 
 
 public static readonly DependencyProperty MaximumProperty =
 
 DependencyProperty.Register("Maximum", typeof(double),
 
 typeof(Dial), null);
 
 
 
 public static readonly DependencyProperty KnobProperty =
 
 DependencyProperty.Register("Knob", typeof(UIElement),
 
 typeof(Dial), null);
 
 
 
 public static readonly DependencyProperty FaceProperty =
 
 DependencyProperty.Register("Face", typeof(UIElement),
 
 typeof(Dial), null);
 
 
 
 public double Value
 
 {
 
 get { return (double)GetValue(ValueProperty); }
 
 set { SetValue(ValueProperty, value); }
 
 }
 
 
 
 public double Minimum
 
 {
 
 get { return (double)GetValue(MinimumProperty); }
 
 set { SetValue(MinimumProperty, value); }
 
 }
 
 
 
 public double Maximum
 
 {
 
 get { return (double)GetValue(MaximumProperty); }
 
 set { SetValue(MaximumProperty, value); }
 
 }
 
 
 
 public UIElement Knob
 
 {
 
 get { return (UIElement)GetValue(KnobProperty); }
 
 set { SetValue(KnobProperty, value); }
 
 }
 
 
 
 public UIElement Face
 
 {
 
 get { return (UIElement)GetValue(FaceProperty); }
 
 set { SetValue(FaceProperty, value); }
 
 }
 
 
 
 double AngleQuadrant(double width, double height, Point point)
 
 {
 
 double radius = width / 2;
 
 Point centre = new Point(radius, height / 2);
 
 Point start = new Point(0, height / 2);
 
 double triangleTop = Math.Sqrt(Math.Pow((point.X - centre.X), 2)
 
 + Math.Pow((centre.Y - point.Y), 2));
 
 double triangleHeight = (point.Y > centre.Y) ?
 
 point.Y - centre.Y : centre.Y - point.Y;
 
 return ((triangleHeight * Math.Sin(90)) / triangleTop) * 100;
 
 }
 
 
 
 double GetAngle(Point point)
 
 {
 
 double diameter = _knob.ActualWidth;
 
 double height = _knob.ActualHeight;
 
 double radius = diameter / 2;
 
 double rotation = AngleQuadrant(diameter, height, point);
 
 if ((point.X > radius) && (point.Y <= radius))
 
 {
 
 rotation = 90.0 + (90.0 - rotation);
 
 }
 
 else if ((point.X > radius) && (point.Y > radius))
 
 {
 
 rotation = 180.0 + rotation;
 
 }
 
 else if ((point.X < radius) && (point.Y > radius))
 
 {
 
 rotation = 270.0 + (90.0 - rotation);
 
 }
 
 return rotation;
 
 }
 
 
 
 private void SetPosition(double rotation)
 
 {
 
 if (Minimum > 0 && Maximum > 0 && Minimum < 360 && Maximum <= 360)
 
 {
 
 if (rotation < Minimum) { rotation = Minimum; }
 
 if (rotation > Maximum) { rotation = Maximum; }
 
 }
 
 _value.Angle = rotation;
 
 Value = rotation;
 
 }
 
 
 
 public Dial()
 
 {
 
 this.DefaultStyleKey = typeof(Dial);
 
 }
 
 
 
 public override void OnApplyTemplate()
 
 {
 
 base.OnApplyTemplate();
 
 _knob = ((Grid)GetTemplateChild("Knob"));
 
 _value = ((RotateTransform)GetTemplateChild("DialValue"));
 
 if (Minimum > 0 && Minimum < 360) { SetPosition(Minimum); }
 
 _knob.MouseLeftButtonUp += (object sender, MouseButtonEventArgs e) =>
 
 {
 
 _hasCapture = false;
 
 };
 
 _knob.MouseLeftButtonDown += (object sender, MouseButtonEventArgs e) =>
 
 {
 
 _hasCapture = true;
 
 SetPosition(GetAngle(e.GetPosition(_knob)));
 
 };
 
 _knob.MouseMove += (object sender, MouseEventArgs e) =>
 
 {
 
 if (_hasCapture)
 
 {
 
 SetPosition(GetAngle(e.GetPosition(_knob)));
 
 }
 
 };
 
 _knob.MouseLeave += (object sender, MouseEventArgs e) =>
 
 {
 
 _hasCapture = false;
 
 };
 
 }
 
 }
 
 
 
 }

 

Build the Solution by Pressing F6

When the Build is completed, return to the MainPage.XAML Designer by double clicking on it.

Add the Following line on the top of the XAML

 

 xmlns:my="clr-namespace:Dial;assembly=Dial"

From the Toolbar, Add Dial onto the Stage.

And change the XAML between the Content Grid to this,

 <my:Dial x:Name="Dial" Height="400" Width="400" Minimum="0.0" Maximum="360.0">
 
 <my:Dial.Knob>
 
 <Grid>
 
 <Ellipse Fill="{StaticResource PhoneAccentBrush}"/>
 
 <Rectangle Height="50" Width="50" Margin="0,0,350,0"
 
 RadiusX="25" RadiusY="25" Fill="{StaticResource PhoneForegroundBrush}"/>
 
 </Grid>
 
 </my:Dial.Knob>
 
 </my:Dial>

 

The page will look something like this,

 

While still in the XAML,

Add a textBlock, using the XAML below

 <TextBlock x:Name="myTextblock" Text="{Binding ElementName=Dial, Path=Value}" Margin="-3,-8,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>

 Save the Project and Run it, you now have a fully functional Dial that is bound to a Textblock.
you can also change the minimum and Maximum of the Dial from the XAML to your likings.

Happy Developing