鼠标输入Rx-制作一系列可观察的东西



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

这是我的解决方案:

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()来处理每个输入。请注意,lambda本身在没有订阅的情况下创建了一个事件流,并且我正在丢弃内部和外部可观察的结果,因为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();

要扩展我的评论,可以使用.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);
});

这里有一种方法:

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
    }
});

相关内容

  • 没有找到相关文章

最新更新