我有一个媒体应用程序,允许用户播放,暂停,逐帧,FastForward等。我试图使用Rx获得步进和FastForward的以下行为。
我认为我的快进部分是正确的,但我不确定如何制作这个以获得步骤功能。我也愿意采用“更好”的方式来做快进。
//start FF when we get 2 key presses within the threshold time
Observable.FromEventPattern<KeyEventArgs>(this, "KeyDown")
.Where(k => k.EventArgs.Key == Key.Right)
.Timestamp()
.Buffer(2)
.Where(x => (x[1].Timestamp - x[0].Timestamp).Milliseconds < 300)
.Subscribe(x =>
{
Console.WriteLine("FastForward GO");
_viewModel.FastForward();
});
//stop ff on the key up
Observable.FromEventPattern<KeyEventArgs>(this, "KeyUp")
.Where(k => k.EventArgs.Key == Key.Right)
.Subscribe(x => {
Console.WriteLine("FastForward STOP");
_viewModel.StopFastForward();
});
答案 0 :(得分:2)
var up = Observable.FromEventPattern<KeyEventArgs>(this, "KeyUp")
.Where(x => x.EventArgs.KeyCode == Keys.Right);
// Take, Concat, and Repeat work together to prevent repeated KeyDown events.
var down = Observable.FromEventPattern<KeyEventArgs>(this, "KeyDown")
.Where(x => x.EventArgs.KeyCode == Keys.Right)
.Take(1)
.Concat(up.Take(1).IgnoreElements())
.Repeat();
var t = TimeSpan.FromMilliseconds(300);
var tap = down.SelectMany(x =>
Observable.Amb(
Observable.Empty<EventPattern<KeyEventArgs>>().Delay(t),
up.Take(1)
))
.Publish()
.RefCount();
var longPress = down.SelectMany(x =>
Observable.Return(x).Delay(t).TakeUntil(tap)
);
有多种方法可以做到这一点,但这有助于获得所需的“longPress”以及“tap”。您可以使用longPress
开始快速发送,up
停止快进,tap
进行快速转发。
tap
的时间范围内按下并释放某个键时, t
会产生。
longPress
时, t
会产生。
up
会产生。
存在问题,因为Keydown事件会在每次按下一个键时重复多次。
var down = Observable.FromEventPattern<KeyEventArgs>(this, "KeyDown");
在这种情况下,我们需要一种方法来过滤掉重复的KeyDown事件。我们可以通过使用运算符组合来实现。首先,我们将使用Take(1)
。这将产生第一个事件而忽略其余事件。
var first = down.Take(1);
如果我们只需要按一下实际按键,那就太棒了。但是,唉,我们需要得到所有的实际按键。我们需要等待KeyUp事件发生并开始整个过程。为此,我们可以使用Concat
和Repeat
的组合。对于concat observable,我们需要确保我们只接受1个up事件,并且我们忽略了up observable的元素,否则我们最终会将所有up事件提供给我们新的observable。
var down = Observable.FromEventPattern<KeyEventArgs>(this, "KeyDown")
.Take(1)
.Contact(up.Take(1).IgnoreElements())
.Repeat();
这为我们提供了实际向下事件,没有中间重复事件。
现在我们已经清理了我们的源可观察对象,我们可以用有用的方式开始编写它们。我们正在寻找的是“点击”活动和“长按”活动。要获得点击事件,我们需要执行单个实际注册事件,并确保它没有按下太长时间......一种方法是使用{{1} } operator。
Amb
var tap = down.SelectMany(x =>
Observable.Amb(
Observable.Empty<EventPattern<KeyEventArgs>>().Delay(t),
up.Take(1)
))
运算符代表“模棱两可”。它需要一些Observable,听取每一个,并等待它们产生一些东西。一旦其中一个产生事件,Amb
运算符将忽略(处理其他可观察的订阅)。
在我们的案例中,对于发生的每个事件,我们使用Amb
和SelectMany
运算符来检查哪个收益率或首先完成......单个向上事件或空的可观察事件在t的一个时间段之后完成。如果up事件发生在空observable完成之前,则触发它。否则,我们会忽略它。
现在我们可以为“长按”做类似的事情,除了这次我们想要延迟KeyDown事件,直到我们知道它不是一个点击。我们可以使用Amb
和Delay
运算符的组合来执行此操作。 TakeUntil
确保在注册点击之前不会长按,Delay
确保我们忽略了KeyPress,如果它最终是一个点按。
TakeUntil
此版本适用于任何密钥。
var longPress = down.SelectMany(x =>
Observable.Return(x).Delay(t).TakeUntil(tap)
);
var up = Observable.FromEventPattern<KeyEventArgs>(this, "KeyUp");
var downWithRepeats = Observable.FromEventPattern<KeyEventArgs>(this, "KeyDown");
var down =
Observable.Merge(
up.Select(x => new { e = x, type = "KeyUp" }),
downWithRepeats.Select(x => new { e = x, type = "KeyDown" })
)
.GroupByUntil(
x => x.e.EventArgs.KeyCode,
g => g.Where(y => y.type == "KeyUp")
)
.SelectMany(x => x.FirstAsync())
.Select(x => x.e);
var t = TimeSpan.FromMilliseconds(300);
var tap = down.SelectMany(x =>
Observable.Amb(
Observable.Empty<EventPattern<KeyEventArgs>>().Delay(t),
up.Where(y => y.EventArgs.KeyCode == x.EventArgs.KeyCode).Take(1)
))
.Publish()
.RefCount();
var longPress = down.SelectMany(x =>
Observable.Return(x).Delay(t).TakeUntil(
tap.Where(y => y.EventArgs.KeyCode == x.EventArgs.KeyCode)
)
);
答案 1 :(得分:1)
这是Chris的另一种选择,它提供三个流,一个用于点击,一个用于开始持有,一个用于结束持有。使用TimeInterval
来记录事件之间的持续时间。
我们可以使用GroupByUntil
分组KeyDown
来捕获KeyDown消除重复,直到KeyUp
发生:
TimeSpan limit = TimeSpan.FromMilliseconds(300);
var key = Keys.Right;
var keyUp = Observable.FromEventPattern<KeyEventArgs>(this, "KeyUp")
.Where(i => i.EventArgs.KeyCode == key)
.Select(_ => true);
var keyDown = Observable.FromEventPattern<KeyEventArgs>(this, "KeyDown")
.Where(i => i.EventArgs.KeyCode == key)
.GroupByUntil(k => 0, _ => keyUp)
.SelectMany(x => x.FirstAsync());
var keyDownDuration = keyDown.Select(k => keyUp.TimeInterval()).Switch();
var clicks = keyDownDuration.Where(i => i.Interval < limit);
var beginHold = keyDown.Select(k => Observable.Timer(limit).TakeUntil(keyUp))
.Switch();
var endHold = keyDownDuration.Where(i => i.Interval > limit);
/* usage */
clicks.Subscribe(_ => Console.WriteLine("Click"));
beginHold.Subscribe(_ => Console.WriteLine("Hold Begin"));
endHold.Subscribe(_ => Console.WriteLine("Hold End"));
最初,我错误地认为KevEventArgs
的WPF风格因为IsRepeat
在WinForms版本中不可用 - 这意味着这对OP不起作用,但我会把它保留为它可能对其他人有用。
TimeSpan limit = TimeSpan.FromMilliseconds(300);
var key = Key.Right;
var keyUp = Observable.FromEventPattern<KeyEventArgs>(this, "KeyUp")
.Where(i => i.EventArgs.Key == key);
var keyDown = Observable.FromEventPattern<KeyEventArgs>(this, "KeyDown")
.Where(i => i.EventArgs.IsRepeat == false
&& i.EventArgs.Key == key);
var keyDownDuration = keyDown.Select(k => keyUp.TimeInterval()).Switch();
var clicks = keyDownDuration.Where(i => i.Interval < limit);
var beginHold = keyDown.Select(k => Observable.Timer(limit).TakeUntil(keyUp))
.Switch();
var endHold = keyDownDuration.Where(i => i.Interval > limit);
/* usage */
clicks.Subscribe(_ => Console.WriteLine("Click"));
beginHold.Subscribe(_ => Console.WriteLine("Hold Begin"));
endHold.Subscribe(_ => Console.WriteLine("Hold End"));
包含nuget包rx-main并将WinForms / WPF或代码段粘贴到Form构造函数的末尾。然后运行代码并按右箭头键,同时观察VS Output窗口以查看结果。