最近為公司開發一個生產系統,其中用到掃描槍輸入條碼,結果發現手頭的掃描槍居然是模擬鍵盤輸入將條碼數據直接發送到焦點控件中的(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的類代碼
/// <summary> /// 掃描槍鍵盤輸入檢測類 /// </summary> public class ScanningGunMonitor { #region 內部成員 Timer _Timer = new Timer();//計時器 DateTime _TickTime=DateTime.MinValue;//記錄前一次按鍵周期的時間 string _TempText=string.Empty;//控件文本副本 object _Sender;//保存外部控件源,用于發送事件時傳回 #endregion #region 事件 /// <summary> /// 輸入超時委托 /// </summary> /// <param name="Sender">來源</param> public delegate void InputTimeOut(object Sender); /// <summary> /// 輸入超時事件委托對象 /// </summary> public event InputTimeOut OnInputTimeOut; /// <summary> /// 發送輸入超時事件 /// </summary> private void SendInputTimeOutEvent() { if (OnInputTimeOut != null) OnInputTimeOut(_Sender); } #endregion #region 構造函數 /// <summary> /// 構造函數 /// </summary> public ScanningGunMonitor() { _Timer.Elapsed+=Timer_Elapsed;//內置事件對象綁定觸發事件方法 } #endregion #region 屬性 #region 條碼最小長度 int _MiniLength = 20; /// <summary> /// 讀取或設置條碼最小長度值,當超過一個時鐘周期后,如果條碼文本不符合最小長度則被丟棄。 /// </summary> public int MiniLength { get { return _MiniLength; } set { _MiniLength = value; } } #endregion #region 按鍵間隔超時限定值 int _TimeOut=100; /// <summary> /// 讀取或設置按鍵間隔超時限定值 /// </summary> public int TimeOut { get { return _TimeOut; } set { _TimeOut = value; } } #endregion #region 時鐘周期 int _ClockTick = 100; /// <summary> /// 讀取或設置內置時鐘周期 /// </summary> public int ClockTick { get { return _ClockTick; } set { _ClockTick = value; } } #endregion #endregion #region 方法 /// <summary> /// 當發生按鍵時檢查條碼文本是否超時(開啟內置時鐘) /// </summary> /// <param name="sender">發送控件</param> /// <param name="inputText">條碼文本</param> /// <returns>有效按鍵標志</returns> public bool CheckKeyPress(object sender,string inputText) { int gap; _Sender = sender; DateTime thisTime = DateTime.Now; gap = thisTime.Subtract(_TickTime).Milliseconds; if (_TickTime == DateTime.MinValue)//第一次 { _Timer.Interval = _ClockTick; _Timer.Enabled = true;//開啟時鐘 SendInputTimeOutEvent();//發送輸入超時事件 return true;//保留當前輸入字符 } else { if (gap > _TimeOut) { StopCheckGap();//停止檢查輸入間隔 _TempText = "";//清空本地文本 SendInputTimeOutEvent();//發送輸入超時事件 return false; //通知取消當前輸入 } } _TickTime = thisTime;//保存時間現場,用于下一周期判斷依據 _TempText = inputText;//保存文本,提供時鐘事件判斷依據 return true;//保留當前輸入 } /// <summary> /// 停止檢測時間間隔,重置狀態,停止內置時鐘 /// </summary> private void StopCheckGap() { _TickTime= DateTime.MinValue; _Timer.Enabled = false; } /// <summary> /// 時鐘事件方法 /// </summary> /// <param name="sender">發送者</param> /// <param name="e">事件對象</param> private void Timer_Elapsed(object sender,ElapsedEventArgs e) { int gap = e.SignalTime.Subtract(_TickTime).Milliseconds; if (gap > _TimeOut) { StopCheckGap();//停止檢測,可能是掃描槍掃描結束,也可能是手工輸入間隔 if (_TempText.Length < _MiniLength)//進一步檢查長度,如果較短,說明為手工輸入 { _TempText = "";//清空本地文本 SendInputTimeOutEvent();//發送輸入超時事件 } } } #endregion }
三、類的使用
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項目中測試過。

浙公網安備 33010602011771號