最近爲公司開發一個生產系統,其中用到掃描槍輸入條碼,結果發現手頭的掃描槍竟然是模擬鍵盤輸入將條碼數據直接發送到焦點控件中的(USB口的),好比TextBox,而因爲業務要求,不容許生產線上員工手工輸入,所以我將文本框設爲只讀,想不到掃描槍也沒法輸入了。併發
看來想經過控件的鍵盤事件去識別掃描槍輸入與鍵盤輸入是行不通的。百度了下,也沒找到好的解決方案,不過獲得了一個經過檢測按鍵間隔來識別是否爲人工輸入的思路,通過多番研究和調試,終於完成了功能,而且將該功能完美封裝在類中,實現了下降耦合的要求,並納入自定義DLL中,做爲一個通用庫的一部分。異步
基本思路爲:使用時間類型變量記錄每次按鍵發生時間,計算兩次按鍵之間的時間間隔,若是超時,則認爲是鍵盤輸入,變量初始化爲MinValue,用來區分是不是首次按鍵。間隔限定100毫秒,由於掃描儀輸入間隔很是快,以此區分。ide
類的方法負責接收發送者和發送文本,負責開啓計時器跟蹤,保存每次調用的時間,計算兩次輸入間隔,第一次輸入或出現超時則發送清空輸入事件通知,其次,計時器計算按鍵後是否超時併發送清空輸入事件。窗口程序每當發生輸入則調用類方法,並經過對象事件相應清空文本框。函數
重點:如何判斷掃描儀掃描條碼結束仍是手工按鍵間隔,僅經過2次按鍵間隔判斷沒法檢查最後一個字符,由於兩次按鍵間隔是在後一次按鍵發生時纔會被動檢查兩次按鍵間隔時間,而若是是到達了最後一個字符,後面就不會再有按鍵發生,那麼按鍵檢查就不會執行。直到下一次掃描或按鍵纔會去檢查前一次掃描狀況。所以定義了一個計時器來跟蹤每次按鍵後的超時狀況,這樣即使遇到最夠一個按鍵,沒有調用函數,計時器也會發現超時,二者結合解決問題。測試
現介紹類的定義和給出完整代碼,以及調用代碼。ui
類名:ScanningGunMonitorthis
1、依賴引用spa
using System.Timers;線程
2、類的定義調試
一、內部成員介紹
Timer _Timer
按鍵後計時器,用於監控按鍵後的時間段是否超時。
DateTime _TickTime
記錄每次按鍵的時間。
string _TempText
保存處理後的外部控件文本
object _Sender
保存外部控件源對象,用於發送事件時傳回
二、屬性介紹
int MiniLength
字符串最小長度,限定連續輸入時最小長度,以此區分人工輸入。由於人工輸入按鍵間隔很難作到保持每一個間隔都在時間間隔限定以內,也即作不到連續穩定均勻輸入。這是區分按鍵/掃描槍輸入的依據之一。
int TimeOut
輸入超時限定時間(毫秒),這是區分按鍵/掃描槍輸入的依據之二,確保每次輸入間隔在限定時間內。
int ClockTick
時鐘週期(毫秒),內部計時器參數,指定計時器按此時間週期性處理。
三、方法介紹
CheckKeyPress
檢查2次按鍵之間的時間間隔,若是第一次輸入,開啓計時器,檢查是否超時,發送清空文本的事件,中止計時器工做,或保持文本。
StopCheckGap
中止計時器跟蹤按鍵後間隔。
Timer_Elapsed
計時器事件方法,檢查按鍵後是否超時,進一步判斷是不是掃描結束仍是按鍵輸入。
四、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 }
3、類的使用
一、定義文本框事件
private void txtProductBarCode_KeyPress(object sender, KeyPressEventArgs e) { if (!ScanerMonitor.CheckKeyPress(txtProductBarCode, txtProductBarCode.Text)) { e.KeyChar = '\0'; } }
調用對象方法,傳遞當前文本框和文本內容,若是返回false,將當前按鍵值清除。
二、定義掃描槍監控對象
ScanningGunMonitor ScanerMonitor = new ScanningGunMonitor();
三、定義事件響應
定義事件方法
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項目中測試過。