3

我是一名 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 使用处理程序;序列永远不会完成。我想为每次拖动将流分成单独的序列,我该怎么做?

4

3 回答 3

3

这是我的解决方案:

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() 处理每个输入的 observable。请注意,lambda 本身创建了一个事件流而不订阅它,并且我丢弃了内部和外部 observable 的结果,因为 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();
于 2013-07-09T17:46:05.090 回答
1

在这里扩展我的评论是您可以使用.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);
});
于 2013-07-11T12:42:03.353 回答
0

这是一种方法:

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
    }
});
于 2013-07-09T17:05:43.787 回答