鼠标手势非常方便,在很多程序中得到了广泛应用。在这里我要介绍一种通过用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不熟,只是偶尔有空的时候看了看,目前还属于管中窥豹阶段,上述代码并非最佳实践,本文这里只是给出了一种思路和方法,如果谁有更合适的实现,欢迎留言指正。