Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/ruby/22.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 反应式扩展:匹配复杂的按键顺序_C#_System.reactive_Keypress - Fatal编程技术网

C# 反应式扩展:匹配复杂的按键顺序

C# 反应式扩展:匹配复杂的按键顺序,c#,system.reactive,keypress,C#,System.reactive,Keypress,我试图实现的是用Rx处理一些复杂的按键和释放序列。我在Rx方面有一些经验,但这显然不足以满足我目前的任务,所以我来这里寻求帮助 我的WinForms应用程序正在后台运行,仅在系统托盘中可见。通过给定的键序列,我想激活它的一个窗体。顺便说一句,为了连接到全局按键,我使用了一个很好的库,我能够接收每一个按键下降和上升事件,当按键下降时,会产生多个按键下降事件(以标准键盘重复率) 我想要捕捉的一个示例键序列是快速按Ctrl+Insert键(比如在给定的时间段内按住Ctrl键并按Insert两次)。以下

我试图实现的是用Rx处理一些复杂的按键和释放序列。我在Rx方面有一些经验,但这显然不足以满足我目前的任务,所以我来这里寻求帮助

我的WinForms应用程序正在后台运行,仅在系统托盘中可见。通过给定的键序列,我想激活它的一个窗体。顺便说一句,为了连接到全局按键,我使用了一个很好的库,我能够接收每一个按键下降和上升事件,当按键下降时,会产生多个按键下降事件(以标准键盘重复率)

我想要捕捉的一个示例键序列是快速按Ctrl+Insert键(比如在给定的时间段内按住Ctrl键并按Insert两次)。以下是我目前代码中的内容:

var keyDownSeq = Observable.FromEventPattern<KeyEventArgs>(m_KeyboardHookManager, "KeyDown");
var keyUpSeq = Observable.FromEventPattern<KeyEventArgs>(m_KeyboardHookManager, "KeyUp");

var ctrlDown = keyDownSeq.Where(ev => ev.EventArgs.KeyCode == Keys.LControlKey).Select(_ => true);
var ctrlUp = keyUpSeq.Where(ev => ev.EventArgs.KeyCode == Keys.LControlKey).Select(_ => false);
但我认为它破坏了整个Rx方法。关于如何在保持Rx范式的同时实现这一点,有什么想法吗

然后,当按下Ctrl键时,我需要在给定的时间内捕获两次插入按压。我正在考虑使用缓冲区:

var insertUp = keyUpSeq.Where(ev => ev.EventArgs.KeyCode == Keys.Insert);
insertUp.Buffer(TimeSpan.FromSeconds(1), 2)
    .Do((buffer) => { if (buffer.Count == 2) Debug.WriteLine("happened"); })
    .Subscribe();
但我不确定这是否是最有效的方法,因为缓冲区每秒钟都会产生事件,即使并没有按下任何键。有更好的办法吗?我还需要以某种方式将其与Ctrl-down组合起来

所以,我再次需要在按住Ctrl键的同时跟踪双击。我走的方向对吗

另外,另一种可能的方法是只在按下Ctrl键时订阅Insert observable。但不知道如何实现这一目标。也许你也有一些想法


编辑:我发现的另一个问题是缓冲区不能完全满足我的需要。问题在于缓冲区每两秒钟产生一次样本,如果我的第一次按压属于第一个缓冲区,第二次按压属于下一个缓冲区,那么什么都不会发生。如何克服这个问题?

好吧,我想出了一些解决办法。它是有效的,但有一些限制,我将进一步解释。我暂时不会接受这个答案,也许其他人会提供一个更好更通用的方法来解决这个问题。无论如何,以下是当前的解决方案:

private IDisposable SetupKeySequenceListener(Keys modifierKey, Keys doubleClickKey, TimeSpan doubleClickDelay, Action<Unit> actionHandler)
{
    var keyDownSeq = Observable.FromEventPattern<KeyEventArgs>(m_KeyboardHookManager, "KeyDown");
    var keyUpSeq = Observable.FromEventPattern<KeyEventArgs>(m_KeyboardHookManager, "KeyUp");

    var modifierIsPressed = Observable
        .Merge(keyDownSeq.Where(ev => (ev.EventArgs.KeyCode | modifierKey) == modifierKey).Select(_ => true),
               keyUpSeq.Where(ev => (ev.EventArgs.KeyCode | modifierKey) == modifierKey).Select(_ => false))
        .DistinctUntilChanged()
        .Do(b => Debug.WriteLine("Ctrl is pressed: " + b.ToString()));

    var mainKeyDoublePressed = Observable
        .TimeInterval(keyDownSeq.Where(ev => (ev.EventArgs.KeyCode | doubleClickKey) == doubleClickKey))
        .Select((val) => val.Interval)
        .Scan((ti1, ti2) => ti2)
        .Do(ti => Debug.WriteLine(ti.ToString()))
        .Select(ti => ti < doubleClickDelay)
        .Merge(keyUpSeq.Where(ev => (ev.EventArgs.KeyCode | doubleClickKey) == doubleClickKey).Select(_ => false))
        .Do(b => Debug.WriteLine("Insert double pressed: " + b.ToString()));

    return Observable.CombineLatest(modifierIsPressed, mainKeyDoublePressed)
        .ObserveOn(WindowsFormsSynchronizationContext.Current)
        .Where((list) => list.All(elem => elem))
        .Select(_ => Unit.Default)
        .Do(actionHandler)
        .Subscribe();
}
让我解释一下这里发生了什么,也许对一些人会有用。我基本上设置了3个观察值,一个用于修改器键(
modifierIsPressed
),另一个用于在按下修改器激活序列时需要双击的键(
mainKeyDoublePressed
),最后一个是将两个键结合在一起的

第一个非常简单:只需将按键和释放转换为bool(使用
Select
)<需要使用code>DistinctUntilChanged,因为如果用户按住某个键,将生成多个事件。我在这个可观察对象中得到的是一系列布尔值,表示如果修改键被按下

然后是最棘手的一个,处理主键。让我们一步一步来:

  • 我正在使用
    TimeInterval
    来用时间跨度替换键关闭(这很重要)事件
  • 然后我用
    Select
    函数得到实际的时间跨度(为下一步做准备)
  • 然后是最棘手的事情,
    扫描
    。它所做的是从前面的序列(本例中为timespans)中获取每两个连续元素,并将它们作为两个参数传递到函数中。进一步传递该函数的输出(必须与参数的类型相同,时间跨度)。在我的例子中,函数的作用非常简单:只返回第二个参数 为什么??现在是时候记住我在这里的实际任务了:捕捉到在时间上彼此足够接近的按钮的两次按下(就像我的示例中的半秒钟)。我的输入是一系列的时间跨度,表示自上次事件发生以来经过了多少时间。这就是为什么我需要等待两个事件:第一个事件通常足够长,因为它会告诉用户上次按下按键的时间,可能是几分钟或更长。但是,如果用户快速按两次键,那么第二次时间间隔将很小,因为它将显示最后两次快速按下之间的差异

    听起来很复杂,对吧?然后用一种简单的方式来思考:
    Scan
    总是将两个最新事件组合在一起。这就是为什么在这种情况下它适合我的需要:我需要听双击。如果我需要连续等待3次印刷,我在这里会不知所措。这就是为什么我称这种方法为有限的,并且仍然等待是否有人会提供更好、更通用的解决方案来处理任何可能的键组合

    无论如何,让我们继续解释:

    4.
    Select(ti=>ti
    :这里我只是将序列从时间戳转换为布尔值,对于足够快的连续事件传递true,对于不够快的事件传递false

    5.还有一个技巧:我将布尔序列从第4步合并到新的一步,在新的一步中,我将听向上键事件。还记得最初的序列1是从按键关闭事件构建的吗?所以在这里,我基本上采用了与可观测数字1相同的方法:向下传递true,向上传递false

    然后,使用
    combinelateest
    函数变得非常简单,它从每个序列中提取最后一个事件,并将它们作为列表进一步传递到
    Where
    函数,该函数检查所有事件是否都为真。这就是我如何实现我的目标:现在我知道当主键被按下两次而修改键被按下时。在main key up事件中进行合并可确保清除状态,因此下次按下modifier键不会触发序列

    好了,差不多就这样了。我会发布这个,但不会接受,就像我之前说的。我希望有人能插话来启发我。:)


    提前谢谢

    首先,欢迎来到Re的大脑弯曲魔法
    private IDisposable SetupKeySequenceListener(Keys modifierKey, Keys doubleClickKey, TimeSpan doubleClickDelay, Action<Unit> actionHandler)
    {
        var keyDownSeq = Observable.FromEventPattern<KeyEventArgs>(m_KeyboardHookManager, "KeyDown");
        var keyUpSeq = Observable.FromEventPattern<KeyEventArgs>(m_KeyboardHookManager, "KeyUp");
    
        var modifierIsPressed = Observable
            .Merge(keyDownSeq.Where(ev => (ev.EventArgs.KeyCode | modifierKey) == modifierKey).Select(_ => true),
                   keyUpSeq.Where(ev => (ev.EventArgs.KeyCode | modifierKey) == modifierKey).Select(_ => false))
            .DistinctUntilChanged()
            .Do(b => Debug.WriteLine("Ctrl is pressed: " + b.ToString()));
    
        var mainKeyDoublePressed = Observable
            .TimeInterval(keyDownSeq.Where(ev => (ev.EventArgs.KeyCode | doubleClickKey) == doubleClickKey))
            .Select((val) => val.Interval)
            .Scan((ti1, ti2) => ti2)
            .Do(ti => Debug.WriteLine(ti.ToString()))
            .Select(ti => ti < doubleClickDelay)
            .Merge(keyUpSeq.Where(ev => (ev.EventArgs.KeyCode | doubleClickKey) == doubleClickKey).Select(_ => false))
            .Do(b => Debug.WriteLine("Insert double pressed: " + b.ToString()));
    
        return Observable.CombineLatest(modifierIsPressed, mainKeyDoublePressed)
            .ObserveOn(WindowsFormsSynchronizationContext.Current)
            .Where((list) => list.All(elem => elem))
            .Select(_ => Unit.Default)
            .Do(actionHandler)
            .Subscribe();
    }
    
    var subscriptionHandler = SetupKeySequenceListener(
        Keys.LControlKey | Keys.RControlKey, 
        Keys.Insert | Keys.C, 
        TimeSpan.FromSeconds(0.5),
        _ => { WindowState = FormWindowState.Normal; Show(); Debug.WriteLine("IT HAPPENED"); });
    
    using(var hook = new KeyboardHookListener(new GlobalHooker()))
    {
        hook.Enabled = true;
        var keyDownSeq = Observable.FromEventPattern<KeyEventArgs>(hook, "KeyDown");
        var keyUpSeq = Observable.FromEventPattern<KeyEventArgs>(hook, "KeyUp");    
    
        var ctrlPlus =
            // Start with a key press...
            from keyDown in keyDownSeq
    
            // and that key is the lctrl key...
            where keyDown.EventArgs.KeyCode == Keys.LControlKey
    
            from otherKeyDown in keyDownSeq
                // sample until we get a keyup of lctrl...
                .TakeUntil(keyUpSeq
                    .Where(e => e.EventArgs.KeyCode == Keys.LControlKey))
    
                // but ignore the fact we're pressing lctrl down
                .Where(e => e.EventArgs.KeyCode != Keys.LControlKey)
            select otherKeyDown;
    
        using(var sub = ctrlPlus
               .Subscribe(e => Console.WriteLine("CTRL+" + e.EventArgs.KeyCode)))
        {
            Console.ReadLine();
        }
    }
    
    var alphamabits = 
        from keyA in keyDown.Where(e => e.EventArgs.KeyCode == Keys.A)
        from keyB in keyDown.Where(e => e.EventArgs.KeyCode == Keys.B)
        from keyC in keyDown.Where(e => e.EventArgs.KeyCode == Keys.C)
        from keyD in keyDown.Where(e => e.EventArgs.KeyCode == Keys.D)
        from keyE in keyDown.Where(e => e.EventArgs.KeyCode == Keys.E)
        from keyF in keyDown.Where(e => e.EventArgs.KeyCode == Keys.F)
        select new {keyA,keyB,keyC,keyD,keyE,keyF};
    
    if A, then B, then C, then..., then F -> return one {a,b,c,d,e,f}
    
    var ctrlinsins =
        from keyDown in keyDownSeq
        where keyDown.EventArgs.KeyCode == Keys.LControlKey
        from firstIns in keyDownSeq
          // optional; abort sequence if you leggo of left ctrl
          .TakeUntil(keyUpSeq.Where(e => e.EventArgs.KeyCode == Keys.LControlKey))
          .Where(e => e.EventArgs.KeyCode == Keys.Insert)
        from secondIns in keyDownSeq
          // optional; abort sequence if you leggo of left ctrl
          .TakeUntil(keyUpSeq.Where(e => e.EventArgs.KeyCode == Keys.LControlKey))
          .Where(e => e.EventArgs.KeyCode == Keys.Insert)
        select "Dude, it happened!";