在上一家公司创业初期,我接触的第一个项目是医院的供应室消毒包管理系统,几乎一人之力用了两个多月的时间,完成了基本的开发工作。回想起来,当时有个设计印象深刻。趁着还有记忆,记录下来,供大家一时之需。
当时开发的时候,对于物资的管理,自然离不开条码枪的角色。设计的是无线条码枪,让工作人员可以自由的在作业现场走动。但是有个用户体验问题,就是一般情况下,条码枪扫描的时候,都需要将输入焦点放到文本框之中,这个限制会造成极为不好的用户体验。此外,在流程设计的时候,实际上一系列操作动作,是使用扫描不同命令条码后,实现的。比如启动某功能,扫码后,确定继续下一个动作等等,全程不用碰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>
最后实现的效果还是十分不错的,只要是系统处于前台(不太确定处于后台是否好用,有点记不住了),条码枪随意扫描,一窜操作下来,根本不用动键盘或鼠标,因为所有的靠键盘鼠标触发的功能,也都是使用特定条码定义了,用户体验十分不错。可惜的是,最后这个系统因为商务原因,没有上线,虽然完成度已经很高了,但是也废弃了。
最后,扫码关键源码的下载链接