C# WPF 无焦点自动获取USB 二维码扫码枪内容,包含中文

C# WPF 无焦点自动获取USB 二维码扫码枪内容,包含中文

  • 前言
    • 项目背景
  • 需要预知的知识
  • 实现方案
    • 第一步 安装键盘钩子
    • 第二步 获取输入的值
    • 第3 步 解决中文乱码
      • 问题分析
      • 解决思路
      • 工具函数
  • 结束

前言

USB接口的扫码枪基本就相当于一个电脑外设,等同于一个快速输入键盘。现如令二维码的使用相当流行。那我们开发程序必须要跟上节奏,使用上二维码,扫码器有很多通信接口,其中USB 接口更通用。

项目背景

本人自从业以来,一直从业计量行业相关的系统开发,主要开发无人值守智能称重系统。在称重系统里主要用到的关键主流是车牌识别抽像机,红外光栅,道闸,语音等设备。称重系统通常是以前端客户端运行,有些大客户有自己的综合平台,要求称重系统能接入平台,内部的业务控制权由客户的平台系统管理。称重系统作为一个子系统,只要按平台系统的要求工作。客户要求,称重系统能够离线运行。这导致不得使用物理设备来传输转载一些必须信息,以前主要的解决方案是 IC 卡,现在有了二维码,并且二维码的信息量比 IC 卡大很多,读写方便,Ic 的读写都需要硬件投入和软件对接,使用起来不方便,不经济。因此 二维码 是最优的选择,使用手机App 或者小程序动态生成二维码,USB接口的扫码设备,即插即用,不需要依赖具体那个品牌,随时可以更换,非常方便。所有我们面对客户的新需求采用二维码的解决方案。

需要预知的知识

  • 托管代码与非托管代码的交互
  • Win32 APi 安装钩子来监听系统的行为(键盘输入)
  • 编码的转换 (最重要,二维码中有中文内容)

实现方案

USB扫码枪为即插即用,通过类似键盘的方式和系统进行交互,扫描出来的数据获取方式有两种实现方式。

(1)文本框输入获取焦点,扫描后自动显示在文本框内。

(2)使用键盘钩子,勾取扫描枪虚拟按键,进行键盘虚拟码和ASCII码的转换后获取数据。

在无人值守的场景下,第一种方试 Pass掉,在程序进行开发时,一般使用第二种方式,有以下两个优势,当我们的程序最小化,被隐藏,失去焦点的情况下有新的扫码事件时也能够快速响应,并进行自动工作中,下面在接收USB扫码枪扫描数据方面的问题进行探讨分享。

第一步 安装键盘钩子

安装键盘钩子,需要用到 Win32 API ,代码如下 :

public const int WM_KEYDOWN = 256;//KEYDOWN 0x100public const int WM_KEYUP = 257;//KEYUP 0x101public const int WM_SYSKEYDOWN = 260;//SYSKEYDOWN 0x104public const int WM_SYSKEYUP = 261;//SYSKEYUP 0x105public const int WH_KEYBOARD = 2; public const int WH_KEYBOARD_LL = 13;//0x00Dpublic delegate long KeyboardProc (int code, Int16 wParam, IntPtr lParam);[StructLayout(LayoutKind.Sequential)]public class KeyboardHookStruct{public int vkCode;  //定一个虚拟键码。该代码必须有一个价值的范围1至254public int scanCode; // 指定的硬件扫描码的关键public int flags;  // 键标志public int time; // 指定的时间戳记的这个讯息public int dwExtraInfo; // 指定额外信息相关的信息}//idHook 钩子类型,即确定钩子监听何种消息,上面的代码中设为2,即监听键盘消息并且是线程钩子,如果是全局钩子监听键盘消息应设为13,//线程钩子监听鼠标消息设为7,全局钩子监听鼠标消息设为14。lpfn 钩子子程的地址指针。如果dwThreadId参数为0 或是一个由别的进程创建的//线程的标识,lpfn必须指向DLL中的钩子子程。 除此以外,lpfn可以指向当前进程的一段钩子子程代码。钩子函数的入口地址,当钩子钩到任何//消息后便调用这个函数。hInstance应用程序实例的句柄。标识包含lpfn所指的子程的DLL。如果threadId 标识当前进程创建的一个线程,而且子//程代码位于当前进程,hInstance必须为NULL。可以很简单的设定其为本应用程序的实例句柄。threaded 与安装的钩子子程相关联的线程的标识符//如果为0,钩子线程与所有的线程关联,即为全局钩子//使用此功能,安装了一个钩子[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]public static extern int SetWindowsHookEx(int idHook, KeyboardProc lpfn, IntPtr hInstance, int threadId);//调用此函数卸载钩子[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]public static extern bool UnhookWindowsHookEx(int idHook);

代码只列出部分钩子类型,详情更多 参见:

SetWindowsHookExA 函数 参数 idHook类型:

在WPF 项目中应用 代码如下


// 在窗体显示后注册 钩子private void Window_ContentRendered(object sender, EventArgs e){this.mainNotifyIcon.Visibility = Visibility.Visible;InitTime();//注册 钩子RegistHook();}#region hook /// <summary>/// 安装钩子成功后的 ID/// </summary>private int mHookID;private Win32Helper.KeyboardProc KeyboardHookDelegate;private void RegistHook(){           KeyboardHookDelegate = new Win32Helper.KeyboardProc(KeyboardProc);IntPtr intPtr = Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0]);mHookID =  Win32Helper.SetWindowsHookEx(Win32Helper.WH_KEYBOARD_LL, KeyboardHookDelegate, intPtr, 0);}public long KeyboardProc(int nCode, Int16 wParam, IntPtr lParam){// 侦听键盘事件if (nCode >= 0){if(wParam == Win32Helper.WM_KEYUP){//获取键盘钩子的结构体var mstruct = (Win32Helper.KeyboardHookStruct)Marshal.PtrToStructure(lParam,  typeof(Win32Helper.KeyboardHookStruct));// 其它处理//notify main window                     OnQrScanerInput(keyData,mstruct.time);}}//如果返回1,则结束消息,这个消息到此为止,不再传递。//如果返回0或调用CallNextHookEx函数则消息出了这个钩子继续往下传递,也就是传给消息真正的接受者return Win32Helper.CallNextHookEx(mHookID , nCode, wParam, lParam);}#endregion

第二步 获取输入的值

获取键盘的ASCII 码

        /// <summary>/// keyboard input value or qrcode scanner value/// </summary>private string qrValue =string.Empty;private int LastInputTime = 0;/// <summary>///  keyboard has input/// </summary>private void OnQrScanerInput(Keys key,int timestamp){//time interval 20 msbool ishandleing = false;int maxInputInterval = 20;        if (LastInputTime == 0) LastInputTime = timestamp;int ms = timestamp - LastInputTime;Console.WriteLine("ms:" + ms);LastInputTime = timestamp;if (ms  > maxInputInterval){qrValue = String.Empty;  return;}if (key == Keys.Back || key == Keys.Apps|| key == Keys.Cancel|| key == Keys.Tab || key == Keys.LShiftKey ){return;}            if(key == Keys.Decimal || key == Keys.OemPeriod){qrValue += ".";}else if (key == Keys.Oem5){qrValue += "|";}else if (key == Keys.OemMinus){qrValue += "_";}else if (key == Keys.Enter){// not do nothing}else{//Console.WriteLine(key + ":" + (int)key);var temp = Convert.ToChar((int)key).ToString();qrValue += temp;}}

上述代码中 var temp = Convert.ToChar((int)key).ToString(); qrValue += temp; 最终得到输入的内容,同时也处理了一些特殊按键,如下

  • key == Keys.Back || key == Keys.Apps || key == Keys.Cancel|| key == Keys.Tab || key == Keys.LShiftKey ,不需要拼接,因为真实使用不会有这些
  • Keys.Oem5 “|”
  • Keys.OemMinus - 或’’ ,"" 要判断上一个输入的键 是否为 左右 Shift
  • key == Keys.Decimal || key == Keys.OemPeriod 小数点

结果

在这里插入图片描述在上面的载图中我们能看到 有乱码,下面我们就来解决它

第3 步 解决中文乱码

问题分析

  • 程序够获取 输入的键 是否铵 左右Shift,当无法适应输入法去转化,所以处理中是个很麻烦的事情。
  • 二维码内容格式 单号|车牌号|类型|重量
  • 二维码内容值例:XS_YL_20230816011232001|云DDD732|2|12.32

要生成二维码的内容 :XS_YL_20230816011232001|云DDD732|2|12.32
云DDD732 为车牌号,是一定有存在中文的。

解决思路

解决思路: 就是把车牌号 这个段的内容进行编号的转换
字符串 -> bytes 数组 -> 转换拼接成HEX 进制的字符串 -> 生成 二维码

![转换后的值](https://img-blog.csdnimg.cn/15bcbf88b95741dc8cd95ae2e8a73e84.png#pic_center)这样是不是就不需要处理 中文 拉。

解码路径
hex 字符串-> bytes 数组 -> 字符串

   			var data = qrContent.Split('|');if (data.Length < 4){Growl.Info("无效的二维码: "+data.Length);return;}// 转换 车牌号currCarNumber = ICReaderHelper.StringToStr(data[1].Replace(" ",""));if (!string.IsNullOrEmpty(currCarNumber)){var msg = "已识别,请稍候。";CommonFunction.SpeakAsync(msg, true);}     

工具函数

一 、十六进制代表的字符串转换成 普通字符串,程序可识别的

  		/// <summary>/// 返回十六进制代表的字符串 空格有和没都 可以/// </summary>/// <param name="hexStr"></param>/// <returns></returns>public static string StringToStr(string hexStr){if (string.IsNullOrEmpty(hexStr)){return "";}if (hexStr.Contains(" ")){hexStr = hexStr.Replace(" ", "");}if (hexStr.Length <= 0) return "";byte[] vBytes = new byte[hexStr.Length / 2];for (int i = 0; i < hexStr.Length; i += 2)if (!byte.TryParse(hexStr.Substring(i, 2), NumberStyles.HexNumber, null, out vBytes[i / 2])){vBytes[i / 2] = 0;}return Encoding.GetEncoding("GB2312").GetString(vBytes);}

二 、将字符转化成十六进制的字符串,

/// <summary>/// 将字符转化成十六进制的字符串/// </summary>/// <param name="strValue"></param>/// <param name="hasSpace">是否含有空格</param>/// <returns></returns>public static string StringToHex(string strValue, bool hasSpace = true){return BitConverter.ToString(ASCIIEncoding.GetEncoding("GB2312").GetBytes(strValue)).Replace("-", hasSpace == true ? " " : "");}
  • 我采用的编码规范 GB2312 ,只要编号和解码使用一个规范就行
  • 代码并不全面,关键代码已经在文章中,其它代码不影响本文要讨论的问题

结束

非常感谢您的耐心阅读,不足之处,欢迎批评指出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/39556.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Oracle Data Redaction与Data Pump

如果表定义了Redaction Policy&#xff0c;导出时数据会脱敏吗&#xff1f;本文解答这个问题。 按照Oracle文档Advanced Security Guide第13章&#xff0c;13.6.5的Tutorial&#xff0c;假设表HR.jobs定义了Redaction Policy。 假设HR用户被授予了访问目录对象的权限&#xf…

Unity引擎使用InteriorCubeMap采样制作假室内效果

Unity引擎制作假室内效果 大家好&#xff0c;我是阿赵。   这次来介绍一种使用CubeMap做假室内效果的方式。这种技术名叫InteriorCubeMap&#xff0c;是UE引擎自带的节点效果。我这里是在Unity引擎里面的实现。 一、效果展示 这个假室内效果&#xff0c;要动态看才能看出效…

柏睿向量数据库Rapids VectorDB赋能企业级大模型构建及智能应用

ChatGPT的问世,在为沉寂已久的人工智能重新注入活力的同时,也把长期默默无闻的向量数据库推上舞台。今年4月以来,全球已有4家知名向量数据库公司先后获得融资,更加印证了向量数据库在AI大模型时代的价值。 什么是向量数据库? 在认识向量数据库前,先来了解一下最常见的关…

【业务功能篇62】Spring boot maven多模块打包时子模块报错问题

程序包 com.xxx.common.utils不存在或者xxx找不到符号 我们项目中一般都是会分成多个module模块&#xff0c;做到解耦&#xff0c;方便后续做微服务拆分模块&#xff0c;可以直接就每个模块进行打包拎出来执行部署这样就会有模块之间的调用&#xff0c;比如API模块会被Service…

【SpringBoot】SpringBoot获取不到用户真实IP怎么办

文章目录 前言问题原因解决方案修改Nginx配置文件SpringBoot代码实现 前言 项目部署后发现服务端无法获取到客户端真实的IP地址&#xff0c;这是怎么回事呢&#xff1f;给我都整懵逼了&#xff0c;经过短暂的思考&#xff0c;我发现了问题的真凶&#xff0c;那就是我们使用了N…

Vue基础

Vue基础 Vue应用 <!DOCTYPE html> <html> <head><meta charset"utf-8"><title></title><!-- 开发环境版本 --><script src"https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head&g…

vue所有UI库通用)tree-select 下拉多选(设置 maxTagPlaceholder 隐藏 tag 时显示的内容,支持鼠标悬浮展示更多

如果可以实现记得点赞分享&#xff0c;谢谢老铁&#xff5e; 1.需求描述 引用的下拉树形结构支持多选&#xff0c;限制选中tag的个数&#xff0c;且超过制定个数&#xff0c;鼠标悬浮展示更多已选中。 2.先看下效果图 3.实现思路 首先根据API文档&#xff0c;先设置maxTagC…

【Docker】Docker network之bridge、host、none、container以及自定义网络的详细讲解

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;陈童学哦&#xff0c;目前学习C/C、算法、Python、Java等方向&#xff0c;一个正在慢慢前行的普通人。 &#x1f3c0;系列专栏&#xff1a;陈童学的日记 &#x1f4a1;其他专栏&#xff1a;CSTL&…

TCP/IP网络江湖初探:物理层的奥秘与传承(物理层上篇-基础与本质)

〇、引言 在这个数字时代,计算机网络如同广袤的江湖,数据在其中畅游,信息传递成为了生活的常态。然而,在这个充满虚拟奇观的网络江湖中,隐藏着一个不容忽视的存在,那就是物理层,这个江湖的基石。就如同江湖中的土地一样,物理层作为计算机网络的基础,承载着数据的最初转…

STM32 CubeMX (uart_IAP串口)简单示例

STM32 CubeMX STM32 CubeMX &#xff08;串口IAP&#xff09; STM32 CubeMXIAP有什么用&#xff1f;整体思路 一、STM32 CubeMX 设置时钟树UART使能UART初始化设置 二、代码部分文件移植![在这里插入图片描述](https://img-blog.csdnimg.cn/0c4841d8328b4169a8833f15fe3d670c.p…

PHP Smarty中的缓存如何实现?

欢迎来到PHP Smarty的缓存世界&#xff01;这里是一个简单的指南&#xff0c;帮助你理解如何在这个强大的模板引擎中启用和配置缓存。 首先&#xff0c;让我们先了解一下什么是缓存。简单来说&#xff0c;缓存就是将需要花费大量时间处理的数据或资源存储起来&#xff0c;以便…

2023/8/16总结

这几天完成了私信的功能点&#xff0c;用websocket做的。 这是大概的界面&#xff0c;参考的是微信 用户可以搜索好友&#xff1a; 如果不存在是下面这样&#xff0c;存在就会在左边的聊天里面显示有这个人选项 发送消息 接下来需要把推荐算法给做了

文件IO编程 1 2

头文件包含路径 linux 操作系统分为两大空间&#xff1a;用户空间和内核空间 这样划分&#xff0c;是为了保护内核的核心组件&#xff0c;不被轻易访问和修改 系统调用&#xff1a;安全的访问内核空间 其核心是&#xff1a;函数API&#xff08;API&#xff1a;用户编程接口&…

svn文章五:问题排查与修复 - 出了问题怎么办?SVN故障排除与修复指南

文章五&#xff1a;问题排查与修复 - “出了问题怎么办&#xff1f;SVN故障排除与修复指南” 概述&#xff1a;在使用SVN时&#xff0c;难免会遇到一些问题和错误。在这篇文章中&#xff0c;我们将教您如何进行故障排查和修复&#xff0c;保护您的SVN仓库和数据安全。 1. 引言…

K8S系列文章之 Docker安装使用Kafka

通过Docker拉取镜像的方式进行安装 照例先去DockerHub找一下镜像源&#xff0c;看下官方提供的基本操作&#xff08;大部分时候官方教程比网上的要清晰一些&#xff0c;并且大部分教程可能也是翻译的官方的操作步骤&#xff0c;所以直接看官方的就行&#xff09; 老实说Kafka…

“深入剖析JVM内部原理:解密Java虚拟机的奥秘“

标题&#xff1a;深入剖析JVM内部原理&#xff1a;解密Java虚拟机的奥秘 摘要&#xff1a;本文将深入探讨Java虚拟机&#xff08;JVM&#xff09;的内部原理&#xff0c;包括其架构、内存管理、垃圾回收机制、即时编译器等关键组成部分。通过解密JVM的奥秘&#xff0c;我们将更…

【Vue3】Vue3 UI 框架 | Element Plus —— 创建并优化表单

安装 # NPM $ npm install element-plus --save // 或者&#xff08;下载慢切换国内镜像&#xff09; $ npm install element-plus -S// 可以选择性安装 less npm install less less-loader -D // 可以选择性配置 自动联想src目录Element Plus 的引入和注入 main.ts import…

解决VSCode CPU高占问题的方法

如果你也遇到VSCode的CPU占用过高的问题&#xff0c;可以尝试使用官方自带的插件Bisect&#xff08;扩展二分查找&#xff09;功能来查找具体是哪个扩展出了问题。 找到“糟糕”的扩展可能很容易&#xff0c;也可能很困难。 打开扩展视图 ( CtrlShiftX )&#xff0c;禁用扩展&…

网络:杂记

1. 完成链路认证后&#xff0c;STA会继续发起链路服务协商&#xff0c;具体的协商是通过Association报文实现。 2. RSTP可以提高收敛速度的原因&#xff1a; RSTP的拓扑变化机制 Proposal/Agreement机制 根端口快速切换机制 边缘端口的引入

git cherry-pick

cherry-pick命令的基本用法 对于多分支的代码库&#xff0c;将代码从一个分支转移到另一个分支是常见需求。这时分两种情况。一种情况是&#xff0c;你需要另一个分支的所有代码变动&#xff0c;那么就采用合并&#xff08; git merge &#xff09;。另一种情况是&#xff0c;…