Reactive Extensions(Rx) - 仅在按住键时订阅鼠标位置数据

时间:2014-02-19 16:36:47

标签: wpf events system.reactive

在WPF中,我只想在按住键的同时订阅鼠标位置点。然后,我想将捕获的点设置到属性上,只有当键被释放时(即,当我有一整套捕获点时)并继续侦听下一个键向下/向上组合以创建另一个鼠标位置捕获,等

我对上面的解释是,我需要在按键关闭时触发序列,并在释放按键时停止拍摄,但我希望OnNext接收一组鼠标点。

从相当数量的阅读(我是Rx的新手)我把以下伪/真实样本放在一起:

var keyDownSeq = Observable.FromEvent(...);
var keyUpSeq = Observable.FromEvent(...);
var mouseMoveSeq = Observable.FromEvent(...);

var mouseMovesWhileKeyDown = keyDownSeq
    .Where(keyEventArgs => keyEventArgs.IsRepeat == false) //WPF fires the same KeyDown repeatedly
    .Where(keyEventArgs => keyEventArgs.Key == Key.Space)
    .Select(_ => mouseMoveSeq
                    .TakeUntil(keyUpSeq)
                    .ToList())
    .Subscribe(listOfMousePoints => MyProperty = listOfMousePoints);
  1. 以上是否会按照我的想法执行操作并创建按住空格键时遇到的鼠标点列表?我需要在我所在的地方调用ToList(),还是应该在订阅中执行此操作?

  2. 如果我删除了第二个Where子句(允许按任何键开始捕获),如何防止第二个或第三个键被按下并在结果序列中造成重复?

  3. 谢谢。

    修改

    使用局部变量执行以下操作是否完全不正确?

    • 将局部变量设置为Select()
    • 中的KeyDown序列值
    • 当KeyUpSeq遇到相同的密钥
    • 时,将局部变量重置为null
    • 过滤KeyDownSeq以忽略所有值,而此变量具有值
    • 过滤KeyUpSeq以忽略与本地变量
    • 不匹配的所有KeyUp值

    Rx是否有这种局部状态变量的概念?

2 个答案:

答案 0 :(得分:2)

我看到两种简化设置的方法。第一种是创建单个IObservable<bool>,当您的键在向下或向上位置之间切换时,它会完全发出。

//true means key down, false means key up
IObservable<bool> keyChange =
    Observable.Merge(
        Observable.FromEvent(/*keyDown*/).Select(_ => true),
        Observable.FromEvent(/*keyUp*/).Select(_ => false))
    .DistinctUntilChanged();

历史将是这样的,x是事件触发,TF是布尔值,从左到右是增加时间。

keyDown    -----xxxxxxxx--------xxxxxxx------xxxxxxxxxxxxx----
keyUp      xxxxx--------xxxxxxxx-------xxxxxx-------------xxxx
keyChange  F----T-------F-------T------F-----T------------F---

第二种方法是使用Observable.Window在关键的窗口和#34;之间拉出连续的鼠标移动序列。并关键关闭&#34;窗口关闭。&#34;

IObservable<Point> mouseMoves = Observable.FromEvent(...);

IObservable<IObservable<Point>> mousePaths = mouseMoves.Window(
    keyChange.Where(b => b),
    _ => keyChange.Where(b => !b));

所用方法的文档:

Window的签名一开始可能有点吓人,但一旦理解了它,它的使用起来相当简单。

答案 1 :(得分:1)

你必须小心这里的竞争条件。

进行一些设置。

IObservable<Unit> keyDown = Observable.FromEvent(/*keydown*/).Select(_=>true);
IObservable<Unit> keyUp = Observable.FromEvent(/*keyup*/).Select(_=>false);

IObservable<Point> mouseMoves = Observable.FromEvent(...);

现在创建一个我们知道将以keyDown事件开始的observable

var keys = keyDown.Merge(keyUp).DistinctUntilChanged().SkipWhile(_=>!_);

使用此observable创建一个窗口。

IOBservable<IObservable<Point> mousePaths = 
    mouseMoves
       .Window(keys)
       .Where((_,i)=>i%2==0);

请注意,我们要跳过奇数窗口。即便是窗户也很关键。奇怪的窗户是关键。

现在没有竞争条件,我们永远不会错过keyDown或keyUp事件。

解释如果你使用Window的另一个重载,那个具有单独的打开和关闭触发器的重载,问题是关闭触发器生成是懒惰的。它不会生成,也不会在触发窗口的开始边缘之前订阅它的源。这意味着在注册窗口关闭触发器之前,有一个小的时间窗口可以发生keyUp事件。