使用反应式扩展来处理条形码阅读器的按键操作

时间:2012-07-03 20:04:30

标签: c# winforms system.reactive

我的条形码阅读器已编程为添加前缀和后缀“,”,否则就像键盘一样工作。我有一个Windows窗体,在扫描条形码时会打开。

Reactive Extensions不是编写一堆KeyDown代码,而是适合这种工作。我想做什么:

  • 按下逗号后,开始按住按键(不要让它们由屏幕上的任何控件处理)
  • 收集所有密钥,直到按下另一个逗号或已经过了250毫秒。
  • 如果在250毫秒内没有按下逗号,请将按键按回任何有效的控件。
  • 如果按下逗号,请对条形码中扫描的字符串值进行处理。

如何在匹配条形码扫描器的前缀和后缀时使用System.Reactive来保持按键,如果匹配后缀则进行处理,但是如果后缀在时间限制内不匹配,则处理键正常按下?

2 个答案:

答案 0 :(得分:1)

有人会来,可能会给出比这更优雅的答案。但我发现这个具有挑战性。

首先,这个例子没有说明如何为表单上的键做这件事。它有足够的额外复杂性,它首先尝试自己,或提出另一个问题。这需要一个完整的应用程序来回答您问题的所有方面。我只想说:

  1. 您可以将KeyEventArgs转换为可在此框架中使用的可观察对象
  2. 您可以决定取消使用SuppressKeyPress
  3. KeyEventArgs进入条形码流
  4. 您可以决定不禁止非条形码流中的任何KeyEventArgs
  5. 现有解决方案涉及定义一个函数,该函数接受IObservable<char>并将其转换为GroupedObservable,标记(使用bool)底层字符是否在&#内39;条形码&#39; stream(true)与否(false):

    public IObservable<IGroupedObservable<bool, IObservable<char>>> GroupBySurroundingChars(IObservable<char> source, char ends, TimeSpan within)
    {
        var result = 
            source.Buffer(() => 
                source.Select(c => {
                    if (c == ends) return source.Where(x => x == ends).Amb(Observable.Timer(within).Select(_ => default(char)));
                    else           return Observable.Return(default(char));
                }).Concat())
                .GroupBy(buffer => buffer.Count > 2 && buffer[0] == ends && buffer.Last() == ends, buffer => buffer.ToObservable());                        
    
        return result;
    }
    

    这个功能的作用是:

    1. 使用每个字符启动缓冲区
    2. 如果该字符不是逗号,请立即返回缓冲区
    3. 如果该字符是逗号,请将缓冲区保持打开状态,直到找到另一个逗号或已经过了250毫秒
    4. 检查缓冲区两端是否包含逗号(标记为条形码)或不标记为条形码
    5. 然后是一个完整的用法示例:

      var keys1 = "xxx,1234567,xxx,1,xxx".ToCharArray().ToObservable(Scheduler.ThreadPool).Do(_ => Thread.Sleep(100)).Publish().RefCount();
      var keys2 = "xxx,1234567,xxx,1,xxx".ToCharArray().ToObservable(Scheduler.ThreadPool).Do(_ => Thread.Sleep(10)).Publish().RefCount();
      
      var result = GroupBySurroundingChars(keys1, ',', TimeSpan.FromMilliseconds(250));
      
      var barcodes = result.Where(x => x.Key);
      var others = result.Where(x => !x.Key);
      
      barcodes.Subscribe(groups => groups.Subscribe(x => x.ToList().Dump()));
      

      如果您使用keys1,则按下按键的速度太慢,无法找到第一个条形码,但会找到第二个条形码。如果您使用keys2,则按下按键的速度足以找到两个条形码。

      在任何一种情况下,others流都包含最终未标记为包含条形码的所有密钥。 barcodes流可以作为逐个字符流的字符,或者像我上面那样使用每组条形码转换为List

答案 1 :(得分:1)

让我们把它分开。

首先,您需要按键作为Observable:

        var keys =
        Observable.FromEventPattern<KeyEventHandler, KeyEventArgs>(
                a => this.KeyDown += a,
                a => this.KeyDown -= a
            ).Select(ea => ea.EventArgs)
            .Publish();

        var unsubscription = keys.Connect();

根据收到按键,您有条件描述缓冲:

        Func<KeyEventArgs, bool> isDelimiter =
            k => k.KeyCode == Keys.Oemcomma;

现在,只要满足keys.Where(isDelimiter)

的缓冲条件,我们就会收到通知

我们需要在遇到分隔符时关闭缓冲区或者已经过了一段时间直到没有给出输入:

Observable.Amb(keys.Where(isDelimiter), keys.Throttle(TimeSpan.FromMilliseconds(2000))

将这些放在一起,我们可以创建在这些条件下出现的字符窗口:

        var windows = 
            keys.Window(keys.Where(isDelimiter),
                        first => Observable.Amb(
                                    keys.Where(isDelimiter), 
                                    keys.Throttle(TimeSpan.FromMilliseconds(2000)
                                )
                                .Where(_ => isDelimiter(first))));

现在您只需要保持缓冲直到窗口关闭,并尝试阻止其余控件在缓冲时接收密钥:

        windows
            .SelectMany(window => window
                                        .Do(ka => ka.SuppressKeyPress = true)
                                        .Buffer(() => Observable.Never<KeyEventArgs>())
                                        )
            .Subscribe(buf => Trace.WriteLine(new string(buf.Select(ka => (char)ka.KeyValue).ToArray())));

SelectMany为您提供缓冲按键的最终流,您最终可以在其中输入程序逻辑。在这里,我只是将列表打印为Trace字符串。