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

时间:2010-12-03 15:06:22

标签: c# wpf .net-4.0 system.reactive

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

这些示例都展示了如何定义MouseDownMouseMoveMouseUp

中的事件流
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");

然后如何在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();
             });

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

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

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

所以第一个看似合理的选项是订阅mouseUp事件并在那里做一些事情。

mouseUp.Subscribe(x =>
           {
              // Do stuff here..
           }

但是在这一点上,我可以回到使用“普通”MouseLeftButtonUp事件处理程序。

还有另一种方法可以确定mouseDrag何时“完成”(或TakeUntil(mouseUp))何时发生并执行某些操作?

1 个答案:

答案 0 :(得分: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);
}

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

element.TrackDrag(
    rect => { },
    () => {}
);

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

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

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