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



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

与其编写一堆KeyDown代码,反应式扩展似乎非常适合这种工作。 我想做什么:

  • 按下逗号时,开始按住按键(不要让屏幕上的任何控件处理它们)
  • 收集所有键,直到按下另一个逗号或 250 毫秒过去。
  • 如果在 250 毫秒内未按逗号,请将按键按回任何处于活动状态的控件。
  • 如果按逗号,则对从条形码扫描的字符串值进行处理。

如何使用 System.Reactive 在匹配条形码扫描仪的前缀和后缀时按住按键,处理后缀是否匹配,但在时间限制内后缀不匹配时正常处理按键?

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

首先,此示例不演示如何对窗体上的键执行此操作。那里有足够的额外复杂性,值得先尝试自己,或者问另一个问题。它需要一个完整的应用程序来回答您在这里问题的各个方面。可以说:

  1. 您可以将 KeyEventArgs 转换为可在此框架中使用的可观察量
  2. 您可以决定使用抑制键来抑制进入条形码流的KeyEventArgs
  3. 您可以决定不禁止显示非条形码流中的任何KeyEventArgs

目前的解决方案涉及定义一个函数,该函数接受IObservable<char>并将其转换为标记(带有bool)底层字符是否在"条形码"流(true)或"false"(false)内的GroupedObservable

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. 检查缓冲区两端是否包含逗号(标记为条形码)或不包含逗号(标记为无条形码)

然后是一个完整的使用示例:

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,就像我上面所做的那样。

让我们把它一块一块地拆开。

首先,您需要按键作为可观察量:

        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为您提供缓冲按键的最终流,您最终可以在其中输入程序逻辑。在这里,我只是将列表打印为字符串以跟踪。

相关内容

  • 没有找到相关文章

最新更新