While working gesture recognition through Kinect on a Windows computer, one of the most common problems faced is invoking a “click” similar to a tile selection on default Xbox dashboard.
There are many ways of doing this. One of the best ways out is doing this by “Timer” same as the XBox Kinect concept. All you have to do is move the cursor over the Button, hold on for a predefined time at the same place and the tile is clicked. This enables the user to fire the click event by pointing the hand icon on a tile for a predefined time duration (say 5 seconds).
This example includes selecting a tile out of 5 tiles, thereby navigating to the page linked to that tile and navigating back to the start dashboard by clicking on the back button.
To achieve this we need to create two controls the “HoverButton” and second a “HandCursor”.
Creating HoverButton:
1) Create a WPF user control library and add a user control HoverButton then add reference to Microft.Kinect and Coding4Fun.Kinect.Wpf.
2) Define the dependency property in HoverButton.xaml.cs for that button to customize it in application, like:-
public int HoverTime
{
set { hoverDuration = new Duration(new TimeSpan(0, 0, value)); }
}
public Brush BackgroundColor
{
get { return (Brush)this.GetValue(BackgroundColorProperty); }
set { this.SetValue(BackgroundColorProperty, value); }
}
public static readonly DependencyProperty BackgroundColorProperty = DependencyProperty.Register(
"BackgroundColor", typeof(Brush), typeof(HoverButton), new PropertyMetadata(Brushes.Red));
public Brush HoverColor
{
get { return (Brush)this.GetValue(HoverColorProperty); }
set { this.SetValue(HoverColorProperty, value); }
}
public static readonly DependencyProperty HoverColorProperty = DependencyProperty.Register(
"HoverColor", typeof(Brush), typeof(HoverButton), new PropertyMetadata(Brushes.White));
public string Image
{
get { return (string)this.GetValue(ImageProperty); }
set { this.SetValue(ImageProperty, value); }
}
public static readonly DependencyProperty ImageProperty = DependencyProperty.Register(
"Image", typeof(string), typeof(HoverButton), new PropertyMetadata(""));
You can define other properties as per need of application.
3) In HoverButton.xaml for button:
<Grid Background="{Binding BackgroundColor}">
<Image Source="{Binding Image}" Stretch="Fill"/>
<TextBlock Text="{Binding Text}" FontSize="{Binding TextSize}" Foreground="{Binding TextColor}" FontFamily="Segoe UI" HorizontalAlignment="Center" VerticalAlignment="Center">
</TextBlock>
<Rectangle Name="Mask" Fill="{Binding HoverColor}" Width="{Binding Width}" Opacity="0.3" RenderTransformOrigin="0,0" Height="0" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</Grid>
4) Now to data bind the button with properties add below in constructor:
this.DataContext = this;
5) Add below for hover and animation:
//animation related
private Duration hoverDuration = new Duration(new TimeSpan(0, 0, 2));
private Duration reverseDuration = new Duration(new TimeSpan(0, 0, 1));
private DoubleAnimation maskAnimation;
private bool isHovering = false;
6) To start and stop Hover add these two methods:
private void StartHovering()
{
double maxFillHeight = this.ActualHeight;
if (!isHovering)
{
isHovering = true;
maskAnimation = new DoubleAnimation(Mask.ActualHeight, maxFillHeight, hoverDuration);
maskAnimation.Completed += new EventHandler(maskAnimation_Completed);
Mask.BeginAnimation(Canvas.HeightProperty, maskAnimation);
}
}
private void StopHovering()
{
if (isHovering)
{
isHovering = false;
maskAnimation.Completed -= maskAnimation_Completed;
maskAnimation = new DoubleAnimation(Mask.ActualHeight, 0, reverseDuration);
Mask.BeginAnimation(Canvas.HeightProperty, maskAnimation);
}
}
7) Another important step is adding click event and its handler:
public delegate void ClickHandler(object sender, EventArgs e);
public event ClickHandler Click;
void maskAnimation_Completed(object sender, EventArgs e)
{
isHovering = false;
if (Click != null)
Click(this, e);
Mask.BeginAnimation(Canvas.HeightProperty, null);
}
Implementation:- Check if the cursor is over the button or not
public bool Check(FrameworkElement cursor)
{
if (IsCursorInButton(cursor))
{
this.StartHovering();
return true;
}
else
{
this.StopHovering();
return false;
}
}
private bool IsCursorInButton(FrameworkElement cursor)
{
try
{
//Cursor midpoint location
Point cursorTopLeft = cursor.PointToScreen(new Point());
double cursorCenterX = cursorTopLeft.X + (cursor.ActualWidth / 2);
double cursorCenterY = cursorTopLeft.Y + (cursor.ActualHeight / 2);
//Button location
Point buttonTopLeft = this.PointToScreen(new Point());
double buttonLeft = buttonTopLeft.X;
double buttonRight = buttonLeft + this.ActualWidth;
double buttonTop = buttonTopLeft.Y;
double buttonBottom = buttonTop + this.ActualHeight;
if (cursorCenterX < buttonLeft || cursorCenterX > buttonRight)
return false;
if (cursorCenterY < buttonTop || cursorCenterY > buttonBottom)
return false;
return true;
}
catch
{
return false;
}
}
Create HandCursor:
1) Add another user control for cursor
In xaml:
<Ellipse Height="40" Width="40" Name="hand">
<Ellipse.Fill>
<ImageBrush ImageSource="/HoverAppWPF;component/Images/Hand.png" />
</Ellipse.Fill>
</Ellipse>
In xaml.cs:
public void SetPosition(Joint joint)
{
Joint scaledJoint = joint.ScaleTo(1300, 600, 0.4f, 0.4f);
Canvas.SetLeft(this, scaledJoint.Position.X);
Canvas.SetTop(this, scaledJoint.Position.Y);
}
Use the Controls
1) Add the above user control library reference to your application:
Add below to xaml page
<Page x:Class="HoverAppWPF.Main"
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"
d:DesignHeight="600" d:DesignWidth="1000"
xmlns:ctrl="clr-namespace:KinectControls;assembly=KinectControls"
Title="Main" Loaded="Page_Loaded" Unloaded="Page_Unloaded">
<Canvas Background="#146c3a">
<ctrl:HoverButton x:Name="btnVideoChat" BackgroundColor="Blue" HoverColor="White" Height="100" Width="200" Click="btnVideoChat_Click" Canvas.Top="300" Canvas.Left="100" Image="Images/VideoChat-icon.jpg"/>
<ctrl:HoverButton x:Name="btnPicture" BackgroundColor="Blue" HoverColor="White" Height="100" Width="200" Click="btnPicture_Click" Canvas.Top="300" Canvas.Left="350" Image="Images/Pictures-icon.jpg"/>
<ctrl:HoverButton x:Name="btnMenu" BackgroundColor="Blue" HoverColor="White" Height="100" Width="200" Click="btnMenu_Click" Canvas.Top="300" Canvas.Left="600" Image="Images/Menu-icon.jpg" />
<ctrl:HoverButton x:Name="btnWeather" BackgroundColor="Blue" HoverColor="White" Height="100" Width="200" Click="btnWeather_Click" Canvas.Top="300" Canvas.Left="850" Image="Images/Weather-icon.jpg"/>
<ctrl:HoverButton x:Name="btnDoctor" BackgroundColor="Blue" HoverColor="White" Height="100" Width="200" Click="btnDoctor_Click" Canvas.Top="300" Canvas.Left="1100" Image="Images/Doctor-icon.jpg"/>
<ctrl:HandCursor x:Name="hand" Canvas.Top="0" Canvas.Left="0"/>
</Canvas>
2) Add below to xaml.cs to initialize the sensor and for skeleton tracking:
KinectSensor kinect = null;
bool _isInit;
private void Page_Loaded(object sender, RoutedEventArgs e)
{
SetupKinect();
this.Cursor = Cursors.None;
}
private void SetupKinect()
{
if (_isInit)
StopKinect();
if (KinectSensor.KinectSensors.Count > 0)
{
//pull the first Kinect
kinect = KinectSensor.KinectSensors[0];
}
if (kinect.Status != KinectStatus.Connected || KinectSensor.KinectSensors.Count == 0)
{
MessageBox.Show("No Kinect connected");
return;
}
kinect.SkeletonStream.Enable();
//to experiment, toggle TransformSmooth between true & false
// parameters used to smooth the skeleton data
TransformSmoothParameters parameters = new TransformSmoothParameters();
parameters.Smoothing = 0.1f;
parameters.Correction = 0.9f;
parameters.Prediction = 0.3f;
parameters.JitterRadius = 0.5f;
parameters.MaxDeviationRadius = 0.1f;
kinect.SkeletonStream.Enable(parameters);
kinect.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(kinect_SkeletonFrameReady);
kinect.Start();
_isInit = true;
}
public void StopKinect()
{
if (kinect != null)
{
kinect.Stop();
}
_isInit = false;
}
private void Page_Unloaded(object sender, RoutedEventArgs e)
{
kinect.SkeletonFrameReady -= new EventHandler<SkeletonFrameReadyEventArgs>(kinect_SkeletonFrameReady);
if (kinect != null)
kinect.Stop();
kinect = null;
}
//Here the event gets called:-
void kinect_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
{
SkeletonFrame frame = e.OpenSkeletonFrame();
if (frame != null)
{
Skeleton[] skeletons = new Skeleton[frame.SkeletonArrayLength];
frame.CopySkeletonDataTo(skeletons);
var skeleton = skeletons.Where(s => s.TrackingState == SkeletonTrackingState.Tracked).FirstOrDefault();
if (skeleton != null)
{
Joint handJoint = skeleton.Joints[JointType.HandRight];
//set cursor position
hand.SetPosition(handJoint);
//check if the cursor is over the button or not
btnMenu.Check(hand);
btnPicture.Check(hand);
btnVideoChat.Check(hand);
btnWeather.Check(hand);
btnDoctor.Check(hand);
}
}
}
3) For navigation to next page:
private void btnPicture_Click(object sender, EventArgs e)
{
((Window)(this.Parent)).Content = new Picture();
}
4) Remember to remove the event handler on page unload otherwise when you navigate to next page it will also call the handler of old page with new one that will result in unexpected behavior of cursor.
5) The above code was for dashboard, add same logic to each individual page to reach the dashboard again.
This is all about how to call Click event through Kinect windows.
So let the gestures speak, till then keep learning and keep exploring.