Reputation: 17186
I'm an Rx newbie trying to figure out how to process mouse gestures with Rx. I found this solution somewhere:
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));
But multiple independent mouse gestures are all part of the same stream. So I can't use a handler for onCompleted; the sequence is never completed. I'd like to break the stream into a separate sequence for each drag, how do I do that?
Upvotes: 3
Views: 765
Reputation: 7919
To expand on my comment here is a way in which you might use the .Scan
operator
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));
The sequence will still be "never-ending" but you will receive increasing lists of points to your Subscribe
method that are reset back to 1 when a new line begins:
[(0,0] // Mouse down
[(0,0), (1,1)] // Mouse move
[(0,0), (1,1), (1,0)] // Mouse up
[(6,7)] // Mouse down again
You could also use the .Window
operator to partition your sequence into a sequence of sequences:
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);
});
Upvotes: 1
Reputation: 39192
Here is one way:
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
}
});
Upvotes: 0
Reputation: 17186
Here's my solution:
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();
How this works is, each time a mouse-down arrives, the start=>{...}
lambda runs. This lambda returns an observable that uses Do() to process each input. Notice that the lambda itself creates an event stream without subscribing to it, and I am discarding the results of the inner and outer observable, because Do() has already processed the input.
The lambda does not subscribe to the query because the outer Subscribe() has the effect of subscribing to both an individual mouse drag and the whole sequence of them (thanks to SelectMany).
In case the mouse-up point differs from last mouse move, I used Do()
to capture it. However, it appears that the mouse-up point is always equal to the previous point. So here's a slightly simpler version that ignores the mouse-up point:
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();
Upvotes: 3