WPF实现无线扫码枪无焦点自动获取数据并逻辑处理

USB接口的扫码枪基本就相当于一个电脑外设,在WINDOWS系统中,甚至可以简单到不做任何处理,就能在焦点位置获取扫码枪返回的数据。但是作为一个程序员,总是会遇到客户各种神奇的需求,比如一个比较常见的功能:客户手拿扫码枪不管扫什么,软件都能自动辨别是什么类型的条码,叭叭叭扫好几种条码的时候,软件能够区分出来各种条码,并且根据条码调出相关数据。

呐,这个需求的话,就直接把手动更改焦点位置来扫码的可能性pass了,客户没有那么智能,并且客户很懒。这样的话,就需要根据条码的内容来确定是什么条码,然后再和后台数据一起显示到软件界面上。

这个时候就确定了软件有关扫码枪方面需要实现的功能:无视焦点,后台获取扫码枪的数据,然后就是业务逻辑了。这篇文章就是介绍WPF如何实现无焦点获取扫码枪数据。

这里需要感谢一下简书上的一位大佬,他的一篇文章基本解决我的需求,但是其中很多地方讲解的不太详细,我在这里再画蛇添足一下,dalao勿怪。

现在放出几个关键点的代码,加以说明,全部代码在最后放出链接:
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

就是在这里,原文作者没有讲清楚。

第一步:寻找USB设备的ID;Win10系统在设备管理->人体学输入设备->找到USB选项的设备ID

WPF实现无线扫码枪无焦点自动获取数据并逻辑处理 第二步:在程序中确认设备ID写入,并且测试SDK

这其中涉及到App.config的编程和设备ID的解析,还有一个&符号的转义,这里我就不赘述了。

完整Demo