What is the proper way to determine the end of a mouse drag using Rx?
I am slowly learning how to use Reactive Extensions for .NET with WPF. There a few beginner examples about how simple it is to write drag-drop or drawing routines but they are all extremely simple. I'm trying to go one step further and it's not obvious to me what the "proper" way is.
The examples all show how you can define streams of events from MouseDown
, MouseMove
, and MouseUp
var mouseDown = from evt in Observable.FromEvent<MouseButtonEventArgs>(..., "MouseDown")
select evt.EventArgs.GetPosition(...);
var mouseMoves = from evt in Observable.FromEvent<MouseEventArgs>(..., "MouseMove")
select evt.EventArgs.GetPosition(...);
var mouseUp = Observable.FromEvent<MouseButtonEventArgs>(..., "MouseUp");
And then how you can easily do things during a MouseDrag
(this displays the co-ordinates of the rectangle created from the starting drag point to the current mouse position)
var mouseDrag = from start in mouseDown
from currentPosition in mouseMoves.TakeUntil(mouseUp)
select new Rect(Math.Min(start.X, currentPosition.X),
Math.Min(start.Y, currentPosition.Y),
Math.Abs(start.X - currentPosition.X),
Math.Abs(start.Y - currentPosition.Y));
mouseDrag.Subscribe(x =>
{
Info.Text = x.ToString();
});
My question is, what is the "proper" way to accomplish a task at the end of the mouse drag? Originally, I thought I could do something like this:
mouseDrag.Subscribe(
onNext: x =>
{
Info.Text = x.ToString();
},
onCompleted: () =>
{
// Do stuff here...except it never gets called
});
Reading more of the documentation, though, it seems that onCompleted
is called when there is no more data (ever) and when the object can be disposed.
So the first option that seems plausable is subscribing to the mouseUp
event and doing something there.
开发者_如何学GomouseUp.Subscribe(x =>
{
// Do stuff here..
}
But then at this point, I may as well go back to just use the "normal" MouseLeftButtonUp
event handler.
Is there another way to determine when the mouseDrag
is "completed" (or when the TakeUntil(mouseUp)
) occurs and perform some action then?
The sequence never completes because the source (MouseDown) never completes (it is an event). It's worth pointing out that a IObservable
cannot call OnComplete
of a subscriber more than once, it's part of the contract (OnNext* (OnCompleted|OnError)?
).
To find out when themouseMove.TakeUntil(mouseUp)
sequence completes, you'll need to hook into the call to SelectMany
:
public static IDisposable TrackDrag(this UIElement element,
Action<Rect> dragging, Action dragComplete)
{
var mouseDown = Observable.FromEvent(...);
var mouseMove = Observable.FromEvent(...);
var mouseUp = Observable.FromEvent(...);
return (from start in mouseDown
from currentPosition in mouseMove.TakeUntil(mouseUp)
.Do(_ => {}, () => dragComplete())
select new Rect(Math.Min(start.X, currentPosition.X),
Math.Min(start.Y, currentPosition.Y),
Math.Abs(start.X - currentPosition.X),
Math.Abs(start.Y - currentPosition.Y));
).Subscribe(dragging);
}
Then you can use it like so:
element.TrackDrag(
rect => { },
() => {}
);
For the interest of clarity, here is the LINQ expression using the underlying extension methods:
return mouseDown.SelectMany(start =>
{
return mouseMove
.TakeUntil(mouseUp)
.Do(_ => {}, () => dragComplete())
.Select(currentPosition => new Rect(...));
})
.Subscribe(dragging);
That is, for each value from mouseDown a new sequence will be subscribed to. When that sequence completes, call dragComplete().
精彩评论