使用Rx确定鼠标拖动结束的正确方法是什么?

Jed*_*dja 2 c# wpf .net-4.0 system.reactive

我正在慢慢学习如何使用WPF的.NET Reactive Extensions.有一些初学者的例子说明编写拖放或绘图例程是多么简单,但它们都非常简单.我试图更进一步,对我而言,"正确"方式是什么并不明显.

这些例子都告诉你如何从定义事件流MouseDown,MouseMove以及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");
Run Code Online (Sandbox Code Playgroud)

然后如何在a期间轻松完成任务MouseDrag(这将显示从起始拖动点到当前鼠标位置创建的矩形的坐标)

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();
             });
Run Code Online (Sandbox Code Playgroud)

我的问题是,在鼠标拖动结束时完成任务的"正确"方法是什么?最初,我以为我可以这样做:

mouseDrag.Subscribe(
     onNext: x =>
             {
                 Info.Text = x.ToString();
             },
     onCompleted: () =>
              {
                 // Do stuff here...except it never gets called
              });
Run Code Online (Sandbox Code Playgroud)

但是,阅读更多文档时,似乎onCompleted在没有更多数据(永远)以及可以处理对象时调用.

因此,看起来可行的第一个选择是订阅mouseUp事件并在那里做一些事情.

mouseUp.Subscribe(x =>
           {
              // Do stuff here..
           }
Run Code Online (Sandbox Code Playgroud)

但是在这一点上,我不妨回过头来使用"普通" MouseLeftButtonUp事件处理程序.

是否有另一种方法来确定何时mouseDrag"完成"(或何时TakeUntil(mouseUp))发生并执行某些操作呢?

Ric*_*lay 5

序列永远不会完成,因为源(MouseDown)永远不会完成(它是一个事件).值得指出的是,IObservable不能OnComplete多次调用订户,这是合同的一部分(OnNext* (OnCompleted|OnError)?).

要找出mouseMove.TakeUntil(mouseUp)序列何时完成,您需要挂钩调用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);
}
Run Code Online (Sandbox Code Playgroud)

然后你可以像这样使用它:

element.TrackDrag(
    rect => { },
    () => {}
);
Run Code Online (Sandbox Code Playgroud)

为了清楚起见,这里是使用底层扩展方法的LINQ表达式:

return mouseDown.SelectMany(start =>
{
    return mouseMove
        .TakeUntil(mouseUp)
        .Do(_ => {}, () => dragComplete())
        .Select(currentPosition => new Rect(...));
})
.Subscribe(dragging);
Run Code Online (Sandbox Code Playgroud)

也就是说,对于来自mouseDown的每个值,将订阅新序列.当序列完成后,调用dragComplete().