我是一个Rx新手,正在尝试如何用Rx处理鼠标手势。我在某个地方找到了这个解决方案:
var mouseMove = Observable.FromEventPattern<MouseEventArgs>(this, "MouseMove");
var lMouseDown = Observable.FromEventPattern<MouseEventArgs>(this, "MouseDown")
.Where(e => e.EventArgs.Button == MouseButtons.Left);
var lMouseUp = Observable.FromEventPattern<MouseEventArgs>(this, "MouseUp")
.Where(e => e.EventArgs.Button == MouseButtons.Left);
var dragSequence =
from down in lMouseDown
from move in mouseMove.StartWith(down).TakeUntil(lMouseUp)
select move;
dragSequence.ObserveOn(this).Subscribe(e => Trace.WriteLine(e.EventArgs.Location));
但是,多个独立的鼠标手势都是同一个流的一部分。所以我不能使用onCompleted的处理程序;该序列永远不会完成。我想为每次拖动将流分成一个单独的序列,我该怎么做?
这是我的解决方案:
var mouseMove = Observable.FromEventPattern<MouseEventArgs>(this, "MouseMove");
var lMouseDown = Observable.FromEventPattern<MouseEventArgs>(this, "MouseDown")
.Where(e => e.EventArgs.Button == MouseButtons.Left);
var lMouseUp = Observable.FromEventPattern<MouseEventArgs>(this, "MouseUp")
.Where(e => e.EventArgs.Button == MouseButtons.Left);
lMouseDown.SelectMany(start =>
{
// a new drag event has started, prepare to receive input
var dragSeq = new List<Point>();
Action<EventPattern<MouseEventArgs>, bool> onNext = (e, mouseUp) => {
// This code runs for each mouse move while mouse is down.
// In my case I want to constantly re-analyze the shape being
// drawn, so I make a list of points and send it to a method.
dragSeq.Add(e.EventArgs.Location);
AnalyzeGesture(dragSeq, mouseUp);
};
return mouseMove
.StartWith(start)
.TakeUntil(lMouseUp.Do(e => onNext(e, true)))
.Do(e => onNext(e, false));
})
.Subscribe();
它的工作原理是,每次鼠标按下时,start=>{...}
lambda都会运行。此lambda返回一个可观察的值,该值使用Do()来处理每个输入。请注意,lambda本身在没有订阅的情况下创建了一个事件流,并且我正在丢弃内部和外部可观察的结果,因为Do()已经处理了输入。
lambda不订阅查询,因为外部subscribe()具有订阅单个鼠标拖动和整个鼠标拖动序列的效果(多亏了SelectMany)。
如果鼠标上一次移动的点和上次移动的点不一样,我用Do()
来捕捉它。但是,看起来鼠标上一点总是等于上一点。因此,这里有一个稍微简单一点的版本,它忽略了鼠标上点:
lMouseDown.SelectMany(start =>
{
var dragSeq = new List<Point>();
return mouseMove
.StartWith(start)
.TakeUntil(lMouseUp)
.Do(e => {
dragSeq.Add(e.EventArgs.Location);
AnalyzeGesture(dragSeq, false);
}, () => AnalyzeGesture(dragSeq, true));
})
.Subscribe();
要扩展我的评论,可以使用.Scan
运算符
Func<List<T>, T, List<T>> AddWithNew = (list, t) =>
{
var newList = list.ToList();
newList.Add(t);
return newList;
}
var dragGestures = from start in lMouseDown
select mouseMove.StartWith(start)
.TakeUntil(lMouseUp)
.Scan(new List<Point>(), AddWithNew);
dragGestures.Subscribe(listOfPoints => Console.WriteLine(listOfPoints));
序列仍然是"永无止境"的,但你会收到越来越多的Subscribe
方法的点列表,当新行开始时,这些点会重置回1:
[(0,0] // Mouse down
[(0,0), (1,1)] // Mouse move
[(0,0), (1,1), (1,0)] // Mouse up
[(6,7)] // Mouse down again
您也可以使用.Window
运算符将您的序列划分为一系列序列:
var dragSequences = from start in lMouseDown
select mouseMove.StartWith(start)
.TakeUntil(lMouseUp)
.Scan(new List<Point>(), AddWithNew)
.Window(() => lMouseUp);
dragSequences.Subscribe(seq =>
{
seq.Subscribe(list => Analyze(list, false);
seq.Last().Subscribe(list => Analyze(list, true);
});
这里有一种方法:
var dragSequences = dragSequence.TakeUntil(lMouseUp)
.Concat(Observable.Return<MouseEventArgs>(null)) // send a NULL event after a drag completes
.Repeat(); // then start listening for the next drag gesture
dragSequences.ObserveOn(this).Subscribe(e =>
{
if (e == null)
{
// the previous drag operation has completed. Take any actions you need
}
else
{
// drag event. If the event is for MouseDown then it is the start of a new drag
}
});