鼠标手势非常方便,在很多程序中得到了广泛应用。在这里我要介绍一种通过用Reactive Extensions快速实现鼠标手势功能的方法,以供有需要的朋友参考。
一般用的那些只有上下左右的鼠标手势的识别原理并不复杂,只需要对鼠标轨迹进行定点采集,根据偏移的角度进行降噪处理,折换成上下左右四个方向即可。具体代码可以参考下文中的GetDirection方法(这个函数不是我写的,以前在网上找的,原始出处不记得了)。通过用Reactive extensions可以非常容易的实现这一过程。具体代码如下:
public
partial
class
Form1 : Form
{
public Form1()
{
InitializeComponent();
Process();
}
private void Process()
{
var mouseDown = Observable.FromEventPattern < MouseEventArgs > ( this , " MouseDown " );
var mouseMove = Observable.FromEventPattern < MouseEventArgs > ( this , " MouseMove " );
var mouseUp = Observable.FromEventPattern < MouseEventArgs > ( this , " MouseUp " );
var mousePath = mouseMove.SkipUntil(mouseDown).TakeUntil(mouseUp).Select(i => i.EventArgs.Location);
mousePath.Take( 1 ).Subscribe(_ => { }, () => label1.Text = " waitting for mouse up... " );
ProcessMouseGesture(mousePath);
}
void ProcessMouseGesture(IObservable < Point > mousePath)
{
// 这里只是取0.1秒间隔作为采集点,不是很合适,还要把距离拿来一起算可能更精确一些
var points = mousePath.Sample(TimeSpan.FromSeconds( 0.1 ));
var directions = (from direction in points.Zip(points.Skip( 1 ), (p1, p2) => GetDirection(p1, p2))
where direction != MouseGestureDirection.Unknown
select direction).DistinctUntilChanged();
var directionList = new List < MouseGestureDirection > ();
directions.Subscribe(d => directionList.Add(d), () =>
{
label1.Text = string .Join( " , " , directionList.ToArray());
Process();
});
}
static MouseGestureDirection GetDirection(Point start, Point end)
{
const double maxAngleError = 30 ;
int deltaX = end.X - start.X;
int deltaY = end.Y - start.Y;
double length = Math.Sqrt(deltaX * deltaX + deltaY * deltaY);
double sin = deltaX / length;
double cos = deltaY / length;
double angle = Math.Asin(Math.Abs(sin)) * 180 / Math.PI;
if ((sin >= 0 ) && (cos < 0 ))
angle = 180 - angle;
else if ((sin < 0 ) && (cos < 0 ))
angle = angle + 180 ;
else if ((sin < 0 ) && (cos >= 0 ))
angle = 360 - angle;
// direction recognition
if ((angle > 360 - maxAngleError) || (angle < 0 + maxAngleError))
return MouseGestureDirection.Down;
else if ((angle > 90 - maxAngleError) && (angle < 90 + maxAngleError))
return MouseGestureDirection.Right;
else if ((angle > 180 - maxAngleError) && (angle < 180 + maxAngleError))
return MouseGestureDirection.Up;
else if ((angle > 270 - maxAngleError) && (angle < 270 + maxAngleError))
return MouseGestureDirection.Left;
else return MouseGestureDirection.Unknown;
}
}
enum MouseGestureDirection
{
Unknown,
Up,
Right,
Down,
Left
}
{
public Form1()
{
InitializeComponent();
Process();
}
private void Process()
{
var mouseDown = Observable.FromEventPattern < MouseEventArgs > ( this , " MouseDown " );
var mouseMove = Observable.FromEventPattern < MouseEventArgs > ( this , " MouseMove " );
var mouseUp = Observable.FromEventPattern < MouseEventArgs > ( this , " MouseUp " );
var mousePath = mouseMove.SkipUntil(mouseDown).TakeUntil(mouseUp).Select(i => i.EventArgs.Location);
mousePath.Take( 1 ).Subscribe(_ => { }, () => label1.Text = " waitting for mouse up... " );
ProcessMouseGesture(mousePath);
}
void ProcessMouseGesture(IObservable < Point > mousePath)
{
// 这里只是取0.1秒间隔作为采集点,不是很合适,还要把距离拿来一起算可能更精确一些
var points = mousePath.Sample(TimeSpan.FromSeconds( 0.1 ));
var directions = (from direction in points.Zip(points.Skip( 1 ), (p1, p2) => GetDirection(p1, p2))
where direction != MouseGestureDirection.Unknown
select direction).DistinctUntilChanged();
var directionList = new List < MouseGestureDirection > ();
directions.Subscribe(d => directionList.Add(d), () =>
{
label1.Text = string .Join( " , " , directionList.ToArray());
Process();
});
}
static MouseGestureDirection GetDirection(Point start, Point end)
{
const double maxAngleError = 30 ;
int deltaX = end.X - start.X;
int deltaY = end.Y - start.Y;
double length = Math.Sqrt(deltaX * deltaX + deltaY * deltaY);
double sin = deltaX / length;
double cos = deltaY / length;
double angle = Math.Asin(Math.Abs(sin)) * 180 / Math.PI;
if ((sin >= 0 ) && (cos < 0 ))
angle = 180 - angle;
else if ((sin < 0 ) && (cos < 0 ))
angle = angle + 180 ;
else if ((sin < 0 ) && (cos >= 0 ))
angle = 360 - angle;
// direction recognition
if ((angle > 360 - maxAngleError) || (angle < 0 + maxAngleError))
return MouseGestureDirection.Down;
else if ((angle > 90 - maxAngleError) && (angle < 90 + maxAngleError))
return MouseGestureDirection.Right;
else if ((angle > 180 - maxAngleError) && (angle < 180 + maxAngleError))
return MouseGestureDirection.Up;
else if ((angle > 270 - maxAngleError) && (angle < 270 + maxAngleError))
return MouseGestureDirection.Left;
else return MouseGestureDirection.Unknown;
}
}
enum MouseGestureDirection
{
Unknown,
Up,
Right,
Down,
Left
}
由于我对Reactive Extensions不熟,只是偶尔有空的时候看了看,目前还属于管中窥豹阶段,上述代码并非最佳实践,本文这里只是给出了一种思路和方法,如果谁有更合适的实现,欢迎留言指正。