我把我的条形码阅读器编程为添加前缀和后缀",",否则就像键盘一样工作。我有一个 Windows 窗体,扫描条形码时将打开该窗体。
与其编写一堆KeyDown代码,反应式扩展似乎非常适合这种工作。 我想做什么:
- 按下逗号时,开始按住按键(不要让屏幕上的任何控件处理它们)
- 收集所有键,直到按下另一个逗号或 250 毫秒过去。
- 如果在 250 毫秒内未按逗号,请将按键按回任何处于活动状态的控件。
- 如果按逗号,则对从条形码扫描的字符串值进行处理。
如何使用 System.Reactive 在匹配条形码扫描仪的前缀和后缀时按住按键,处理后缀是否匹配,但在时间限制内后缀不匹配时正常处理按键?
有人会来,可能会给出比这更优雅的答案。但我发现这个具有挑战性。
首先,此示例不演示如何对窗体上的键执行此操作。那里有足够的额外复杂性,值得先尝试自己,或者问另一个问题。它需要一个完整的应用程序来回答您在这里问题的各个方面。可以说:
- 您可以将 KeyEventArgs 转换为可在此框架中使用的可观察量
- 您可以决定使用抑制键来抑制进入条形码流的
KeyEventArgs
- 您可以决定不禁止显示非条形码流中的任何
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;
}
此函数的作用是:
- 用每个字符开始缓冲区
- 如果该字符不是逗号,请立即返回缓冲区
- 如果该字符是逗号,请保持缓冲区打开状态,直到找到另一个逗号或 250 毫秒过去
- 检查缓冲区两端是否包含逗号(标记为条形码)或不包含逗号(标记为无条形码)
然后是一个完整的使用示例:
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
为您提供缓冲按键的最终流,您最终可以在其中输入程序逻辑。在这里,我只是将列表打印为字符串以跟踪。