????在上一家公司創業初期,我接觸的第一個項目是醫院的供應室消毒包管理系統,幾乎一人之力用了兩個多月的時間,完成了基本的開發工作。回想起來,當時有個設計印象深刻。趁著還有記憶,記錄下來,供大家一時之需。
當時開發的時候,對于物資的管理,自然離不開條碼槍的角色。設計的是無線條碼槍,讓工作人員可以自由的在作業現場走動。但是有個用戶體驗問題,就是一般情況下,條碼槍掃描的時候,都需要將輸入焦點放到文本框之中,這個限制會造成極為不好的用戶體驗。此外,在流程設計的時候,實際上一系列操作動作,是使用掃描不同命令條碼后,實現的。比如啟動某功能,掃碼后,確定繼續下一個動作等等,全程不用碰PC機。
所以問題的核心就在于是否可以接觸焦點必須放到文本框之中的限制。
此方法并不是原創,但是原始代碼是對WinForm平臺進行開發的,被我修改為支持WPF平臺。并且原文的鏈接已經失效,所以這段代碼還是很有價值的。
現在放出幾個關鍵點的代碼,加以說明,全部代碼在最后放出鏈接:
1.掃描監聽器BarcodeScannerListener
使用WindowInteropHelper獲取傳入窗體的句柄,并且綁定ThreadFilterMessage事件,達到從而可以觸發ProcessRawInputMessage方法
/// <summary>
/// 將監聽器附著到窗體上
/// </summary>
/// <param name="form">需要附著的窗體(WPF)</param>
public void Attach(Window form)
{
var helper = new WindowInteropHelper(form);
IntPtr hwnd = helper.Handle;
form.KeyDown += (sender, args) =>
{
if (_ControlHandled)
{
args.Handled = true;
_ControlHandled = false;
}
};
DoAttach(hwnd);
}
/// <summary>
/// 監聽綁定
/// </summary>
/// <param name="hwnd">設備指針</param>
private void DoAttach(IntPtr hwnd)
{
this.keystrokeBuffer = new StringBuilder();
this.InitializeBarcodeScannerDeviceHandles();
this.interopHelper.HookRawInput(hwnd);
//this.HookHandleEvents(form);
//this.AssignHandle(ptr);
this.filter = new BarcodeScannerKeyDownMessageFilter();
ComponentDispatcher.ThreadFilterMessage -= ComponentDispatcher_ThreadFilterMessage;
ComponentDispatcher.ThreadFilterMessage += ComponentDispatcher_ThreadFilterMessage;
//Application.AddMessageFilter(this.filter);
}
ProcessRawInputMessage方法中,判斷傳入的字符串是否是掃碼槍設置的結束字符(掃碼的字符串是一個一個傳入的),如果不是,就加入到Buffer中,如果是,則觸發FireBarcodeScanned方法
/// <summary>
/// 處理WM_INPUT消息
/// </summary>
/// <param name="rawInputHeader">rawInputHeader的指針</param>
/// <returns>按鍵是否被處理</returns>
private bool ProcessRawInputMessage(IntPtr rawInputHeader)
{
BarcodeScannerDeviceInfo deviceInfo;
bool handled;
bool keystroke;
string localBuffer;
IntPtr rawInputDeviceHandle;
handled = false;
keystroke = false;
localBuffer = string.Empty;
rawInputDeviceHandle = IntPtr.Zero;
this.interopHelper.GetRawInputInfo(
rawInputHeader,
ref rawInputDeviceHandle,
ref keystroke,
ref localBuffer);
if (this.devices.TryGetValue(rawInputDeviceHandle, out deviceInfo) && keystroke)
{
handled = true;
// 這里判斷的是Tab按鍵,可以更換為其他按鍵
if (localBuffer.Length == 1 && (localBuffer[0] == 0x09 || localBuffer[0] == '\t'))
{
this.FireBarcodeScanned(deviceInfo);
}
else
{
this.keystrokeBuffer.Append(localBuffer);
}
}
return handled;
}
FireBarcodeScanned方法中,則會調用界面初始化時,綁定的事件,傳入掃碼的字符串
/// <summary>
/// 觸發掃碼事件
/// </summary>
/// <param name="deviceInfo">掃碼設備信息</param>
private void FireBarcodeScanned(BarcodeScannerDeviceInfo deviceInfo)
{
string barcode;
EventHandler handler;
barcode = this.keystrokeBuffer.ToString();
if (barcode.Length > 0)
{
handler = this.BarcodeScanned;
this.keystrokeBuffer = new StringBuilder();
if (handler != null)
{
handler(this, new BarcodeScannedEventArgs(barcode, deviceInfo));
}
}
}
2.頁面調用
這里我使用的MVVM模式,所以在ViewModel層調用,但是只要能拿到View的對象,在那一層都沒有關系
BarcodeScannerListener = new BarcodeScannerListener();
BarcodeScannerListener.Attach((Window)GetView());
BarcodeScannerListener.BarcodeScanned += OnBarcodeScanned;
在傳入的事件中,獲取Barcode屬性即可得到掃描的值
private void OnBarcodeScanned(object sender, EventArgs e)
{
string barcode = ((BarcodeScannedEventArgs)e).Barcode;
DoBarcodeScanned(barcode);
}
3.配置條碼槍的硬件ID
需要在windows設備管理器中,找到掃碼槍的設備ID,我這沒有圖上網找了一個
將這個設備ID保存在App.cofig中,可以是多個
<!--在上面先配置一下-->
<configSections>
<section name="barcodeScanner" type="Huitai.Cssd.Common.Util.Barcode.BarcodeScannerListenerConfigurationSection, Huitai.Cssd.Common" />
</configSections>
<barcodeScanner>
<hardwareIds>
<add id="HID#VID_05FE&PID_1010" />
</hardwareIds>
</barcodeScanner>
最后實現的效果還是十分不錯的,只要是系統處于前臺(不太確定處于后臺是否好用,有點記不住了),條碼槍隨意掃描,一竄操作下來,根本不用動鍵盤或鼠標,因為所有的靠鍵盤鼠標觸發的功能,也都是使用特定條碼定義了,用戶體驗十分不錯。可惜的是,最后這個系統因為商務原因,沒有上線,雖然完成度已經很高了,但是也廢棄了。
最后,掃碼關鍵源碼的下載鏈接