Stick Control
Learn creating a Directional Stick using Windows App SDK with this Tutorial
Stick Control shows how to create a Directional Stick that can be used for selecting an Angle and Ratio using Windows App SDK.
Step 1
Follow Setup and Start on how to get Setup and Install what you need for Visual Studio 2022 and Windows App SDK.
Step 2
Then in Visual Studio within Solution Explorer for the Solution, right click on the Project shown below the Solution and then select Add then New Item…
Step 3
Then in Add New Item from the C# Items list, select Code and then select Code File from the list next to this, then type in the name of Stick.cs and then Click on Add.
Step 4
Step 5
You will now be in the View for the Code of Stick.cs, within this type the following Code:
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Shapes;
using System;
namespace StickControl;
public class Stick : Grid
{
// Members & Event
// Dependency Properties
// Properties
// ToRadians, ToDegrees, SetMiddle, & GetCircle Methods
// Move Method
// Layout & Load Methods and Constructor
}
There are using statements for the User Control, a namespace for StickControl
along with a class of Stick that will represent the User Control for the Directional Stick.
Step 6
Then in the namespace of StickControl in the class of Stick after the Comment
of // Members & Event type the following Members and Event:
private bool _capture;
private Ellipse _knob;
private Ellipse _face;
private double x = 0;
private double y = 0;
private double _m = 0;
private double _res = 0;
private double _width = 0;
private double _height = 0;
private double _alpha = 0;
private double _alphaM = 0;
private double _centreX = 0;
private double _centreY = 0;
private double _distance = 0;
private double _oldAlphaM = -999.0;
private double _oldDistance = -999.0;
public delegate void ValueChangedEventHandler(
object sender, double angle, double ratio);
public event ValueChangedEventHandler ValueChanged;
Members include Ellipses needed to represent the different directional parts of the Directional Stick and there is also a
delegate along with an event for when the Directional Stick is interacted with.
Step 7
While still in the namespace of StickControl in the class of Stick after the Comment
of // Dependency Properties type the following Dependency Properties:
public static readonly DependencyProperty RadiusProperty =
DependencyProperty.Register(nameof(Radius), typeof(int),
typeof(Stick), new PropertyMetadata(100));
public static readonly DependencyProperty KnobProperty =
DependencyProperty.Register(nameof(Knob), typeof(Brush),
typeof(Stick), new PropertyMetadata(new SolidColorBrush(Colors.Red)));
public static readonly DependencyProperty FaceProperty =
DependencyProperty.Register(nameof(Face), typeof(Brush),
typeof(Stick), new PropertyMetadata(new SolidColorBrush(Colors.Black)));
public static readonly DependencyProperty AngleProperty =
DependencyProperty.Register(nameof(Angle), typeof(double),
typeof(Stick), null);
public static readonly DependencyProperty RatioProperty =
DependencyProperty.Register(nameof(Ratio), typeof(double),
typeof(Stick), null);
public static readonly DependencyProperty SensitivityProperty =
DependencyProperty.Register(nameof(Sensitivity), typeof(double),
typeof(Stick), null);
There will also be some Errors as these refer to Properties that will be added in the next step.
These Dependency Properties refer to various Properties of the Directional Stick that can be customised for the User Control.
Step 8
While still in the namespace of StickControl in the class of Stick after the Comment
of // Properties type the following Properties:
public int Radius
{
get { return (int)GetValue(RadiusProperty); }
set { SetValue(RadiusProperty, value); Load(); }
}
public Brush Knob
{
get { return (Brush)GetValue(KnobProperty); }
set { SetValue(KnobProperty, value); }
}
public Brush Face
{
get { return (Brush)GetValue(FaceProperty); }
set { SetValue(FaceProperty, value); }
}
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
public double Ratio
{
get { return (double)GetValue(RatioProperty); }
set { SetValue(RatioProperty, value); }
}
public double Sensitivity
{
get { return (double)GetValue(SensitivityProperty); }
set { SetValue(SensitivityProperty, value); }
}
All previous Errors should now be resolved, however there will be just one for a Method of Load which will be resolved in a future
step but if you are getting any others check any previous steps to see if you have missed anything.
These Properties are for values for the User Control such as the Angle or Ratio values for the Directional Stick.
Step 9
While still in the namespace of StickControl in the class of Stick after the Comment
of // ToRadians, ToDegrees, SetMiddle, & GetCircle Methods type the following Methods:
private static double ToRadians(double angle) =>
Math.PI * angle / 180.0;
private static double ToDegrees(double angle) =>
angle * (180.0 / Math.PI);
private void SetMiddle()
{
_capture = false;
Canvas.SetLeft(_knob, (Width - _width) / 2);
Canvas.SetTop(_knob, (Height - _height) / 2);
_centreX = Width / 2;
_centreY = Height / 2;
}
private Ellipse GetCircle(double dimension, string path)
{
var circle = new Ellipse()
{
Height = dimension,
Width = dimension
};
circle.SetBinding(Shape.FillProperty, new Binding()
{
Source = this,
Path = new PropertyPath(path),
Mode = BindingMode.TwoWay
});
return circle;
}
The Methods of ToRadians and ToDegrees will perform the relevant conversions, SetMiddle will determine the centre point of
the Directional Stick and GetCircle will create an Ellipse with a Binding for the Fill.
Step 10
While still in the namespace of StickControl in the class of Stick after the Comment
of // Move Method type the following Method:
private void Move(PointerRoutedEventArgs e)
{
x = e.GetCurrentPoint(this).Position.X;
y = e.GetCurrentPoint(this).Position.Y;
_res = Math.Sqrt((x - _centreX) *
(x - _centreX) + (y - _centreY) * (y - _centreY));
_m = (y - _centreY) / (x - _centreX);
_alpha = ToDegrees(Math.Atan(_m) + Math.PI / 2);
if (x < _centreX)
_alpha += 180.0;
else if (x == _centreX && y <= _centreY)
_alpha = 0.0;
else if (x == _centreX)
_alpha = 180.0;
if (_res > Radius)
{
x = _centreX + Math.Cos(ToRadians(_alpha) - Math.PI / 2) * Radius;
y = _centreY + Math.Sin(ToRadians(_alpha) - Math.PI / 2) * Radius
* ((_alpha % 180.0 == 0.0) ? -1.0 : 1.0);
_res = Radius;
}
if (Math.Abs(_alpha - _alphaM) >= Sensitivity ||
Math.Abs(_distance * Radius - _res) >= Sensitivity)
{
_alphaM = _alpha;
_distance = _res / Radius;
}
if (_oldAlphaM != _alphaM ||
_oldDistance != _distance)
{
Angle = _alphaM;
Ratio = _distance;
_oldAlphaM = _alphaM;
_oldDistance = _distance;
ValueChanged?.Invoke(this, Angle, Ratio);
}
Canvas.SetLeft(_knob, x - _width / 2);
Canvas.SetTop(_knob, y - _height / 2);
}
The Method of Move will be used by the Directional Stick to determine the current Angle and Ratio along with raising the Event of ValueChanged with those values.
Step 11
While still in the namespace of StickControl in the class of Stick after the Comment
of // Layout & Load Methods and Constructor type the following Methods and Constructor:
private void Layout()
{
_knob = GetCircle(Radius, "Knob");
_face = GetCircle(Radius * 2, "Face");
_height = _knob.ActualHeight;
_width = _knob.ActualWidth;
Width = _width + Radius * 2;
Height = _height + Radius * 2;
SetMiddle();
PointerExited -= null;
PointerExited += (object sender, PointerRoutedEventArgs e) =>
SetMiddle();
_knob.PointerReleased += (object sender, PointerRoutedEventArgs e) =>
SetMiddle();
_knob.PointerPressed += (object sender, PointerRoutedEventArgs e) =>
_capture = true;
_knob.PointerMoved += (object sender, PointerRoutedEventArgs e) =>
{
if (_capture)
Move(e);
};
_knob.PointerExited += (object sender, PointerRoutedEventArgs e) =>
SetMiddle();
}
private void Load()
{
Layout();
Children.Clear();
Children.Add(_face);
var canvas = new Canvas()
{
Width = Width,
Height = Height
};
canvas.Children.Add(_knob);
Children.Add(canvas);
}
public Stick() => Load();
All Errors should now be resolved, if you continue to get them check any previous steps to see if you have missed anything.
The Constructor will be used to create the look-and-feel of the User Control and will use the Method of
Load which will use the Method of Layout which will capture if the User Control is being interacted with.
Step 12
Step 13
In the XAML for MainWindow.xaml there will be some XAML for a StackPanel, this should be Removed:
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Button x:Name="myButton" Click="myButton_Click">Click Me</Button>
</StackPanel>
Step 14
While still in the XAML for MainWindow.xaml above </Window>, type in the following XAML:
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Name="Label" HorizontalAlignment="Center"
Style="{StaticResource SubtitleTextBlockStyle}"/>
<local:Stick Radius="200" Knob="{ThemeResource AccentButtonBackground}"
Face="WhiteSmoke" ValueChanged="ValueChanged"/>
</StackPanel>
This XAML contains a StackPanel including a TextBlock and the User Control of Stick with the Event of ValueChanged.
Step 15
Step 16
In the Code for MainWindow.xaml.cs there be a Method of myButton_Click(...) this should be Removed by removing the following:
private void myButton_Click(object sender, RoutedEventArgs e)
{
myButton.Content = "Clicked";
}
Step 17
Once myButton_Click(...) has been removed, within the Constructor of public MainWindow() { ... } and below the line of this.InitializeComponent(); type in the following Code:
private void ValueChanged(object sender, double angle, double ratio) =>
Label.Text = $"Angle {angle}, Ratio {ratio}";
The Method of ValueChanged will be used with Event Handler from the XAML to display the selected Angle and Ratio,
this Method uses Arrow Syntax with the => for an expression body which is useful when a Method only has one line.
Step 18
Step 19
Once running you will see the Stick Control displayed, then you can select and move the centre portion of the Directional Stick and can see the Angle around the centre, or the Ratio of the distance between the centre and the outside displayed.
Step 20