手势识别(1):基础理论
像点击(clicks)是GUI平台的核心,轻点(taps)是触摸平台的核心那样,手势(gestures)是Kinect应用程序的核心。和图形用户界面中的数字交互不同,手势是现实生活中存在的动作。如果没有电脑我们就不需要鼠标,但是没了Kinect,手势依然存在。从另一方面讲,手势是日常生活中人与人之间相互交流的一部分。手势能够增强演讲的说服力,能够用来强调和传递情感。像挥手(waving)或者指向(pointing)这些手势都是某种无声的演讲。
Kinect应用程序的设计和开发者的任务就是将这些现实生活中存在的手势映射到计算机交互中去以传达人的想法。尝试从鼠标或触摸式的GUI设计移植基于手势的自然交互界面要做很多工作。借鉴过去30多年来对于这一概念的研究,以及从一些Kinect for Xbox的体感游戏中获取一些设计理念,计算机工程师和交互设计师一起为Kinect创建了一系列新的手势库。
本文将会介绍用户体验的一些知识,并讨论如何将手势应用到Kinect应用程序中。我们将展示Kinect如何作为自然交互界面(Natural User Interface)的人机交互模型的一部分。我们将讨论一些具体的使用Kinect来进行手势识别及交互的例子。更重要的是,将会展示一些已经作为Kinect手势识别库中的手势
1. 什么是手势
Eric Hulteen 和Gord Kurtenbach关于手势的定义的中心在于手势能够用来交流,手势的意义在于讲述而不是执行。
2. 自然交互界面(NUI)
自然用户界面设计师所追求的是的是自然,按钮恰好提供了这一点。但是按钮在每一种用户界面中的转换都面临着一些挑战。
3. 手势从哪里来
本文接下来从理论到实现,讨论如何实现手势识别,并展示了Kinect中八中基本手势中的挥手(wave)手势的识别。
4. 实现手势识别
4.1 基于算法的手势识别
4.2 基于神经网络的手势识别
当用户在做手势时,手势的形式并不总是足够清晰到能够判断用户的意图。例如跳跃手势,跳跃手势就是用户短暂的跳起来,脚离开地面。这个定义不能够提供足够的信息来识别这一动作。
神经网络的组织和判断是基于统计和概率的,因此使得像识别手势这些过程变得容易控制。基于什么网络的手势识别引擎对于下蹲然后跳跃动作,80%的概率判断为跳跃,10%会判定为下蹲。
4.3 基于样本的识别
5.识别常见的手势
5.1 挥动(wave)手势
XBOX中的挥手动作定义为:从胳膊开始到肘部弯曲。用户以胳膊肘为焦点来回移动前臂,移动平面和肩部在一个平面上,并且胳膊和地面保持平行,在手势的中部(下图1),前臂垂直于后臂和地面。下图展示了这一姿势。
从图中可以观察得出一些规律,第一个规律就是,手和手腕都是在肘部和肩部之上的,这也是大多是挥手动作的特征。这也是我们识别挥手这一手势的第一个标准。
<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private enum </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">WavePosition </span></span>{None = 0,Left = 1,Right = 2,Neutral = 3 }<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private enum </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">WaveGestureState </span></span>{None = 0,Success = 1,Failure = 2,InProgress = 3 }<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private struct </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">WaveGestureTracker </span></span>{<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public int </span></span>IterationCount;<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">WaveGestureState </span></span>State;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public long </span></span>Timestamp;<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">WavePosition </span></span>StartPosition;<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">WavePosition </span></span>CurrentPosition;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public void </span></span>Reset(){IterationCount = 0;State = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">WaveGestureState</span></span>.None;Timestamp = 0;StartPosition = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">WavePosition</span></span>.None;CurrentPosition = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">WavePosition</span></span>.None;} }
<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public class </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">WaveGesture </span></span>{<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private const float </span></span>WAVE_THRESHOLD = 0.1f;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private const int </span></span>WAVE_MOVEMENT_TIMEOUT = 5000;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private const int </span></span>LEFT_HAND = 0;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private const int </span></span>RIGHT_HAND = 1;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private const int </span></span>REQUIRED_ITERATIONS = 4;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private </span></span>WaveGestureTracker[,] _PlayerWaveTracker = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">new </span></span>WaveGestureTracker[6, 2];<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public event </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">EventHandler </span></span>GestureDetected;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public void </span></span>Update(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Skeleton</span></span>[] skeletons, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">long </span></span>frameTimestamp){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(skeletons != <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>){<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Skeleton </span></span>skeleton;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">for </span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">int </span></span>i = 0; i < skeletons.Length; i++){skeleton = skeletons[i];<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(skeleton.TrackingState != <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">SkeletonTrackingState</span></span>.NotTracked){TrackWave(skeleton, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">true</span></span>, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">ref this</span></span>._PlayerWaveTracker[i, LEFT_HAND], frameTimestamp);TrackWave(skeleton, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">false</span></span>, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">ref this</span></span>._PlayerWaveTracker[i, RIGHT_HAND], frameTimestamp);}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">else</span></span>{<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>._PlayerWaveTracker[i, LEFT_HAND].Reset();<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>._PlayerWaveTracker[i, RIGHT_HAND].Reset();}}}} }
<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private void </span></span>TrackWave(Skeleton skeleton, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">bool </span></span>isLeft, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">ref </span></span>WaveGestureTracker tracker, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">long </span></span>timestamp) {JointType handJointId = (isLeft) ? JointType.HandLeft : JointType.HandRight;JointType elbowJointId = (isLeft) ? JointType.ElbowLeft : JointType.ElbowRight;Joint hand = skeleton.Joints[handJointId];Joint elbow = skeleton.Joints[elbowJointId];<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(hand.TrackingState != JointTrackingState.NotTracked && elbow.TrackingState != JointTrackingState.NotTracked){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(tracker.State == WaveGestureState.InProgress && tracker.Timestamp + WAVE_MOVEMENT_TIMEOUT < timestamp){tracker.UpdateState(WaveGestureState.Failure, timestamp);System.Diagnostics.Debug.WriteLine(<span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"Fail!"</span></span>);}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">else if </span></span>(hand.Position.Y > elbow.Position.Y){<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: green;">//使用 (0, 0) 作为屏幕的中心. 从用户的视角看, X轴左负右正.</span><span style="margin: 0px; padding: 0px; color: blue;">if </span></span>(hand.Position.X <= elbow.Position.X - WAVE_THRESHOLD){tracker.UpdatePosition(WavePosition.Left, timestamp);}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">else if </span></span>(hand.Position.X >= elbow.Position.X + WAVE_THRESHOLD){tracker.UpdatePosition(WavePosition.Right, timestamp);}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">else</span></span>{tracker.UpdatePosition(WavePosition.Neutral, timestamp);}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(tracker.State != WaveGestureState.Success && tracker.IterationCount == REQUIRED_ITERATIONS){tracker.UpdateState(WaveGestureState.Success, timestamp);System.Diagnostics.Debug.WriteLine(<span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"Success!"</span></span>);<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(GestureDetected != <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>){GestureDetected(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">new </span></span>EventArgs());}}}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">else</span></span>{<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(tracker.State == WaveGestureState.InProgress){tracker.UpdateState(WaveGestureState.Failure, timestamp);System.Diagnostics.Debug.WriteLine(<span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"Fail!"</span></span>);}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">else</span></span>{tracker.Reset();}}}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">else</span></span>{tracker.Reset();} }
<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public void </span></span>UpdateState(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">WaveGestureState </span></span>state, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">long </span></span>timestamp) {State = state;Timestamp = timestamp; }<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public void </span></span>Reset() {IterationCount = 0;State = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">WaveGestureState</span></span>.None;Timestamp = 0;StartPosition = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">WavePosition</span></span>.None;CurrentPosition = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">WavePosition</span></span>.None; }<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public void </span></span>UpdatePosition(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">WavePosition </span></span>position, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">long </span></span>timestamp) {<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(CurrentPosition != position){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(position == <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">WavePosition</span></span>.Left || position == <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">WavePosition</span></span>.Right){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(State != <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">WaveGestureState</span></span>.InProgress){State = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">WaveGestureState</span></span>.InProgress;IterationCount = 0;StartPosition = position;}IterationCount++;}CurrentPosition = position;Timestamp = timestamp;} }
6. 基本的手势追踪
<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public class </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventArgs</span></span>:<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">RoutedEventArgs </span></span>{<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public double </span></span>X { <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get</span></span>; <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">set</span></span>; }<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public double </span></span>Y { <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get</span></span>; <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">set</span></span>; }<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public double </span></span>Z { <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get</span></span>; <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">set</span></span>; }<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">CursorAdorner </span></span>Cursor { <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get</span></span>; <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">set</span></span>; }<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public </span></span>KinectCursorEventArgs(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double </span></span>x, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double </span></span>y){X = x;Y = y;}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public </span></span>KinectCursorEventArgs(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Point </span></span>point){X = point.X;Y = point.Y;} }
<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public </span></span>KinectCursorEventArgs(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">RoutedEvent</span></span>routedEvent) : <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>(routedEvent) { } <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public</span></span>KinectCursorEventArgs(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">RoutedEvent</span></span>routedEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>x, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>y, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>z) <br style="margin: 0px; padding: 0px;" /> : <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>(routedEvent) { X = x; Y = y; Z = z; } <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public</span></span>KinectCursorEventArgs(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">RoutedEvent</span></span>routedEvent, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Point</span></span>point) <br style="margin: 0px; padding: 0px;" /> : <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>(routedEvent) { X = point.X; Y = point.Y; } <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public</span></span>KinectCursorEventArgs(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">RoutedEvent</span></span>routedEvent, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Point</span></span>point,<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>z) <br style="margin: 0px; padding: 0px;" /> : <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>(routedEvent) { X = point.X; Y = point.Y; Z = z; } <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public</span></span>KinectCursorEventArgs(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">RoutedEvent</span></span>routedEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">object</span></span>source) <br style="margin: 0px; padding: 0px;" /> : <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>(routedEvent, source) {} <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public</span></span>KinectCursorEventArgs(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">RoutedEvent</span></span>routedEvent,<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">object</span></span>source,<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>x,<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>y,<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>z) <br style="margin: 0px; padding: 0px;" /> : <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>(routedEvent, source) { X = x; Y = y; Z = z; } <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public</span></span>KinectCursorEventArgs(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">RoutedEvent</span></span>routedEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">object</span></span>source, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Point</span></span>point) <br style="margin: 0px; padding: 0px;" /> : <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>(routedEvent, source) { X = point.X; Y = point.Y; } <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public</span></span>KinectCursorEventArgs(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">RoutedEvent</span></span>routedEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">object</span></span>source, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Point</span></span>point,<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>z) <br style="margin: 0px; padding: 0px;" /> : <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>(routedEvent, source) { X = point.X; Y = point.Y; Z = z; }
接下来,要在KinectInput类中创建事件来将消息从KinectCursorManager中传递到可视化控件中去。这些事件传递的数据类型为KinectCursorEventArgs类型。
<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public delegate void </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventHandler</span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">object </span></span>sender,<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventArgs </span></span>e);<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static class </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectInput </span></span>{<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEvent </span></span>KinectCursorEnterEvent=<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">EventManager</span></span>.RegisterRoutedEvent(<span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"KinectCursorEnter"</span></span>,<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">RoutingStrategy</span></span>.Bubble,<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventHandler</span></span>),<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectInput</span></span>));<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public static void </span></span>AddKinectCursorEnterHandler(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DependencyObject </span></span>o, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventHandler </span></span>handler){((<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">UIElement</span></span>)o).AddHandler(KinectCursorEnterEvent, handler);}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public static void </span></span>RemoveKinectCursorEnterHandler(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DependencyObject </span></span>o, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventHandler </span></span>handler){((<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">UIElement</span></span>)o).RemoveHandler(KinectCursorEnterEvent, handler);}<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEvent </span></span>KinectCursorLeaveEvent=<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">EventManager</span></span>.RegisterRoutedEvent(<span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"KinectCursorLeave"</span></span>,<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">RoutingStrategy</span></span>.Bubble,<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventHandler</span></span>),<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectInput</span></span>));<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public static void </span></span>AddKinectCursorLeaveHandler(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DependencyObject </span></span>o, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventHandler </span></span>handler){((<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">UIElement</span></span>)o).AddHandler(KinectCursorEnterEvent,handler);}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public static void </span></span>RemoveKinectCursorLeaveHandler(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DependencyObject </span></span>o, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventHandler </span></span>handler){((<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">UIElement</span></span>)o).RemoveHandler(KinectCursorEnterEvent, handler);} }
<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public class </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">CursorAdorner</span></span>:<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Adorner </span></span>{<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private readonly </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">UIElement </span></span>_adorningElement;<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">VisualCollection </span></span>_visualChildren;<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Canvas </span></span>_cursorCanvas;<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">protected </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">FrameworkElement </span></span>_cursor;<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">StroyBoard </span></span>_gradientStopAnimationStoryboard;<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">readonly static </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Color </span></span>_backColor = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Colors</span></span>.White;<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">readonly static </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Color </span></span>_foreColor = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Colors</span></span>.Gray;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public </span></span>CursorAdorner(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">FrameworkElement </span></span>adorningElement): <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>(adorningElement){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>._adorningElement = adorningElement;CreateCursorAdorner();<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.IsHitTestVisible = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">false</span></span>;}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public </span></span>CursorAdorner(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">FrameworkElement </span></span>adorningElement, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">FrameworkElement </span></span>innerCursor): <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>(adorningElement){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>._adorningElement = adorningElement;CreateCursorAdorner(innerCursor);<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.IsHitTestVisible = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">false</span></span>;}<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">FrameworkElement </span></span>CursorVisual{<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get </span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>_cursor; }}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public void </span></span>CreateCursorAdorner(){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var </span></span>innerCursor = CreateCursor();CreateCursorAdorner(innerCursor);}<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">protected </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">FrameworkElement </span></span>CreateCursor(){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var </span></span>brush = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">LinearGradientBrush</span></span>();brush.EndPoint = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>(0, 1);brush.StartPoint = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>(0, 0);brush.GradientStops.Add(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">GradientStop</span></span>(_backColor, 1));brush.GradientStops.Add(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">GradientStop</span></span>(_foreColor, 1));<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var </span></span>cursor = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Ellipse</span></span>(){Width=50,Height=50,Fill=brush};<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>cursor;}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public void </span></span>CreateCursorAdorner(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">FrameworkElement </span></span>innerCursor){_visualChildren = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">VisualCollection</span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>);_cursorCanvas = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Canvas</span></span>();_cursor = innerCursor;_cursorCanvas.Children.Add(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>._cursorCanvas);_visualChildren.Add(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>._cursorCanvas);<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">AdornerLayer </span></span>layer = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">AdornerLayer</span></span>.GetAdornerLayer(_adorningElement);layer.Add(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>);} }
<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">protected override int </span></span>VisualChildrenCount {<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get</span></span>{<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>_visualChildren.Count;} }<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">protected override </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Visual </span></span>GetVisualChild(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">int </span></span>index) {<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>_visualChildren[index]; }<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">protected override </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Size </span></span>MeasureOverride(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Size </span></span>constraint) {<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>._cursorCanvas.Measure(constraint);<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return this</span></span>._cursorCanvas.DesiredSize; }<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">protected override </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Size </span></span>ArrangeOverride(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Size </span></span>finalSize) {<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>._cursorCanvas.Arrange(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Rect</span></span>(finalSize));<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>finalSize; }
<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public void </span></span>UpdateCursor(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Point</span></span>position, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">bool</span></span>isOverride) <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> _isOverriden = isOverride; <br style="margin: 0px; padding: 0px;" /> _cursor.SetValue(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Canvas</span></span>.LeftProperty,position.X-(_cursor.ActualWidth/2)); <br style="margin: 0px; padding: 0px;" /> _cursor.SetValue(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Canvas</span></span>.LeftProperty, position.Y - (_cursor.ActualHeight / 2)); <br style="margin: 0px; padding: 0px;" />} <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public void </span></span>UpdateCursor(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Point</span></span>position) <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if</span></span>(_isOverriden) <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return</span></span>; <br style="margin: 0px; padding: 0px;" /> _cursor.SetValue(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Canvas</span></span>.LeftProperty, position.X - (_cursor.ActualWidth / 2)); <br style="margin: 0px; padding: 0px;" /> _cursor.SetValue(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Canvas</span></span>.LeftProperty, position.Y - (_cursor.ActualHeight / 2)); <br style="margin: 0px; padding: 0px;" />}
最后,添加光标对象动画效果。当Kinect控件需要悬浮于一个元素之上,在用户等待的时候,给用户反馈一些信息告知正在发生的事情,这一点很有好处。下面了的代码展示了如何使用代码实现动画效果:
<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public virtual void </span></span>AnimateCursor(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>milliSeconds) { <br style="margin: 0px; padding: 0px;" /> CreateGradientStopAnimation(milliSeconds); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if</span></span>(_gradientStopAnimationStoryboard != <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>) <br style="margin: 0px; padding: 0px;" /> _gradientStopAnimationStoryboard.Begin(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">true</span></span>); <br style="margin: 0px; padding: 0px;" />} <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public virtual void </span></span>StopCursorAnimation(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>milliSeconds) <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if</span></span>(_gradientStopAnimationStoryboard != <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>) <br style="margin: 0px; padding: 0px;" /> _gradientStopAnimationStoryboard.Stop(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>); <br style="margin: 0px; padding: 0px;" />} <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public virtual void </span></span>CreateGradientStopAnimation(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>milliSeconds) { <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">NameScope</span></span>.SetNameScope(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>, <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">NameScope</span></span>()); <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var</span></span>cursor = _cursor <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">as</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Shape</span></span>; <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if</span></span>(cursor == <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>) <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return</span></span>; <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var</span></span>brush = cursor.Fill <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">as</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">LinearGradientBrush</span></span>; <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var</span></span>stop1 = brush.GradientStops[0]; <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var</span></span>stop2 = brush.GradientStops[1]; <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.RegisterName(<span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"GradientStop1"</span></span>, stop1); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.RegisterName(<span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"GradientStop2"</span></span>, stop2); <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DoubleAnimation</span></span>offsetAnimation = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">DoubleAnimation</span></span>(); <br style="margin: 0px; padding: 0px;" /> offsetAnimation.From = 1.0; <br style="margin: 0px; padding: 0px;" /> offsetAnimation.To = 0.0; <br style="margin: 0px; padding: 0px;" /> offsetAnimation.Duration = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">TimeSpan</span></span>.FromMilliseconds(milliSeconds); <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Storyboard</span></span>.SetTargetName(offsetAnimation, <span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"GradientStop1"</span></span>); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Storyboard</span></span>.SetTargetProperty(offsetAnimation, <br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">PropertyPath</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">GradientStop</span></span>.OffsetProperty)); <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DoubleAnimation</span></span>offsetAnimation2 = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">DoubleAnimation</span></span>(); <br style="margin: 0px; padding: 0px;" /> offsetAnimation2.From = 1.0; <br style="margin: 0px; padding: 0px;" /> offsetAnimation2.To = 0.0; <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> offsetAnimation2.Duration = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">TimeSpan</span></span>.FromMilliseconds(milliSeconds); <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Storyboard</span></span>.SetTargetName(offsetAnimation2, <span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"GradientStop2"</span></span>); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Storyboard</span></span>.SetTargetProperty(offsetAnimation2, <br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">PropertyPath</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">GradientStop</span></span>.OffsetProperty)); <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> _gradientStopAnimationStoryboard = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Storyboard</span></span>(); <br style="margin: 0px; padding: 0px;" /> _gradientStopAnimationStoryboard.Children.Add(offsetAnimation); <br style="margin: 0px; padding: 0px;" /> _gradientStopAnimationStoryboard.Children.Add(offsetAnimation2); <br style="margin: 0px; padding: 0px;" /> _gradientStopAnimationStoryboard.Completed += <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">delegate</span></span>{ _gradientStopAnimationStoryboard.Stop(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>); }; <br style="margin: 0px; padding: 0px;" />}
<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private static </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">UIElement </span></span>GetElementAtScreenPoint(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Point </span></span>point, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Window </span></span>window)
{<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(!window.IsVisible)<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return null</span></span>;<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Point </span></span>windowPoint = window.PointFromScreen(point);<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">IInputElement </span></span>element = window.InputHitTest(windowPoint);<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(element <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">is </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">UIElement</span></span>)<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">UIElement</span></span>)element;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">elsereturn null</span></span>;
}<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private static </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Skeleton </span></span>GetPrimarySkeleton(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">IEnumerable</span></span><<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Skeleton</span></span>> skeletons)
{<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Skeleton </span></span>primarySkeleton = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">foreach </span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Skeleton </span></span>skeleton <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">in </span></span>skeletons){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(skeleton.TrackingState != <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">SkeletonTrackingState</span></span>.Tracked){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">continue</span></span>;}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(primarySkeleton == <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>)primarySkeleton = skeleton;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">else if </span></span>(primarySkeleton.Position.Z > skeleton.Position.Z)primarySkeleton = skeleton;}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>primarySkeleton;
}<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private static </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Joint</span></span>? GetPrimaryHand(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Skeleton </span></span>skeleton)
{ <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Joint </span></span>leftHand=skeleton.Joints[<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">JointType</span></span>.HandLeft];<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Joint </span></span>rightHand=skeleton.Joints[<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">JointType</span></span>.HandRight];<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(rightHand.TrackingState == <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">JointTrackingState</span></span>.Tracked){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(leftHand.TrackingState != <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">JointTrackingState</span></span>.Tracked)<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>rightHand;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">else if </span></span>(leftHand.Position.Z > rightHand.Position.Z)<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>rightHand;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">elsereturn </span></span>leftHand;}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(leftHand.TrackingState == <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">JointTrackingState</span></span>.Tracked){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>leftHand;}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">elsereturn null</span></span>;
}
<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private static </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">UIElement </span></span>GetElementAtScreenPoint(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Point </span></span>point, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Window </span></span>window) { <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(!window.IsVisible) <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return null</span></span>; <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Point </span></span>windowPoint = window.PointFromScreen(point); <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">IInputElement </span></span>element = window.InputHitTest(windowPoint); <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(element <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">is </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">UIElement</span></span>) <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">UIElement</span></span>)element; <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">else return null</span></span>;}<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private static </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Skeleton </span></span>GetPrimarySkeleton(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">IEnumerable</span></span><<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Skeleton</span></span>> skeletons){ <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Skeleton </span></span>primarySkeleton = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>; <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">foreach </span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Skeleton </span></span>skeleton <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">in </span></span>skeletons) { <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(skeleton.TrackingState != <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">SkeletonTrackingState</span></span>.Tracked) { <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">continue</span></span>; } <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(primarySkeleton == <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>) primarySkeleton = skeleton; <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">else if </span></span>(primarySkeleton.Position.Z > skeleton.Position.Z) primarySkeleton = skeleton; } <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>primarySkeleton;}<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private static </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Joint</span></span>? GetPrimaryHand(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Skeleton </span></span>skeleton){ <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Joint </span></span>leftHand=skeleton.Joints[<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">JointType</span></span>.HandLeft]; <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Joint </span></span>rightHand=skeleton.Joints[<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">JointType</span></span>.HandRight]; <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(rightHand.TrackingState == <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">JointTrackingState</span></span>.Tracked) { <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(leftHand.TrackingState != <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">JointTrackingState</span></span>.Tracked) <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>rightHand; <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">else if </span></span>(leftHand.Position.Z > rightHand.Position.Z) <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>rightHand; <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">else return </span></span>leftHand; } <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(leftHand.TrackingState == <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">JointTrackingState</span></span>.Tracked) { <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>leftHand; } <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">else return null</span></span>;}
KinectCursorManager应该是一个单例类。这样设计是能够使得代码实例化起来简单。任何和KinectCursorManager工作的控件在KinectCursorManager没有实例化的情况下可以独立的进行KinectCursorManager的实例化。这意味着任何开发者使用这些控件不需要了解KinectCursorManager对象本身。相反,开发者能够简单的将控件拖动到应用程序中,控件负责实例化KinectCursorManager对象。为了使得这种自服务功能能和KinectCursorMange类一起使用,我们需要创建一个重载的Create方法来将应用程序的主窗体类传进来。下面的代码展示了重载的构造函数以及特殊的单例模式的实现方法。
<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public class </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorManager </span></span>{<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectSensor </span></span>kinectSensor;<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">CursorAdorner </span></span>cursorAdorner;<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private readonly </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Window </span></span>window;<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">UIElement </span></span>lastElementOver;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private bool </span></span>isSkeletonTrackingActivated;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private static bool </span></span>isInitialized;<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private static </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorManager </span></span>instance;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public static void </span></span>Create(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Window </span></span>window){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(!isInitialized){instance = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorManager</span></span>(window);isInitialized = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">true</span></span>;}}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public static void </span></span>Create(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Window </span></span>window,<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">FrameworkElement </span></span>cursor){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(!isInitialized){instance = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorManager</span></span>(window,cursor);isInitialized = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">true</span></span>;}}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public static void </span></span>Create(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Window </span></span>window, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectSensor </span></span>sensor) {<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(!isInitialized){instance = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorManager</span></span>(window, sensor);isInitialized = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">true</span></span>;}}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public static void </span></span>Create(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Window </span></span>window, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectSensor </span></span>sensor, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">FrameworkElement </span></span>cursor){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(!isInitialized){instance = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorManager</span></span>(window, sensor, cursor);isInitialized = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">true</span></span>;}}<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorManager </span></span>Instance{<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get </span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>instance; }}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private </span></span>KinectCursorManager(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Window </span></span>window) : <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>(window, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectSensor</span></span>.KinectSensors[0]) { }<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private </span></span>KinectCursorManager(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Window </span></span>window, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">FrameworkElement </span></span>cursor) : <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>(window, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectSensor</span></span>.KinectSensors[0], cursor) { }<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private </span></span>KinectCursorManager(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Window </span></span>window, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectSensor </span></span>sensor) : <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>(window, sensor, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>) { }<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private </span></span>KinectCursorManager(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Window </span></span>window, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectSensor </span></span>sensor, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">FrameworkElement </span></span>cursor){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.window = window;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectSensor</span></span>.KinectSensors.Count > 0){window.Unloaded += <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">delegate</span></span>{<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.kinectSensor.SkeletonStream.IsEnabled)<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.kinectSensor.SkeletonStream.Disable();};window.Loaded += <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">delegate</span></span>{<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(cursor == <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>)cursorAdorner = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">CursorAdorner</span></span>((<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">FrameworkElement</span></span>)window.Content);<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">else</span></span>cursorAdorner = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">CursorAdorner</span></span>((<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">FrameworkElement</span></span>)window.Content, cursor);<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.kinectSensor = sensor;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.kinectSensor.SkeletonFrameReady += SkeletonFrameReady;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.kinectSensor.SkeletonStream.Enable(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">TransformSmoothParameters</span></span>());<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.kinectSensor.Start();};}} ……
下面的代码展示了KinectCursorManager如何和窗体上的可视化元素进行交互。当用户的手位于应用程序可视化元素之上时,KinectCursorManager对象始终保持对当前手所在的可视化元素以及之前手所在的可视化元素的追踪。当这一点发生改变时,KinectCursorManager会触发之前控件的leave事件和当前控件的enter事件。我们也保持对KinectSensor对象的追踪,并触发activated和deactivated事件。
<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private void </span></span>SetSkeletonTrackingActivated() {<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(lastElementOver != <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null </span></span>&& isSkeletonTrackingActivated == <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">false</span></span>){lastElementOver.RaiseEvent(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEventArgs</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectInput</span></span>.KinectCursorActivatedEvent));}isSkeletonTrackingActivated = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">true</span></span>; }<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private void </span></span>SetSkeletonTrackingDeactivated() {<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(lastElementOver != <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null </span></span>&& isSkeletonTrackingActivated == <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">false</span></span>){lastElementOver.RaiseEvent(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEventArgs</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectInput</span></span>.KinectCursorDeactivatedEvent));}isSkeletonTrackingActivated = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">false </span></span>; }<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private void </span></span>HandleCursorEvents(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Point </span></span>point, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double </span></span>z) {<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">UIElement </span></span>element = GetElementAtScreenPoint(point, window);<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(element != <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>){element.RaiseEvent(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventArgs</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectInput</span></span>.KinectCursorMoveEvent, point, z) {Cursor=cursorAdorner });<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(element != lastElementOver){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(lastElementOver != <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>){lastElementOver.RaiseEvent(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventArgs</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectInput</span></span>.KinectCursorLeaveEvent, point, z) { Cursor = cursorAdorner });}element.RaiseEvent(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventArgs</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectInput</span></span>.KinectCursorEnterEvent, point, z) { Cursor = cursorAdorner });}}lastElementOver = element; }
最后需要两个核心的方法来管理KinectCursorManger类。SkeletonFrameReady方法与之前一样,用来从Kinect获取骨骼数据帧时触发的事件。在这个项目中,SkeletonFrameReady方法负责获取合适的骨骼数据,然后获取合适的手部关节点数据。然后将手部关节点数据传到UpdateCusror方法中,UpdateCursor方法执行一系列方法将Kinect骨骼空间坐标系转化到WPF的坐标系统中,Kinect SDK中MapSkeletonPointToDepth方法提供了这一功能。SkeletonToDepthImage方法返回的X,Y值,然后转换到应用程序中实际的宽和高。和X,Y不一样,Z值进行了不同的缩放操作。简单的从Kinect深度摄像机中获取的毫米数据。代码如下,一旦这些坐标系定义好了之后,将他们传递到HandleCursorEvents方法然后CursorAdorner对象将会给用户以反馈。相关代码如下:
<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private void </span></span>SkeletonFrameReady(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">object</span></span>sender, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">SkeletonFrameReadyEventArgs</span></span>e) <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">using</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">SkeletonFrame</span></span>frame = e.OpenSkeletonFrame()) <br style="margin: 0px; padding: 0px;" /> { <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if</span></span>(frame == <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>|| frame.SkeletonArrayLength == 0) <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return</span></span>; <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Skeleton</span></span>[] skeletons = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Skeleton</span></span>[frame.SkeletonArrayLength]; <br style="margin: 0px; padding: 0px;" /> frame.CopySkeletonDataTo(skeletons); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Skeleton</span></span>skeleton = GetPrimarySkeleton(skeletons); <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if</span></span>(skeleton == <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>) <br style="margin: 0px; padding: 0px;" /> { <br style="margin: 0px; padding: 0px;" /> SetHandTrackingDeactivated(); <br style="margin: 0px; padding: 0px;" /> } <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">else <br style="margin: 0px; padding: 0px;" /> </span></span>{ <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Joint</span></span>? primaryHand = GetPrimaryHand(skeleton); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if</span></span>(primaryHand.HasValue) <br style="margin: 0px; padding: 0px;" /> { <br style="margin: 0px; padding: 0px;" /> UpdateCursor(primaryHand.Value); <br style="margin: 0px; padding: 0px;" /> } <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">else <br style="margin: 0px; padding: 0px;" /> </span></span>{ <br style="margin: 0px; padding: 0px;" /> SetHandTrackingDeactivated(); <br style="margin: 0px; padding: 0px;" /> } <br style="margin: 0px; padding: 0px;" /> } <br style="margin: 0px; padding: 0px;" /> } <br style="margin: 0px; padding: 0px;" />} <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private void</span></span>SetHandTrackingDeactivated() <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> cursorAdorner.SetVisibility(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">false</span></span>); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if</span></span>(lastElementOver != <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>&& isHandTrackingActivated == <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">true</span></span>) <br style="margin: 0px; padding: 0px;" /> {lastElementOver.RaiseEvent(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEventArgs</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectInput</span></span>.KinectCursorDeactivatedEvent)); }; <br style="margin: 0px; padding: 0px;" /> isHandTrackingActivated = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">false</span></span>; <br style="margin: 0px; padding: 0px;" />} <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private void</span></span>UpdateCursor(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Joint</span></span>hand) <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var</span></span>point = kinectSensor.MapSkeletonPointToDepth(hand.Position, kinectSensor.DepthStream.Format); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">float</span></span>x = point.X; <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">float</span></span>y = point.Y; <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">float</span></span>z = point.Depth; <br style="margin: 0px; padding: 0px;" /> x = (<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">float</span></span>)(x * window.ActualWidth / kinectSensor.DepthStream.FrameWidth); <br style="margin: 0px; padding: 0px;" /> y = (<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">float</span></span>)(y * window.ActualHeight / kinectSensor.DepthStream.FrameHeight); <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Point</span></span>cursorPoint = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>(x, y); <br style="margin: 0px; padding: 0px;" /> HandleCursorEvents(cursorPoint, z); <br style="margin: 0px; padding: 0px;" /> cursorAdorner.UpdateCursor(cursorPoint); <br style="margin: 0px; padding: 0px;" />}
至此,我们已经简单实现了一些基础结构,这些仅仅是实现了将用户手部的运动显示在屏幕上。现在我们要创建一个基类来监听光标对象的事件,首先创建一个KinectButton对象,该对象继承自WPF Button类型。定义三个之前在KinectInput中定义好的事件,同时创建这些事件的添加删除方法,代码如下:
<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public class </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectButton</span></span>:<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Button <br style="margin: 0px; padding: 0px;" /></span></span>{ <br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEvent</span></span>KinectCursorEnterEvent = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectInput</span></span>.KinectCursorEnterEvent.AddOwner(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectButton</span></span>)); <br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEvent</span></span>KinectCursorLeaveEvent = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectInput</span></span>.KinectCursorLeaveEvent.AddOwner(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectButton</span></span>)); <br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEvent</span></span>KinectCursorMoveEvent = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectInput</span></span>.KinectCursorMoveEvent.AddOwner(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectButton</span></span>)); <br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEvent</span></span>KinectCursorActivatedEvent = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectInput</span></span>.KinectCursorActivatedEvent.AddOwner(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectButton</span></span>)); <br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEvent</span></span>KinectCursorDeactivatedEvent = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectInput</span></span>.KinectCursorDeactivatedEvent.AddOwner(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectButton</span></span>)); <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public event</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventHandler</span></span>KinectCursorEnter <br style="margin: 0px; padding: 0px;" /> { <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">add</span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>.AddHandler(KinectCursorEnterEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); } <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">remove</span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>.RemoveHandler(KinectCursorEnterEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); } <br style="margin: 0px; padding: 0px;" /> } <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public event</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventHandler</span></span>KinectCursorLeave <br style="margin: 0px; padding: 0px;" /> { <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">add</span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>.AddHandler(KinectCursorLeaveEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); } <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">remove</span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>.RemoveHandler(KinectCursorLeaveEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); } <br style="margin: 0px; padding: 0px;" /> } <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public event</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventHandler</span></span>KinectCursorMove <br style="margin: 0px; padding: 0px;" /> { <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">add</span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>.AddHandler(KinectCursorMoveEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); } <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">remove</span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>.RemoveHandler(KinectCursorMoveEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); } <br style="margin: 0px; padding: 0px;" /> } <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public event</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEventHandler</span></span>KinectCursorActivated <br style="margin: 0px; padding: 0px;" /> { <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">add</span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>.AddHandler(KinectCursorActivatedEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); } <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">remove</span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>.RemoveHandler(KinectCursorActivatedEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); } <br style="margin: 0px; padding: 0px;" /> } <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public event</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEventHandler</span></span>KinectCursorDeactivated <br style="margin: 0px; padding: 0px;" /> { <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">add</span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>.AddHandler(KinectCursorDeactivatedEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); } <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">remove</span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>.RemoveHandler(KinectCursorDeactivatedEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); } <br style="margin: 0px; padding: 0px;" /> } <br style="margin: 0px; padding: 0px;" />}
在KinectButton的构造函数中,首先检查当前控件是否运行在IDE或者一个实际的应用程序中。如果没有在设计器中,如果KinectCursorManager对象不存在,我们实例化KinectCursorManager对象。通过这种方式,我们可以在同一个窗体上添加多个Kinect 按钮。这些按钮自动创建KinectCursorManager的实例而不用开发者去创建。下面的代码展示了如何实现这一功能。KinectCursorManager类中的HandleCursorEvents方法负责处理这些事件。
<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public </span></span>KinectButton() <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if</span></span>(!System.ComponentModel.<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DesignerProperties</span></span>.GetIsInDesignMode(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>)) <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorManager</span></span>.Create(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Application</span></span>.Current.MainWindow); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.KinectCursorEnter+=<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventHandler</span></span>(OnKinectCursorEnter); <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.KinectCursorLeave+=<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventHandler</span></span>(OnKinectCursorLeave); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.KinectCursorMove+=<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventHandler</span></span>(OnKinectCursorMove); <br style="margin: 0px; padding: 0px;" />} <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">protected virtual void</span></span>OnKinectCursorLeave(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Object</span></span>sender, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventArgs</span></span>e) <br style="margin: 0px; padding: 0px;" />{ } <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">protected virtual void</span></span>OnKinectCursorMove(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Object</span></span>sender, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventArgs</span></span>e) <br style="margin: 0px; padding: 0px;" />{ }
下面的代码中,KinectCursorEnter事件中触发ClickEvent,将其改造成了一个标准的点击事件。使得KinectButton能够在鼠标移入时触发Click事件。Kinect中应用程序的交互术语还是使用之前GUI交互界面中的术语,这使得读者能够更容易理解。更重要的是,也能够使得开发者更容易理解,因为我们之前有很多使用按钮来构造用户界面的经验。当然终极的目标是舍弃这些各种各样的控件,改而使用纯粹的手势交互界面,但是按钮在现阶段的交互界面中还是很重要的。另外,这样也能够使用按钮来布局图形用户界面,只需要将普通的按钮换成Kinect按钮就可以了。
<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">protected virtual void </span></span>OnKinectCursorEnter(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">object </span></span>sender, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventArgs </span></span>e) {RaiseEvent(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEventArgs</span></span>(ClickEvent)); }
这种控件有一个最大的问题,在大多数基于Kinect的应用程序中你看不到这个问题,那就是,你不能区分开是有意的还是无意的点击。在传统的基于鼠标的GUI应用中也有类似的倾向,每一次将鼠标移动到按钮上不用点击就会激活按钮。这种用户界面很容易不能使用,这也提醒了一个潜在的值得注意的问题,那就是将按钮从图形用户界面中移植到其他界面中可能存在的问题。悬浮按钮是微软试图解决这一特殊问题的一个尝试。
7. 剩余七种常见手势的识别
前面的文章中已经讲述了挥手手势的识别,本文接下来讲解余下7中常见手势的识别。
7.1悬浮按钮(Hover Button)
悬浮按钮是微软在2010年为Kinect对Xbox的操纵盘进行改进而引入的。 悬浮按钮通过将鼠标点击换成悬浮然后等待(hover-and-wait)动作,解决了不小心点击的问题。当光标位于按钮之上时,意味着用户通过将光标悬浮在按钮上一段时间来表示想选中按钮。另一个重要特点是悬浮按钮在用户悬浮并等待时,多少提供了视觉反馈。 在Kinect中实现悬浮按钮和在Windows Phone开发中实现轻点然后维持(tap-and-hold)这一手势在技术上比较类似。必须使用一个计时器来记录当前用户光标停留在按钮上的时间。一旦用户的手的光标和按钮的边界交叉就开始计时。如果某一个时间阈值内用户光标还没有移除,那么就触发点击事件。 创建一个名为HoverButton的类,他继承自之前创建的KinectButton类,在类中添加一个名为hoverTimer的DispatcherTime实例,代码如下。另外创建一个布尔型的timerEnable字段,将其设置为true。虽然目前不会用到这个字段,但是在后面部分将会用到,当我们想使用HoverButton的某些功能,但是不需要DispatcherTimer时就会非常有用。最后创建一个HoverInterval的依赖属性,使得运行我们将悬浮时间用代码或者xaml进行定义。默认设置为2秒,这是在大多是Xbox游戏中的时间。
<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public class </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">HoverButton</span></span>:<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectButton <br style="margin: 0px; padding: 0px;" /></span></span>{ <br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">readonly</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">DispatcherTimer</span></span>hoverTimer = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">DispatcherTimer</span></span>(); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">protected bool</span></span>timerEnabled = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">true</span></span>; <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public double</span></span>HoverInterval <br style="margin: 0px; padding: 0px;" /> { <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get</span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return</span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>)GetValue(HoverIntervalProperty); } <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">set <br style="margin: 0px; padding: 0px;" /> </span></span>{ <br style="margin: 0px; padding: 0px;" /> SetValue(HoverIntervalProperty, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); <br style="margin: 0px; padding: 0px;" /> } <br style="margin: 0px; padding: 0px;" /> } <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">DependencyProperty</span></span>HoverIntervalProperty = <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DependencyProperty</span></span>.Register(<span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"HoverInterval"</span></span>, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>), <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">HoverButton</span></span>), <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">UIPropertyMetadata</span></span>(2000d)); <br style="margin: 0px; padding: 0px;" />…… }
要实现悬浮按钮的核心功能,我们必须覆写基类中的OnKinectCursorLeave和OnKinectCursorEnter方法,所有和KinectCursorManger进行交互的部分在KinectButton中已经实现了,因此我们在这里不用操心。在类的构造方法中,只需要实例化DispathcerTimer对象,HoverInterval依赖属性和注册hoverTimer_Tick方法到计时器的Tick事件上即可。计时器在一定的间隔时间会触发Tick事件,该事件简单的处理一个Click事件,在OnKinectCursorEnter方法中启动计数器,在OnKinectCursorLeave事件中停止计数器。另外,重要的是,在enter和leave方法中启动和停止鼠标光标动画效果。
<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public </span></span>HoverButton() <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> hoverTimer.Interval = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">TimeSpan</span></span>.FromMilliseconds(HoverInterval); <br style="margin: 0px; padding: 0px;" /> hoverTimer.Tick += <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">EventHandler</span></span>(hoverTimer_Tick); <br style="margin: 0px; padding: 0px;" /> hoverTimer.Stop(); <br style="margin: 0px; padding: 0px;" />} <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">void</span></span>hoverTimer_Tick(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">object</span></span>sender, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">EventArgs</span></span>e) <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> hoverTimer.Stop(); <br style="margin: 0px; padding: 0px;" /> RaiseEvent(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEventArgs</span></span>(ClickEvent)); <br style="margin: 0px; padding: 0px;" />} <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">protected override void</span></span>OnKinectCursorLeave(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">object</span></span>sender, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventArgs</span></span>e) <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if</span></span>(timerEnabled) <br style="margin: 0px; padding: 0px;" /> { <br style="margin: 0px; padding: 0px;" /> e.Cursor.StopCursorAnimation(); <br style="margin: 0px; padding: 0px;" /> hoverTimer.Stop(); <br style="margin: 0px; padding: 0px;" /> } <br style="margin: 0px; padding: 0px;" />} <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">protected override void</span></span>OnKinectCursorEnter(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">object</span></span>sender, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventArgs</span></span>e) <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if</span></span>(timerEnabled) <br style="margin: 0px; padding: 0px;" /> { <br style="margin: 0px; padding: 0px;" /> hoverTimer.Interval = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">TimeSpan</span></span>.FromMilliseconds(HoverInterval); <br style="margin: 0px; padding: 0px;" /> e.Cursor.AnimateCursor(HoverInterval); <br style="margin: 0px; padding: 0px;" /> hoverTimer.Start(); <br style="margin: 0px; padding: 0px;" /> } <br style="margin: 0px; padding: 0px;" />}
悬浮按钮在基于Kinect的Xbox游戏中几乎无处不在。悬浮按钮唯一存在的问题是,光标手势悬停在按钮上时会抖动,这可能是Kinect中骨骼识别本身的问题。当在运动状态时,Kinect能够很好的对这些抖动进行平滑,因为即使在快速移动状态下,Kinect中的软件使用了一系列预测和平滑技术来对抖动进行处理。姿势,和上面的悬停一样,因为是静止的,所以可能存在抖动的问题。另外,用户一般不会保持手势静止,即使他们想哪样做。Kinect将这些小的运动返回给用户。当用户什么都没做时,抖动的手可能会破坏手势的动画效果。对悬浮按钮的一个改进就是磁性按钮(Magnet Button),随着体感游戏的升级,这种按钮逐渐取代了之前的悬浮按钮,后面我们将看到如何实现磁性按钮。
7.2 下压按钮(Push Button)
就像悬浮按钮在Xbox中那样普遍一样,一些Kinect开发者也想创建一些类似PC上的那种交互方式的按钮,这种按钮称之为下压按钮(push button)。下压按钮试图将传统的GUI界面上的按钮移植到Kinect上去。为了代替鼠标点击,下压按钮使用一种将手向前推的手势来表示按下这一动作。 这种手势,手掌张开向前,在形式上有点像动态鼠标。下压按钮的核心算法就是探测手势在Z轴上有一个向负方向的运动。另外,相符方向必须有一个距离阈值,使得超过这一阈值就认为用户想要执行下压指令。代码如下所示:下压按钮有一个称之为Threshold的依赖属性,单位为毫米,这个值可以由开发者来根据动作的灵敏度来进行设置。当用户的手移动到下压按钮的上方时,我们记录一下当前位置手的Z值,以此为基准,然后比较手的深度值和阈值,如果超过阈值,就触发点击事件。
<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public class </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">PushButton</span></span>:<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectButton </span></span>{<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">protected double </span></span>handDepth;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public double </span></span>PushThreshold{<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get </span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>)GetValue(PushThresholdProperty); }<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">set </span></span>{ SetValue(PushThresholdProperty, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); }}<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">DependencyProperty </span></span>PushThresholdProperty =<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DependencyProperty</span></span>.Register(<span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"PushThreshold"</span></span>, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>), <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">PushButton</span></span>), <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">UIPropertyMetadata</span></span>(100d));<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">protected override void </span></span>OnKinectCursorMove(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">object </span></span>sender, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventArgs </span></span>e){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(e.Z < handDepth - PushThreshold){RaiseEvent(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEventArgs</span></span>(ClickEvent));}}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">protected override void </span></span>OnKinectCursorEnter(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">object </span></span>sender, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventArgs </span></span>e){handDepth = e.Z;} }
7.3 磁性按钮(Magnet Button)
如前面所讨论的,磁性按钮是对悬浮按钮的一种改进。他对用户悬浮在按钮上的这一体验进行了一些改进。他试图追踪用户手的位置,然后自动将光标对齐到磁性按钮的中间。当用户的手离开磁性按钮的区域是,手势追踪又恢复正常。在其他方面磁性按钮和悬浮按钮的行为一样。考虑到磁性按钮和悬浮按钮在功能方面差异很小,而我们将他单独作为一个完全不同的控件来对待可能有点奇怪。但是,在用户体验设计领域(UX),这一点差异就是一个完全不同的概念。从编码角度看,这一点功能性的差异也使得代码更加复杂。 首先,创建一个继承自HoverButton的名为MagnetButton的类。磁性按钮需要一些额外的事件和属性来管理手进入到磁性按钮区域和手自动对齐到磁性按钮中间区域的时间。我们需要在KinectInput类中添加新的lock和unlock事件,代码如下:
<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEvent </span></span>KinectCursorLockEvent = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">EventManager</span></span>.RegisterRoutedEvent(<span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"KinectCursorLock"</span></span>, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">RoutingStrategy</span></span>.Bubble, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventHandler</span></span>), <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectInput</span></span>));<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public static void </span></span>AddKinectCursorLockHandler(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DependencyObject </span></span>o, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventHandler </span></span>handler) {((<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">UIElement</span></span>)o).AddHandler(KinectCursorLockEvent, handler); }<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEvent </span></span>KinectCursorUnlockEvent = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">EventManager</span></span>.RegisterRoutedEvent(<span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"KinectCursorUnlock"</span></span>, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">RoutingStrategy</span></span>.Bubble, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventHandler</span></span>), <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectInput</span></span>));<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public static void </span></span>RemoveKinectCursorUnlockHandler(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DependencyObject </span></span>o, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventHandler </span></span>handler) {((<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">UIElement</span></span>)o).RemoveHandler(KinectCursorUnlockEvent, handler); }<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public class </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">MagnetButton </span></span>: <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">HoverButton </span></span>{<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">protected bool </span></span>isLockOn = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">true</span></span>;<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEvent </span></span>KinectCursorLockEvent = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectInput</span></span>.KinectCursorUnlockEvent.AddOwner(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">MagnetButton</span></span>));<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEvent </span></span>KinectCursorUnlockEvent = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectInput</span></span>.KinectCursorLockEvent.AddOwner(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">MagnetButton</span></span>));<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Storyboard </span></span>move;<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public event </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventHandler </span></span>KinectCursorLock{<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">add </span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>.AddHandler(KinectCursorLockEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); }<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">remove </span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>.RemoveHandler(KinectCursorLockEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); }}<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public event </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventHandler </span></span>KinectCursorUnLock{<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">add </span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>.AddHandler(KinectCursorUnlockEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); }<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">remove </span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>.RemoveHandler(KinectCursorUnlockEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); }}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public double </span></span>LockInterval{<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get </span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>)GetValue(LockIntervalProperty); }<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">set </span></span>{ SetValue(LockIntervalProperty, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); }}<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">DependencyProperty </span></span>LockIntervalProperty =<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DependencyProperty</span></span>.Register(<span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"LockInterval"</span></span>, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>), <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">MagnetButton</span></span>), <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">UIPropertyMetadata</span></span>(200d));<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public double </span></span>UnlockInterval{<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get </span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>)GetValue(UnlockIntervalProperty); }<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">set </span></span>{ SetValue(UnlockIntervalProperty, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); }}<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">DependencyProperty </span></span>UnlockIntervalProperty =<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DependencyProperty</span></span>.Register(<span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"UnlockInterval"</span></span>, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>), <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">MagnetButton</span></span>), <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">UIPropertyMetadata</span></span>(80d));……}
磁性按钮的代码中,核心地方在于光标从当前位置移动到磁性按钮的中心位置。看起来很简单,实际上实现起来有点麻烦。需要重写基类中的OnKinectCursorEnter和OnKinectCursorLeave方法。确定磁性按钮的锁定位置第一步需要找到磁性按钮本身所处的位置。代码如下,我们使用WPF中最常见名为FindAncestor帮助方法来遍历可视化对象树来进行查找,需要找到承载该磁性按钮的Windows对象,匹配磁性按钮的当前实例到Windows上,然后将其赋给名为Point的变量。但是point对象只保存了当前磁性按钮的左上角的位置。所以,我们需要给在这个点上加一个磁性按钮一半长宽的偏移值,才能获取到磁性按钮的中心位置x,y。
<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private </span></span>T FindAncestor<T>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DependencyObject</span></span>dependencyObject) <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">where</span></span>T:<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">class <br style="margin: 0px; padding: 0px;" /></span></span>{ <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DependencyObject</span></span>target=dependencyObject; <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">do <br style="margin: 0px; padding: 0px;" /> </span></span>{ <br style="margin: 0px; padding: 0px;" /> target=<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">VisualTreeHelper</span></span>.GetParent(target); <br style="margin: 0px; padding: 0px;" /> } <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">while</span></span>(target!=<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>&&!(target <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">is</span></span>T)); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return</span></span>target <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">as</span></span>T; <br style="margin: 0px; padding: 0px;" />} <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">protected override void </span></span>OnKinectCursorEnter(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">object</span></span>sender, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventArgs</span></span>e) <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: green;">//获取按钮位置 <br style="margin: 0px; padding: 0px;" /> </span><span style="margin: 0px; padding: 0px; color: blue;">var</span></span>rootVisual=FindAncestor<<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Window</span></span>>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var</span></span>point=<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.TransformToAncestor(rootVisual).Transform(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>(0,0)); <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var</span></span>x=point.X+<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.ActualWidth/2; <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var</span></span>y=point.Y+<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.ActualHeight/2; <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var</span></span>cursor=e.Cursor; <br style="margin: 0px; padding: 0px;" /> cursor.UpdateCursor(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>(e.X,e.Y),<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">true</span></span>); <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: green;">//找到目的位置 <br style="margin: 0px; padding: 0px;" /> </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>lockPoint=<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>(x-cursor.CursorVisual.ActualWidth/2,y-cursor.CursorVisual.ActualHeight/2); <br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: green;">//当前位置 <br style="margin: 0px; padding: 0px;" /> </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>cursorPoint=<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>(e.X-cursor.CursorVisual.ActualWidth/2,e.Y-cursor.CursorVisual.ActualHeight/2); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: green;"><span style="background-color: rgb(238, 238, 238);">//将光标从当前位置传送到目的位置 <br style="margin: 0px; padding: 0px;" /> </span></span>AnimateCursorToLockPosition(e,x,y,cursor,<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">ref</span></span>lockPoint,<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">ref</span></span>cursorPoint); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>.OnKinectCursorEnter(sender,e); <br style="margin: 0px; padding: 0px;" />} <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">protected override void </span></span>OnKinectCursorLeave(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">object</span></span>sender, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventArgs</span></span>e) <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>.OnKinectCursorLeave(sender, e); <br style="margin: 0px; padding: 0px;" /> e.Cursor.UpdateCursor(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>(e.X,e.Y),<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">false</span></span>); <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var</span></span>rootVisual=FindAncestor<<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Window</span></span>>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var</span></span>point=<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.TransformToAncestor(rootVisual).Transform(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>(0,0)); <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var</span></span>x=point.X+<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.ActualWidth/2; <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var</span></span>y=point.Y+<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.ActualHeight/2; <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var</span></span>cursor=e.Cursor; <br style="margin: 0px; padding: 0px;" /> <br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: green;">//找到目的位置 <br style="margin: 0px; padding: 0px;" /> </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>lockPoint=<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>(x-cursor.CursorVisual.ActualWidth/2,y-cursor.CursorVisual.ActualHeight/2); <br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: green;">//当前位置 <br style="margin: 0px; padding: 0px;" /> </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>cursorPoint=<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>(e.X-cursor.CursorVisual.ActualWidth/2,e.Y-cursor.CursorVisual.ActualHeight/2); <br style="margin: 0px; padding: 0px;" /> <br style="margin: 0px; padding: 0px;" /> AnimateCursorAwayFromLockPosition(e,cursor,<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">ref</span></span>lockPoint,<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">ref</span></span>cursorPoint); <br style="margin: 0px; padding: 0px;" />}
接下来,我们用手所在的X,Y位置替换手势图标的位置。然而,我们也传入了第二个参数,告诉手势图标自动停止追踪手的位置一段时间。当用户看到光标不听手的使唤自动对齐到磁性按钮的中心,这可能有点不太友好。 虽然我们现在有了磁性按钮的中心位置,但是我们仍不能很好的将手势光标定位到中心。我们必须额外的给手势光标本身给一个一半长宽的偏移值,以使得手在光标的中心位置而不是在左上角。在完成这些操作之后,我们将最终的值赋给lockPoint变量。我们也执行了同样的操作来查找光标目前的左上角位置以及偏移量,并将其赋值给cursorPoint变量。有了这两个值,我们就可以从当前的位置使用动画移动到目标位置了。动画方法代码如下:
<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private void </span></span>AnimateCursorAwayFromLockPosition(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventArgs</span></span>e,<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">CursorAdorner</span></span>cursor,<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">ref</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>lockPoint,<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">ref</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>cursorPoint) <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DoubleAnimation</span></span>moveLeft = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">DoubleAnimation</span></span>(lockPoint.X, cursorPoint.X, <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Duration</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">TimeSpan</span></span>.FromMilliseconds(UnlockInterval))); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Storyboard</span></span>.SetTarget(moveLeft, cursor.CursorVisual); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Storyboard</span></span>.SetTargetProperty(moveLeft, <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">PropertyPath</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Canvas</span></span>.LeftProperty)); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DoubleAnimation</span></span>moveTop = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">DoubleAnimation</span></span>(lockPoint.Y, cursorPoint.Y, <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Duration</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">TimeSpan</span></span>.FromMilliseconds(UnlockInterval))); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Storyboard</span></span>.SetTarget(moveTop, cursor.CursorVisual); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Storyboard</span></span>.SetTargetProperty(moveTop, <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">PropertyPath</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Canvas</span></span>.TopProperty)); <br style="margin: 0px; padding: 0px;" /> move = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Storyboard</span></span>(); <br style="margin: 0px; padding: 0px;" /> move.Children.Add(moveTop); <br style="margin: 0px; padding: 0px;" /> move.Children.Add(moveLeft); <br style="margin: 0px; padding: 0px;" /> move.Completed += <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">delegate</span></span>{ <br style="margin: 0px; padding: 0px;" /> move.Stop(cursor); <br style="margin: 0px; padding: 0px;" /> cursor.UpdateCursor(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>(e.X, e.Y), <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">false</span></span>); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.RaiseEvent(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventArgs</span></span>(KinectCursorUnlockEvent, <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>(e.X, e.Y), e.Z) { Cursor = e.Cursor }); <br style="margin: 0px; padding: 0px;" /> }; <br style="margin: 0px; padding: 0px;" /> move.Begin(cursor, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">true</span></span>); <br style="margin: 0px; padding: 0px;" />} <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private void</span></span>AnimateCursorToLockPosition(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventArgs</span></span>e,<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>x,<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>y,<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">CursorAdorner</span></span>cursor,<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">ref</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>lockPoint,<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">ref</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>cursorPoint) <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DoubleAnimation</span></span>moveLeft=<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">DoubleAnimation</span></span>(cursorPoint.X,lockPoint.X,<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Duration</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">TimeSpan</span></span>.FromMilliseconds(LockInterval))); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Storyboard</span></span>.SetTarget(moveLeft,cursor.CursorVisual); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Storyboard</span></span>.SetTargetProperty(moveLeft,<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">PropertyPath</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Canvas</span></span>.LeftProperty)); <br style="margin: 0px; padding: 0px;" /> <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DoubleAnimation</span></span>moveTop=<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">DoubleAnimation</span></span>(cursorPoint.Y,lockPoint.Y,<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Duration</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">TimeSpan</span></span>.FromMilliseconds(LockInterval))); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Storyboard</span></span>.SetTarget(moveTop,cursor.CursorVisual); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Storyboard</span></span>.SetTargetProperty(moveTop,<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">PropertyPath</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Canvas</span></span>.TopProperty)); <br style="margin: 0px; padding: 0px;" /> move=<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Storyboard</span></span>(); <br style="margin: 0px; padding: 0px;" /> move.Children.Add(moveTop); <br style="margin: 0px; padding: 0px;" /> move.Children.Add(moveLeft); <br style="margin: 0px; padding: 0px;" /> move.Completed+=<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">delegate <br style="margin: 0px; padding: 0px;" /> </span></span>{ <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.RaiseEvent(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventArgs</span></span>(KinectCursorLockEvent,<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new</span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>(x,y),e.Z){Cursor=e.Cursor}); <br style="margin: 0px; padding: 0px;" /> }; <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if</span></span>(move!=<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>) <br style="margin: 0px; padding: 0px;" /> move.Stop(e.Cursor); <br style="margin: 0px; padding: 0px;" /> move.Begin(cursor,<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">false</span></span>); <br style="margin: 0px; padding: 0px;" />}
在上面的lock和unlock动画中,我们等到动画结束时触发KinectCursorLock和KinectCursorUnlock事件。对于磁性按钮本身,这些事件用处不大。但是在后面可以给磁性幻灯片按钮提供一些帮助。
7.4 划动(Swipe)
划动手势和挥手(wave)手势类似。识别划动手势需要不断的跟踪用户手部运动,并保持当前手的位置之前的手的位置。因为手势有一个速度阈值,我们需要追踪手运动的时间以及在三维空间中的坐标。下面的代码展示了存储手势位置点的X,Y,Z坐标以及时间值。如果熟悉图形学中的矢量计算,可以将这个认为是一个四维向量。将下面的结构添加到类库中。
<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public struct </span></span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">GesturePoint <br style="margin: 0px; padding: 0px;" /></span></span>{ <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public double </span></span>X { <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get</span></span>; <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">set</span></span>; } <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public double </span></span>Y { <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get</span></span>; <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">set</span></span>; } <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public double </span></span>Z { <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get</span></span>; <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">set</span></span>; } <br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">DateTime </span></span>T { <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get</span></span>; <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">set</span></span>; } <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public override bool </span></span>Equals(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">object </span></span>obj) <br style="margin: 0px; padding: 0px;" /> { <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var </span></span>o = (<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">GesturePoint</span></span>)obj; <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>(X == o.X) && (Y == o.Y) && (Z == o.Z)&&(T==o.T); <br style="margin: 0px; padding: 0px;" /> } <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public override int </span></span>GetHashCode() <br style="margin: 0px; padding: 0px;" /> { <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return base</span></span>.GetHashCode(); <br style="margin: 0px; padding: 0px;" /> } <br style="margin: 0px; padding: 0px;" />}
我们将在KinectCursorManager对象中实现划动手势识别的逻辑,这样在后面的磁吸幻灯片按钮中就可以复用这部分逻辑。实现代码如下,代码中为了支持划动识别,需要向KinectCurosrManager对象中添加几个字段。GesturePoints集合存储路径上的所有点,虽然我们会一边移除一些点然后添加新的点,但是该集合不可能太大。SwipeTime和swipeDeviation分别提供了划动手势经历的时间和划动手势在y轴上的偏移阈值。划动手势经历时间过长和划动手势路径偏移y值过大都会使得划动手势识别失败。我们会移除之前的路径上的点,然后添加新的划动手势上的点。SwipeLength提供了连续划动手势的阈值。我们提供了两个事件来处理划动手势识别成功和手势不合法两种情况。考虑到这是一个纯粹的手势,与GUI界面无关,所以在实现过程中不会使用click事件。
<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">List</span></span><<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">GesturePoint</span></span>> gesturePoints; <br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private bool </span></span>gesturePointTrackingEnabled; <br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private double </span></span>swipeLength, swipeDeviation; <br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private int </span></span>swipeTime; <br style="margin: 0px; padding: 0px;" /><span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public event </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventHandler </span></span>swipeDetected; <br style="margin: 0px; padding: 0px;" /><span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public event </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventHandler </span></span>swipeOutofBoundDetected; <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private double </span></span>xOutOfBoundsLength; <br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private static double </span></span>initialSwipeX;
xOutOfBoundsLength和initialSwipeX用来设置划动手势的开始位置。通常,我们并不关心挥划动手势的开始位置,只用在gesturePoints中寻找一定数量连续的点,然后进行模式匹配就可以了。但是有时候,我们只从某一个划动开始点来进行划动识别也很有用。例如如果在屏幕的边缘,我们实现水平滚动,在这种情况下,我们需要一个偏移阈值使得我们可以忽略在屏幕外的点,因为这些点不能产生手势。 下面的代码展示了一些帮助方法以及公共属性来管理手势追踪。GesturePointTrackingInitialize方法用来初始化各种手势追踪的参数。初始化好了划动手势之后,需要调用GesturePointTrackingStart方法。自然需要一个相应的GesturePointTrackingStop方法来结束挥动手势识别。最后我们需要提供两个重载的帮助方法ResetGesturePoint来管理一系列的我们不需要的手势点。
<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public void </span></span>GesturePointTrackingInitialize(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double </span></span>swipeLength, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double </span></span>swipeDeviation, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">int </span></span>swipeTime, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double </span></span>xOutOfBounds) <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.swipeLength = swipeLength; <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.swipeDeviation = swipeDeviation; <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.swipeTime = swipeTime; <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>.xOutOfBoundsLength = xOutOfBounds; <br style="margin: 0px; padding: 0px;" />} <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public void </span></span>GesturePointTrackingStart() <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(swipeLength + swipeDeviation + swipeTime == 0) <br style="margin: 0px; padding: 0px;" /> <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">throw new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">InvalidOperationException</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"挥动手势识别参数没有初始化!"</span></span>); <br style="margin: 0px; padding: 0px;" /> gesturePointTrackingEnabled = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">true</span></span>; <br style="margin: 0px; padding: 0px;" />} <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public void </span></span>GesturePointTrackingStop() <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> xOutOfBoundsLength = 0; <br style="margin: 0px; padding: 0px;" /> gesturePointTrackingEnabled = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">false</span></span>; <br style="margin: 0px; padding: 0px;" /> gesturePoints.Clear(); <br style="margin: 0px; padding: 0px;" />} <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public bool </span></span>GesturePointTrackingEnabled <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get </span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>gesturePointTrackingEnabled ; } <br style="margin: 0px; padding: 0px;" />} <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private void </span></span>ResetGesturePoint(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">GesturePoint </span></span>point) <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">bool </span></span>startRemoving = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">false</span></span>; <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">for </span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">int </span></span>i= gesturePoints.Count; i >=0; i--) <br style="margin: 0px; padding: 0px;" /> { <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(startRemoving) <br style="margin: 0px; padding: 0px;" /> gesturePoints.RemoveAt(i); <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">else <br style="margin: 0px; padding: 0px;" /> if </span></span>(gesturePoints[i].Equals(point)) <br style="margin: 0px; padding: 0px;" /> startRemoving = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">true</span></span>; <br style="margin: 0px; padding: 0px;" /> } <br style="margin: 0px; padding: 0px;" />} <br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private void </span></span>ResetGesturePoint(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">int </span></span>point) <br style="margin: 0px; padding: 0px;" />{ <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(point < 1) <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return</span></span>; <br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">for </span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">int </span></span>i = point-1; i >=0; i--) <br style="margin: 0px; padding: 0px;" /> { <br style="margin: 0px; padding: 0px;" /> gesturePoints.RemoveAt(i); <br style="margin: 0px; padding: 0px;" /> } <br style="margin: 0px; padding: 0px;" />}
划动(swipe)手势识别的核心算法在HandleGestureTracking方法中,代码如下。将KinectCursorManager中的UpdateCursor方法和Kinect中的骨骼追踪事件绑定。每一次当获取到新的坐标点时,HandGestureTracking方法将最新的GesturePoint数据添加到gesturePoints集合中去。然后执行一些列条件检查,首先判断新加入的点是否以手势开始位置为起点参考,偏离Y轴过远。如果是,抛出一个超出范围的事件,然后将所有之前累积的点清空,然后开始下一次的划动识别。其次,检查手势开始的时间和当前的时间,如果时间差大于阈值,那么移除开始处手势点,然后将紧接着的点作为手势识别的起始点。如果新的手的位置在这个集合中,就很好。紧接着,判断划动起始点的位置和当前位置的X轴上的距离是否超过了连续划动距离的阈值,如果超过了,则触发SwipeDetected事件,如果没有,我们可以有选择性的判断,当前位置的X点是否超过了划动识别的最大区间返回,然后触发对于的事件。然后我们等待新的手部点传到HandleGestureTracking方法中去。
<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private void </span></span>HandleGestureTracking(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">float </span></span>x, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">float </span></span>y, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">float </span></span>z) {<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(!gesturePointTrackingEnabled)<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return</span></span>;<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: green;">// check to see if xOutOfBounds is being used</span><span style="margin: 0px; padding: 0px; color: blue;">if </span></span>(xOutOfBoundsLength != 0 && initialSwipeX == 0){initialSwipeX = x;}<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">GesturePoint </span></span>newPoint = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">GesturePoint</span></span>() { X = x, Y = y, Z = z, T = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DateTime</span></span>.Now };gesturePoints.Add(newPoint);<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">GesturePoint </span></span>startPoint = gesturePoints[0];<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var </span></span>point = <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">Point</span></span>(x, y);<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: green;">//check for deviation</span><span style="margin: 0px; padding: 0px; color: blue;">if </span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Math</span></span>.Abs(newPoint.Y - startPoint.Y) > swipeDeviation){<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: green;">//Debug.WriteLine("Y out of bounds");</span><span style="margin: 0px; padding: 0px; color: blue;">if </span></span>(swipeOutofBoundDetected != <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>)swipeOutofBoundDetected(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>, <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventArgs</span></span>(point) { Z = z, Cursor = cursorAdorner });ResetGesturePoint(gesturePoints.Count);<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return</span></span>;}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>((newPoint.T - startPoint.T).Milliseconds > swipeTime) <span style="margin: 0px; padding: 0px; color: green;"><span style="background-color: rgb(238, 238, 238);">//check time</span></span>{gesturePoints.RemoveAt(0);startPoint = gesturePoints[0];}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>((swipeLength < 0 && newPoint.X - startPoint.X < swipeLength) <span style="margin: 0px; padding: 0px; color: green;"><span style="background-color: rgb(238, 238, 238);">// check to see if distance has been achieved swipe left</span></span>|| (swipeLength > 0 && newPoint.X - startPoint.X > swipeLength)) <span style="margin: 0px; padding: 0px; color: green;"><span style="background-color: rgb(238, 238, 238);">// check to see if distance has been achieved swipe right</span></span>{gesturePoints.Clear();<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: green;">//throw local event</span><span style="margin: 0px; padding: 0px; color: blue;">if </span></span>(swipeDetected != <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>)swipeDetected(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>, <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventArgs</span></span>(point) { Z = z, Cursor = cursorAdorner });<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return</span></span>;}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(xOutOfBoundsLength != 0 &&((xOutOfBoundsLength < 0 && newPoint.X - initialSwipeX < xOutOfBoundsLength) <span style="margin: 0px; padding: 0px; color: green;"><span style="background-color: rgb(238, 238, 238);">// check to see if distance has been achieved swipe left</span></span>|| (xOutOfBoundsLength > 0 && newPoint.X - initialSwipeX > xOutOfBoundsLength))){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(swipeOutofBoundDetected != <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>)swipeOutofBoundDetected(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">this</span></span>, <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventArgs</span></span>(point) { Z = z, Cursor = cursorAdorner });} }
7.5 磁性幻灯片(Magnetic Slide)
磁性幻灯片是Kinect手势中的精华(holy grail)。他由Harmonix公司的交互设计师们在开发《舞林大会》(Dance Central)这一款游戏时创造的。最初被用在菜单系统中,现在作为一种按钮在很多地方有应用,包括Xbox自身的操作面板。他比磁性按钮好的地方就是,不需要用户等待一段时间。在Xbox游戏中,没有人愿意去等待。而下压按钮又有自身的缺点,最主要的是用户体验不是很好。磁性幻灯片和磁性按钮一样,一旦用户进入到按钮的有效区域,光标就会自定锁定到某一点上。但是在这一点上,可以有不同的表现。除了悬停在按钮上方一段时间触发事件外,用户可以划动收来激活按钮。 从编程角度看,磁性幻灯片基本上是磁性按钮和划动手势(swipe)的组合。要开发一个磁性幻灯片按钮,我们可以简单的在可视化树中的悬浮按钮上声明一个计时器,然后再注册滑动手势识别事件。下面的代码展示了磁性幻灯片按钮的基本结构。其构造函数已经在基类中为我们声明好了计时器。InitializeSwipe和DeinitializeSwipe方法负责注册KinectCursorManager类中的滑动手势识别功能。
<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public class </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">MagneticSlide</span></span>:<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">MagnetButton </span></span>{<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private bool </span></span>isLookingForSwipes;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public </span></span>MagneticSlide(){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>.isLockOn = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">false</span></span>;}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private void </span></span>InitializeSwipe(){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(isLookingForSwipes)<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return</span></span>;<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var </span></span>kinectMgr = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorManager</span></span>.Instance;kinectMgr.GesturePointTrackingInitialize(SwipeLength, MaxDeviation, MaxSwipeTime, xOutOfBoundsLength);kinectMgr.swipeDetected += <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventHandler</span></span>(kinectMgr_swipeDetected);kinectMgr.swipeOutofBoundDetected += <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventHandler</span></span>(kinectMgr_swipeOutofBoundDetected);kinectMgr.GesturePointTrackingStart();}<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private void </span></span>DeInitializeSwipe(){<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">var </span></span>KinectMgr = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorManager</span></span>.Instance;KinectMgr.swipeDetected -= <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventHandler</span></span>(kinectMgr_swipeDetected);KinectMgr.swipeOutofBoundDetected -= <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventHandler</span></span>(kinectMgr_swipeOutofBoundDetected);KinectMgr.GesturePointTrackingStop();isLookingForSwipes = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">false</span></span>;}
另外,我们也需要将控件的滑动手势的初始化参数暴露出来,这样就可以根据特定的需要进行设置了。下面的代码展示了SwipeLength和XOutOfBoundsLength属性,这两个都是默认值的相反数。这是因为磁性幻灯片按钮一般在屏幕的右侧,需要用户向左边划动,因此,相对于按钮位置的识别偏移以及边界偏移是其X坐标轴的相反数。
<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">DependencyProperty </span></span>SwipeLengthProperty =<br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DependencyProperty</span></span>.Register(<span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"SwipeLength"</span></span>, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>), <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">MagneticSlide</span></span>), <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">UIPropertyMetadata</span></span>(-500d));<br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public double </span></span>SwipeLength<br style="margin: 0px; padding: 0px;" />{<br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get </span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>)GetValue(SwipeLengthProperty); }<br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">set </span></span>{ SetValue(SwipeLengthProperty, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); }<br style="margin: 0px; padding: 0px;" />}<br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">DependencyProperty </span></span>MaxDeviationProperty =<br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DependencyProperty</span></span>.Register(<span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"MaxDeviation"</span></span>, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>), <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">MagneticSlide</span></span>), <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">UIPropertyMetadata</span></span>(100d));<br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public double </span></span>MaxDeviation<br style="margin: 0px; padding: 0px;" />{<br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get </span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>)GetValue(MaxDeviationProperty); }<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">set </span></span>{ SetValue(MaxDeviationProperty, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); }<br style="margin: 0px; padding: 0px;" />}<br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">DependencyProperty </span></span>XOutOfBoundsLengthProperty =<br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DependencyProperty</span></span>.Register(<span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"XOutOfBoundsLength"</span></span>, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>), <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">MagneticSlide</span></span>), <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">UIPropertyMetadata</span></span>(-700d));<br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public double </span></span>XOutOfBoundsLength<br style="margin: 0px; padding: 0px;" />{<br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get </span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">double</span></span>)GetValue(XOutOfBoundsLengthProperty); }<br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">set </span></span>{ SetValue(XOutOfBoundsLengthProperty, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); }<br style="margin: 0px; padding: 0px;" />}<br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">DependencyProperty </span></span>MaxSwipeTimeProperty =<br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">DependencyProperty</span></span>.Register(<span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"MaxSwipeTime"</span></span>, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">int</span></span>), <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">MagneticSlide</span></span>), <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">UIPropertyMetadata</span></span>(300));<br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public int </span></span>MaxSwipeTime<br style="margin: 0px; padding: 0px;" />{<br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">get </span></span>{ <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">return </span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">int</span></span>)GetValue(MaxSwipeTimeProperty); }<br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">set </span></span>{ SetValue(MaxSwipeTimeProperty, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); }<br style="margin: 0px; padding: 0px;" />}
要实现磁性幻灯片按钮的逻辑,我们只需要处理基类中的enter事件,以及划动手势识别事件即可。我们不会处理基类中的leave事件,因为当用户做划动手势时,极有可能会不小心触发leave事件。我们不想破坏之前初始化好了的deactivate算法逻辑,所以取而代之的是,我们等待要么下一个划动识别成功,要么在关闭划动识别前划动手势超出识别范围。当探测到划动时,触发一个标准的click事件。
<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public static readonly </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEvent </span></span>SwipeOutOfBoundsEvent = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">EventManager</span></span>.RegisterRoutedEvent(<span style="margin: 0px; padding: 0px; color: rgb(163, 21, 21);"><span style="background-color: rgb(238, 238, 238);">"SwipeOutOfBounds"</span></span>, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">RoutingStrategy</span></span>.Bubble,<br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventHandler</span></span>), <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">typeof</span></span>(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectInput</span></span>));<br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public event </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEventHandler </span></span>SwipeOutOfBounds<br style="margin: 0px; padding: 0px;" />{<br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">add </span></span>{ AddHandler(SwipeOutOfBoundsEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); }<br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">remove </span></span>{ RemoveHandler(SwipeOutOfBoundsEvent, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">value</span></span>); }<br style="margin: 0px; padding: 0px;" />}<br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">void </span></span>KinectMgr_swipeOutofBoundDetected(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">object </span></span>sender, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventArgs </span></span>e)<br style="margin: 0px; padding: 0px;" />{<br style="margin: 0px; padding: 0px;" /> DeInitializeSwipe();<br style="margin: 0px; padding: 0px;" /> RaiseEvent(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">KinectCursorEventArgs</span></span>(SwipeOutOfBoundsEvent));<br style="margin: 0px; padding: 0px;" />}<br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">void </span></span>KinectMgr_swipeDetected(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">object </span></span>sender, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventArgs </span></span>e)<br style="margin: 0px; padding: 0px;" />{<br style="margin: 0px; padding: 0px;" /> DeInitializeSwipe();<br style="margin: 0px; padding: 0px;" /> RaiseEvent(<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">new </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">RoutedEventArgs</span></span>(ClickEvent));<br style="margin: 0px; padding: 0px;" />}<br style="margin: 0px; padding: 0px;" /><br style="margin: 0px; padding: 0px;" /><span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">protected override void </span></span>OnKinectCursorEnter(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">object </span></span>sender, <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">KinectCursorEventArgs </span></span>e)<br style="margin: 0px; padding: 0px;" />{<br style="margin: 0px; padding: 0px;" /> InitializeSwipe();<br style="margin: 0px; padding: 0px;" /> <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">base</span></span>.OnKinectCursorEnter(sender, e);<br style="margin: 0px; padding: 0px;" />}
7.6 垂直滚动条(Vertical Scroll)
并不是所有的内容都能够在一屏之内显示完。有时候可能有一些内容会大于屏幕的实际尺寸,这就需要用户来滚动屏幕或者列表控件来显示在屏幕之外的内容。传统上,垂直滚动条一直是交互界面设计的一个禁忌。但是垂直滚动条在划动触摸界面中得到了很好的应用。所以Xbox和Sony PlayStation系统中都使用了垂直滚动条来构建菜单。Harmonix’s的《舞林大会》(Dance Central)这一系列游戏使用了垂直滚动条式的菜单系统。Dance Central第一次成功的使用了垂直滚动界面作为手势交互界面。在下面的手势交互图中,当用户抬起或者放下手臂时会使得屏幕的内容垂直滚动。胳膊远离身体,抬起手臂会使得屏幕或者菜单从下往上移动,放下手臂会使得从上往下移动。 水平的划动在Kinect应用中似乎很常见(尤其是在Metro风格的Xbox游戏交互界面中,水平划动是占主导的手势),但是垂直滚动用户体验更加友好,也是用户交互界面更好的选择。水平或者垂直划动手势有一些小的用户体验问题。另外,划动手势在识别上也较困难,因为挥动的形式和动作因人而异,且差别很大。就算同一个人,划动手势也不是一直不变的。划动手势在触摸屏设备上能够较好的工作是因为除非不触摸到屏幕,那么动作就不会发生。但是在手势识别界面上,用户的手是和视觉元素进行交互的,这时手就是在某一特定的坐标空间中的视觉元素。 当用户做划动手势时,在整个手的划动过程中会手的位置在水平方向会保持相对一致。这就使得如果想进行多次连续的划动手势时会产生一些问题。有时候会产生一些比较尴尬的场景,那就是会无意中撤销前一次的划动手势。例如,用户使用右手从右向左进行划动手势,使得页面会跳转到下一页,现在用户的右手在身体的左边位置了,然后用户想将手移动回原始的开始位置以准备下一次的从右向左的挥动手势。但是,如果用于依然保持手在水平位置大致一致的话,应用程序会探测到一次从左向右的划动操作然后又将界面切换到了之前的那一页。这就使得用户必须创建一个循环的运动来避免不必要的误读。更进一步,频繁的划动手势也容易使得用户疲劳,而垂直方向的划动也只会加剧这一问题。 但是垂直滚动条则不会有上述的这些用户体验上的缺点。他比较容易使用,对用户来说也更加友好,另外,用户也不需要为了保持手在水平或者垂直方向一致而导致的疲劳。从技术方面来讲,垂直滚动操作识别较划动识别简单。垂直滚动在技术上是一个姿势而不是手势。滚动操作的探测是基于当前手臂的位置而不是手臂的运动。滚动的方向和大小由手臂和水平方向的夹角来确定。下图演示了垂直滚动。 使用之前的姿势识别那篇文章中的内容,我们能够计算从用户的身体到肩部和手腕的夹角,定义一个角度区间作为中间姿势,当用户手臂在这一区间内时,不会产生任何动作,如上图中的,当手臂自然处于-5度或者355度时,作为偏移的零点。建议在实际开发中,将零点的偏移上下加上20度左右。当用户的手臂离开这一区域时,离开的夹角及变化的幅度根据需求而定。但是建议至少在0度区间上下有两个区间来表示小幅和大幅的增加。这使得能够更好的实现传统的人机交互界面中的垂直滚动条的逻辑。
7.7 通用暂停按钮(Universal Pause)
暂停按钮,通常作为引导手势或者退出手势,是微软建议在给用户提供引导时很少的几个手势之一。这个手势是通过将左臂保持和身体45度角来完成的。在很多Kinect的游戏中都使用到了这一手势,用来暂停动作或者唤出Xbox菜单。和本文之前介绍的手势不一样,这个手势并没有什么符号学上的含义,是一个认为设计的动作。通用暂停手势很容易实现,也不一定要限制手臂,并且不容易和其他手势混淆。 通用暂停手势的识别和垂直滚动手势的识别有点类似,就是计算左臂和身体的夹角,然后加上一个阈值即可,相信很简单,在这里就不再赘述了。
2.8测试Demo
结合前篇文章中的挥动(wave)手势识别,以及上文将的几种按钮,做了一个小的例子,使用之前开发的手势识别库,以及手势识别按钮。这部分代码很简单,直接引用之前在类库中定义好的控件即可。大家可以下载本文后面的代码自己回去实验一下。 截图如下:
8. 手势识别的未来
我们进入商场可能对各种商品不会有很深的印象,同样,随着时间的流逝,Kinect也会变得不那么令人印象深刻,甚至被大多数人忘记。但是,随着软硬件技术的发展,Kinect或者相似的技术会融入到生活的各个方面,让你看不到Kinect的存在。 当我们进入一个购物商场时,当你靠近入口时,门会自动打开。这在当时很让人印象深刻,但没有人会注意,观察甚至意识到这一特性。在未来的某一天,Kinect也会像自动门一样融入生活的方方面面。使得我们感受不到他的存在。 Kinect以及NUI的世界才刚刚开始,随着时间的推移,这种交互体验会发生巨大变化。在电影《少数派报告》(Minority Report)中,汤姆克鲁斯使用手势浏览和打开大屏幕上的各种文件和影像资料,这一场景现在已经是基于Kinect的应用程序的要实现目标之一。 有时候,科幻片中比现实有更好的想像力,也能提供比现实生活中更好的科技。在星际迷航(Star Trek),星球大战(Star Wars)或者 2001:太空漫游(2001: A Space Odyssey)这些科幻电影中,电脑能够看到和感应人的存在。在这些电影中,用户使用语音和手势无缝的和电脑进行交互。当然这种交互也有负面因素,应该设置一些限制。 虽然科幻电影变成现实的这一前景会引发一些理性的担忧,但是这种变化正在到来。意识到这种变化带来的好处很重要。Kinect及其类似的技术使得我们的生活环境更加智能化。它使得应用程序能够识别用户的手势,进而能够分析出用户的意图,而不需要用户明确的给出这些信息或者指令。现在的Kinect游戏是基于应用程序查找特定的用户的手势而进行操作的,用户必须主动的和应用程序进行交流或者发送指令。但是,还是有很多用户的信息没有被捕捉和处理。如果应用程序能够探测到其他信息,确切的说,如用户的情绪,那么就可以提供更加人性化的定制服务了。现在我们所识别的姿势都很简单,我们只是在学习如何建立交互界面,可以预见在未来,随着基于手势交互的应用程序越来越多,这种用户界面就会消失,就像移动设备中触摸界面逐渐取代光标那样。 想象一下,下班后回到家,走到卧室,说一句“电脑,放点music”。于是电脑就会识别语音指令开始播放音乐。但是,电脑也能够识别到你工作了一天,需要一些音乐来放松心情,于是电脑自动的根据你的心情选择一些歌曲。语音成了首要的发送指令的形式,手势来对一些指令进行增强。在上面的例子中,电脑能够根据你的身体语言,识别你的情绪,这样,手势是一种主动的,有上下文情景的和电脑进行交互的方法。这并不意味这手势变得不那么重要,相反重要性增加了,只不过是以一种间接的方式。 如今有一些声控传感器,例如能够根据声音探测到人进入到房间,然后开灯或者关灯的声控开关。这是一种比较笨拙的系统,他没有上下文提供。如果使用Kinect技术,它能够识别用户的运动,然后根据情形调整灯光的亮度。例如,如果在凌晨2点,你想起床喝点水,电脑可能会将灯光调整的比较暗,以至于不会太刺眼。但是如果某一天你凌晨2点钟从外面回来,Kinect识别到你是清醒的,就会把灯全部打开。 目前,Kinect仍然是一种比较新的技术,我们仍然试图理解然后能够更好的发挥它的潜能。在最开始Kinect出来的时候只是观看或者体验。随着他作为Xbox外设的发布,游戏的主题也有了一些限制。大多数游戏都是运动型的,这些游戏都只能识别用户的一些基本手势,如跑,跳,踢,划动,扔等这些手势或动作。早期的Kinect体感游戏也只有一些简单的菜单系统,使用手来进行操作。 虽然用户体验设计发生了巨大变化,但是目前基于手势的游戏和应用比较简单。我们仍然处在学习如何定义和识别手势的阶段。这使得我们的手势有些粗糙。我们仍需要扩大手势的幅度才能达到好的识别效果。当我们能够识别到手势的细微方面时,应用程序所带来的沉浸感将会大大提升。 现在的足球游戏只能识别到基本的踢球动作,游戏不能够判断用户是使用脚趾,脚背,脚踝还是脚跟跟球进行交互的。这些不同的姿势可能对球产生完全不同的影响,从而使得游戏产生不同的结果。更进一步,游戏应该能够根据用户踢球的动作,脚的位置,应用一些物理特性,给予球一些真实的加速度,旋转,弧度等特性,这样会使得游戏更加真实,玩家也更加有沉浸感。 目前的这些限制主要是由Kinect摄像头的分辨率决定的。下一代的Kinect硬件设备可能会使用更高分辨率的摄像头来提供更好的深度影像数据。微软已经放出了关于第二代Kinect硬件方面的相关信息。这使得更精确的手势识别变为可能,给基于Kinect的应用程序开发带来了两个方面的改进。首先是骨骼关节精度的提升,这不但能够提升手势识别的精度,也能够扩大手势识别的种类。另一个改进是使得能够产生一些额外的关节点,如手指的信息,以及一些非关节点如嘴唇,鼻子,耳朵,眼睛等位置信息。如今这些信息都能够识别的出来,只是需要使用一些第三方的类库,官方的SDK中没有原生的对这些特征进行支持。 对手指进行追踪和识别能够大大的提高符号语言的作用。如果应用程序能够识别手指的运动,用户就能够使用手指进行更高精度和更自然的操作。手指手势交互信息很复杂,在用户进行交流的过程中能够提供丰富的上下文信息。即使能够识别到手指手势,今天的基于Kinect的间交互体验也没有发生革命性的变化,这是因为用户依然必须意识到Kinect的存在,还要知道如何和这些硬件交互,需要做什么样的手势才能达到想要的结果。当你看别人玩Kinect游戏时,你会注意到他是如何在Kinect前面做动作的。用户的姿势有时候比较僵硬和不自然。很多姿势并不能识别,一些需要重复多次,更坏的情况下姿势会被错误识别,这样就会导致一些意想不到的操作。更进一步,用户的手势通常需要过分的夸大才能被kinect识别的到。这些问题可能都是暂时的。在未来,随着软硬件的不断升级和改进,在使用基于手势的人机交互界面时,用户会越来越感到舒服和自然。从这方面讲,Kinect是一个神奇的设备,以至于会像自动门那样被广大的用户所知道和接受。 手势识别数据集: 本文转载自http://www.cnblogs.com/tornadomeet/archive/2013/01/10/2854421.html ChaLearn Gesture Challenge挑战赛是手势识别中比较新的(2011年开始的)一个挑战赛,属于机器学习挑战赛中的一个,其初衷是进行One-Shot learning的挑战,当然也不局限在此。官网为:http://gesture.chalearn.org/ ,2012年分别主办了两轮比赛,一个是在CVPR2012中进行,另一个是在ICPR2012中进行。关于本次比赛的一些规则,以及已经完成比赛的作者的识别方法的一些细节在官网上都能查看得到。大家有兴趣13年也可以参加下。因为在日常生活中会遇到各种各样的手势,如果对这些手势的识别能取得很大成功的话,那么离AI又更近一步了,下面这张图是生 活中常见产生手势的场所: 本文主要是简单介绍下关于这个挑战赛的数据库,数据库的英文名称为ChaLearn Gesture Data,简称CGD,或者CGD2011。数据库可以从http://gesture.chalearn.org/data/cgd2011这里下载,如果下载压缩版的则大小约5G,否则无压缩版的则达到30G。考虑到即使压缩过后,其实也不会对识别结果有太大的影响,且本人电脑承受能力有限,所以我这里下载了个压缩版的。 当然了,下载下来后的数据库都是视频格式,分为深度图像视频和彩色图像视频,因为在录制视频的时候是用Kinect录制的。由此可见,这些手势识别既可以能有深度信息又可以利用色彩信息,这对搞深度数据的行为识别的人算是一个很好的公共数据库了。 下面是这个数据库的一些特征: 1. 共有30个左右的手势单词,也就是说有30个基本的手势动作。 2. 视频数据分为500个batch,每个batch含有94个视频,视频分为2种,每种各47个。如果视频名称为M_x.avi的话,则表示是RGB图像的视频,否则名称为K_x.avi表示的是深度图像的视频。每个视频有可能有多个基本手势动作,一般是1~5个,且每个batch最终是100个手势,很明显这中间有重复的手势(因为总共才有30个手势单词)。因此,最终有50000=500*100个手势。每个batch如下图显示: 当然了,其中还包含了2个csv文件,用来描述训练和测试数据用的(具体应用还没弄过)。 3. 视频数据的录制是由20个人完成的。取出一个人的某一个手势的深度图和RGB图,显示如下: 4. 视频中每一张图片的大小为240*320. 5. 视频的帧率是每秒10帧。 6. 这500个batch又分为2种,即”devel”和”valid”,其中”devel”有480个,”valid”有20个。”devel”表示可用于挑战赛开发(development)的数据库,这些数据提供了训练数据和测试数据的标注。”valid”表示用于验证(validation)的,只提供了训练数据的标签。测试结果需要在网络上提交。 7. 深度图像是以灰阶的形式显示出来的,其计算公式如下所示: f(x)=(x-mini)/(maxi-mini),max为最大距离,min为最小距离。X是当前点的距离,f(x)为深度图像显示时的灰度值。 8. 深度图像也是有不同精度的,因为录制数据时的软件版本不同。这里分为3个精度,0表示一般(Mediocre),1表示比较好(Good),2表示非常好(Very Good). 比如说前面几个batch的精度显示如下(num表示序号,acc表示精度, miss表示漏洞的文件): 如果大家是研究手势识别的话,特别是基于Kinet深度信息的,可以一起交流下,挑战下这个数据库! 参考资料: http://gesture.chalearn.org/
手势识别(1):基础理论相关推荐
- 基于视觉的手势识别系统(一)——前言
基于视觉的手势识别系统(一)--前言 hj961107@163.com https://blog.csdn.net/qq_39033834 六个月前开始接触计算机视觉(Computer Vision) ...
- Lambda 表达式基础理论与示例
Lambda 表达式基础理论与示例 Lambda 表达式,也可称为闭包,推动 Java 8 发布的最重要新特性. Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中). 使用 Lam ...
- 基于视频理解TSM和数据集20bn-jester-v1的27类手势识别
基于视频理解TSM-mobilenetv2和数据集20bn-jester-v1的27类手势识别 基于视频理解TSM-resnet50和数据集20bn-jester-v1的27类手势识别 基于视频理解T ...
- 基于OpenCV与tensorflow实现实时手势识别
点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 干货-阅读需10分钟左右 基于OpenCV与tensorflow ...
- 【Android游戏开发十六】Android Gesture之【触摸屏手势识别】操作!
本站文章均为 李华明Himi 原创,转载务必在明显处注明: 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/android-game/337.html ...
- 一文详解非线性优化算法:保姆级教程-基础理论
不论是刚入门SLAM的小白,还是导航相关专业的同学,都对"非线性优化"这个词不陌生,如果你说你没听过这个词,那"因子图"一词总该略有耳闻吧,如果还是不知道,那就 ...
- tof摄像头手势识别_行业深度光学行业研究:CIS、光学元件、指纹识别、镜头模组...
一.受益于高端产品及多摄,镜头厂商利好 镜头是一个技术门槛较高的行业,行业格局集中度较高.从市场格局看,行业集中度较 高,并且有进一步提升的趋势.各厂商镜头营收占公司总营收的比例:大立光 99%.玉 ...
- iOS手势识别的详细使用(拖动,缩放,旋转,点击,手势依赖,自定义手势)
其余的请见:http://blog.csdn.net/totogo2010/article/details/8615940 1.UIGestureRecognizer介绍 手势识别在iOS上非常重要, ...
- 发展第三代AI:清华AI研究院基础理论研究中心成立,朱军任主任
https://www.toutiao.com/a6687778128141484552/ 人工智能正处在高速发展时期,而清华的研究人员却早已意识到了目前方法的局限,并放眼于下一代技术上了.5 月 6 ...
最新文章
- hive根据已有表创建新表_Hive基础之创建表
- Android NDK JNI 简单例子1 : Android NDK配置和下载
- 深蓝学院《从零开始手写VIO》作业六
- Python中的生成器与迭代器
- Placement new的用法及用途【转】
- 一个高性能RPC框架的连接管理
- ios是否安装了某应用
- IIS出现问题报CS0016
- JAVA代码查错试题集
- HTML5 web SQL 和indexedDB的使用
- android 支付宝手势密码锁,Android仿支付宝手势密码解锁
- 财务管理都学什么计算机课程,计算机财务管理课程教学大纲.DOC
- R语言学习(二)数据分析数据探索
- flash cs3下停止movieClip的播放
- 重要:欢迎光临新版微软中文CRM论坛!
- java压缩与解压缩(1)使用java.util.zip
- 360搜索推出致敬女性专题 董卿咪蒙领衔十大杰出女性
- 【成为架构师课程系列】消息队列:秒杀时如何处理每秒上万次的下单请求?
- android 定时推送提醒,使用workManager实现每日定时推送通知
- torch.cat()的类型转换