区分扫描枪输入和键盘输入的实现(转)
最近为公司开发一个生产系统,其中用到扫描枪输入条码,结果发现手头的扫描枪居然是模拟键盘输入将条码数据直接发送到焦点控件中的(USB口的),比如TextBox,而由于业务要求,不允许生产线上员工手工输入,因此我将文本框设为只读,想不到扫描枪也无法输入了。
看来想通过控件的键盘事件去识别扫描枪输入与键盘输入是行不通的。百度了下,也没找到好的解决方案,不过得到了一个通过检测按键间隔来识别是否为人工输入的思路,经过多番研究和调试,终于完成了功能,并且将该功能完美封装在类中,实现了降低耦合的要求,并归入自定义DLL中,作为一个通用库的一部分。
基本思路为:使用时间类型变量记录每次按键发生时间,计算两次按键之间的时间间隔,如果超时,则认为是键盘输入,变量初始化为MinValue,用来区分是否是首次按键。间隔限定100毫秒,因为扫描仪输入间隔非常快,以此区分。
类的方法负责接收发送者和发送文本,负责开启计时器跟踪,保存每次调用的时间,计算两次输入间隔,第一次输入或出现超时则发送清空输入事件通知,其次,计时器计算按键后是否超时并发送清空输入事件。窗口程序每当发生输入则调用类方法,并通过对象事件相应清空文本框。
重点:如何判断扫描仪扫描条码结束还是手工按键间隔,仅通过2次按键间隔判断无法检查最后一个字符,因为两次按键间隔是在后一次按键发生时才会被动检查两次按键间隔时间,而如果是到达了最后一个字符,后面就不会再有按键发生,那么按键检查就不会执行。直到下一次扫描或按键才会去检查前一次扫描情况。因此定义了一个计时器来跟踪每次按键后的超时情况,这样即便遇到最够一个按键,没有调用函数,计时器也会发现超时,两者结合解决问题。
现介绍类的定义和给出完整代码,以及调用代码。
类名:ScanningGunMonitor
一、依赖引用
using System.Timers;
二、类的定义
1、内部成员介绍
Timer _Timer
按键后计时器,用于监控按键后的时间段是否超时。
DateTime _TickTime
记录每次按键的时间。
string _TempText
保存处理后的外部控件文本
object _Sender
保存外部控件源对象,用于发送事件时传回
2、属性介绍
int MiniLength
字符串最小长度,限定连续输入时最小长度,以此区分人工输入。因为人工输入按键间隔很难做到保持每个间隔都在时间间隔限定之内,也即做不到连续稳定均匀输入。这是区分按键/扫描枪输入的依据之一。
int TimeOut
输入超时限定时间(毫秒),这是区分按键/扫描枪输入的依据之二,确保每次输入间隔在限定时间内。
int ClockTick
时钟周期(毫秒),内部计时器参数,指定计时器按此时间周期性处理。
3、方法介绍
CheckKeyPress
检查2次按键之间的时间间隔,如果第一次输入,开启计时器,检查是否超时,发送清空文本的事件,停止计时器工作,或保持文本。
StopCheckGap
停止计时器跟踪按键后间隔。
Timer_Elapsed
计时器事件方法,检查按键后是否超时,进一步判断是否是扫描结束还是按键输入。
4、ScanningGunMonitor的类代码
三、类的使用
1、定义文本框事件
private void txtProductBarCode_KeyPress(object sender, KeyPressEventArgs e) { if (!ScanerMonitor.CheckKeyPress(txtProductBarCode, txtProductBarCode.Text)) { e.KeyChar = '\0'; } }
调用对象方法,传递当前文本框和文本内容,如果返回false,将当前按键值清除。
2、定义扫描枪监控对象
ScanningGunMonitor ScanerMonitor = new ScanningGunMonitor();
3、定义事件响应
定义事件方法
ScanerMonitor.OnInputTimeOut+= new ScanningGunMonitor.InputTimeOut(ScanerMonitor_OnInputTimeOut);
这里要说明下,可能我将类封装在DLL,所以与主窗口不在同一线程,因此事件触发时总是出现线程不可访问的错误,不得已做了下处理,增加了一个委托并在事件方法内进行了改造。
定义线程委托
private delegate void OnThreadInput(object sender);
定义具有跨线程的事件相应
private void ScanerMonitor_OnInputTimeOut(object sender) { if (InvokeRequired) { OnThreadInput mydelgate = new OnThreadInput(ScanerMonitor_OnInputTimeOut); //异步的委托 this.Invoke(mydelgate, new object[] { sender }); return; } TextBox box = (TextBox)sender; box.Text = ""; }
通过多次调试改进,该类可以接收任何控件源,类不负责清空或处理控件,而是通过事件让调用者自己处理,这样可以降低耦合,不过目前还没有在WEB项目中测试过。