解决方案
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 的时间跨度内按下并释放一个键时产生。
当按键被按住的时间超过t 时,longPress 产生。
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();
这为我们提供了 实际 向下事件,没有中间重复事件。
现在我们已经清理了源 observables,我们可以开始以有用的方式组合它们。我们正在寻找的是一个“点击”事件和一个“长按”事件。要获得点击事件,我们需要获取单个 actual down 事件,并确保它不会被按住太久...一种方法是使用 Amb 运算符.
var tap = down.SelectMany(x =>
Observable.Amb(
Observable.Empty<EventPattern<KeyEventArgs>>().Delay(t),
up.Take(1)
))
Amb 运算符代表“模糊”。它需要许多 Observable,监听每个,并等待它们产生一些东西。一旦其中一个产生了一个事件,Amb 操作符就会忽略(释放订阅)其他可观察对象。
在我们的例子中,对于每个发生的 down 事件,我们使用 SelectMany 和 Amb 运算符来检查哪个先产生或完成... t 的时间跨度。如果 up 事件发生在空的 observable 完成之前,它是一个点击。否则,我们忽略它。
现在我们可以为“长按”做类似的事情,除了这次我们想要延迟 KeyDown 事件,直到我们知道它不是点击。我们可以使用Delay 和TakeUntil 运算符的组合来执行此操作。 Delay 确保在注册点击之前不会发生长按,TakeUntil 确保我们忽略 KeyPress,如果它最终证明是点击。
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)
)
);
用法
Observable.Merge(
down .Select(x => string.Format("{0} - press", x.EventArgs.KeyCode)),
tap .Select(x => string.Format("{0} - tap", x.EventArgs.KeyCode)),
longPress.Select(x => string.Format("{0} - longPress", x.EventArgs.KeyCode)),
up .Select(x => string.Format("{0} - up", x.EventArgs.KeyCode))
)
.ObserveOn(SynchronizationContext.Current)
.Select(x => string.Format("{0} - {1}", x, DateTime.Now.ToLongTimeString()))
.Subscribe(text => this.myTextBox.Text = text);