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

news2024/11/19 3:40:35

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 0x100
        public const int WM_KEYUP = 257;//KEYUP 0x101
        public const int WM_SYSKEYDOWN = 260;//SYSKEYDOWN 0x104
        public const int WM_SYSKEYUP = 261;//SYSKEYUP 0x105

        public const int WH_KEYBOARD = 2; 
        public const int WH_KEYBOARD_LL = 13;//0x00D

        public delegate long KeyboardProc (int code, Int16 wParam, IntPtr lParam);
 
        [StructLayout(LayoutKind.Sequential)]
        public class KeyboardHookStruct
        {
            public int vkCode;  //定一个虚拟键码。该代码必须有一个价值的范围1至254
            public 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 ms
            bool 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.coloradmin.cn/o/886412.html

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

相关文章

Jmeter数据驱动 —— csv高效用例

目录 1、设置测试用例&#xff0c;创建用例数据文件&#xff1a;testcase.csv 2、新建一个线程组&#xff0c;命名为&#xff1a;数据驱动&#xff0c;添加配置元件-HTTP请求默认值&#xff0c;配置好IP地址和端口号 3、添加逻辑控制器-循环控制器。循环控制器的作用可以控制…

IDEA中导出Javadoc遇到的GBK编码错误的解决思路和应用

IDEA中导出Javadoc遇到的GBK编码错误的解决思路和应用 ​ 当我们在导出自己写的项目的api文档的时候呢&#xff0c;有的时候会出现以下问题&#xff1a;也就是GBK编码错误不可导出 错误描述&#xff1a;编码GBK的不可映射字符无法导出&#xff0c;可以看出这是我们自己写的中文…

移动app软件安全性测试内容有哪些?专业移动app测试报告获取

移动app软件安全性测试报告是评估和验证移动应用程序在设计和开发过程中是否具有足够的安全性措施。在当今移动应用程序的高度发展和普及中&#xff0c;保护用户的个人数据和信息安全至关重要。 一、移动app软件安全性测试包括的内容 1、权限访问测试&#xff1a;测试app获取…

【BIOS】Bios设置通电即自动开机。

【问题需求】 设置电脑/服务器接通电源后&#xff0c;自动开机。 【解决方案】 不同主板的bios设置不一样&#xff0c;但方向都差不多。 在此整理一些不同准版bios的设置方法。 【通用主板Bios】 开机后连续按del键&#xff0c;进入Bios。 切换到【Advanced】菜单下&#xff…

【STM32学习】搭建一个简单的 keil5 工程

一、安装 pack 支持包 pack是支持包文件&#xff0c;当你的板子连接到电脑时&#xff0c;keil5 怎么知道你的板子是哪个型号的&#xff0c;这就需要用到 pack 文件了。Keil 官方下载pack文件的地址&#xff1a;download | device pack 我这里使用的是 STM324 系列 随后直接一…

Nginx运行Vue项目:基本运行

需求 在Nginx服务器中&#xff0c;运行Vue项目。 说明 Vue项目打包生成的生产文件&#xff0c;是无法直接在浏览器打开的。需要放到Nginx服务器中&#xff0c;才能够访问。 本文章只介绍最基本的情况&#xff1a;Nginx中运行一个Vue项目。 实际生产环境&#xff0c;一个Ng…

Qt开发实现字幕滚动效果

1、效果展示 我们经常能够在外面看到那种滚动字幕&#xff0c;那么就拿qt来做一个吧。 2、实现思路 实现一个窗口部件&#xff0c;这个窗口部件显示了一串文本标语,它会每t毫秒向左移动一个像素。如果窗口部件比文本宽,那么文本将会被多次重复,直到能够填满整个窗口部件的宽度…

设计模式之原型模式Prototype的C++实现

1、原型模式提出 在软件功能设计中&#xff0c;经常面临着“某些结构复杂的对象”的创建工作&#xff0c;且创建的对象想拥有其他对象在某一刻的状态&#xff0c;则可以使用原型模型。原型模型是通过拷贝构造函数来创建对象&#xff0c;并且该对象拥有其他对象在某一刻的状态。…

嵌入式设备应用开发(linux应用的几个场景)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 linux内核是可以裁剪的,所以除了pc电脑可以用linux之外,小的嵌入式设备其实也可以用linux。前面我们讨论了soc的各个驱动,本质上还是要把这些外设用起来。操作系统帮助我们把这些…

go语言恶意代码检测系统--对接前端可视化与算法检测部分

Malware Detect System 1 产品介绍 恶意代码检测系统。 2 产品描述 2.1 产品功能 功能点详细描述注册账号未注册用户注册成为产品用户&#xff0c;从而具备享有产品各项服务的资格登录账号用户登录产品&#xff0c;获得产品提供的各项服务上传恶意样本用户可以将上传自己的…

虹科分享 | 使用工业无线方案的包装机械运输轨道系统将会是什么样子的?

背景 包装机械制造商正面临着大规模定制和客户动态需求的新挑战。随着包装机械设计向工业4.0迈进&#xff0c;灵活性、更高的吞吐量和减少停机时间成为关键要求。柔性制造系统需要具备快速更换新产品类型、重新安排操作以及根据产量和产能的重大变化进行调整的能力。因此&…

北京开发者社区简介

标题 北京城市开发者社区简介导语简介技术交流与分享创新与创业平台城市发展与科技应用线上线下活动获益和奖励 展望未来加入我们往期活动照片 博主 默语带您 Go to New World. ✍ 个人主页—— 默语 的博客&#x1f466;&#x1f3fb; 《java 面试题大全》 &#x1f369;惟余…

【ARM】Day4 点亮LED灯

1. 思维导图 2. 自己编写代码实现三盏灯点亮 .text .global _start _start: /**********LED1&#xff0c;LED2,LED3点灯:PE10,PF10,PE8**************/ RCC_INIT:使能GPIOE组/GPIOF组控制器,通过RXCC_MP_AHB4ENSETR设置第[5:4]位写1,地址:0x50000A28[5:4]1ldr r0,0x50000A28 …

c#设计模式-结构型模式 之 桥接模式

前言 桥接模式是一种设计模式&#xff0c;它将抽象与实现分离&#xff0c;使它们可以独立变化。这种模式涉及到一个接口作为桥梁&#xff0c;使实体类的功能独立于接口实现类。这两种类型的类可以结构化改变而互不影响。 桥接模式的主要目的是通过将实现和抽象分离&#xff0c;…

kafka线上问题优化

如何防止消息丢失 生产者&#xff1a; 使用同步发送把ack设成1或者all&#xff08;非0&#xff0c;0可能会出现消息丢失的情况&#xff09;&#xff0c;并且设置同步的分区数>2 消费者&#xff1a;把自动提交改成手动提交 如何防止重复消费 在防止消息丢失的方案中&#…

软件测试人员每天的工作日常

我现在每天9点左右从家里出发&#xff0c;9点半左右到公司&#xff0c;到公司之后王豆豆首先用养生壶煮一壶好茶&#xff0c;工作忙碌时也要记得多喝水&#xff0c;然后一边听着煮茶声一边写着当天的工作计划&#xff0c;工作计划主要包括当天工作内容、学习计划和总结。 计划…

Mysql之 optimizer_trace 相关总结

Mysql之 optimizer_trace 相关总结 MySQL官网介绍&#xff1a;https://dev.mysql.com/doc/dev/mysql-server/latest/PAGE_OPT_TRACE.html 1. 简介 MySQL优化器可以生成Explain执行计划&#xff0c;通过执行计划查看sql是否使用了索引&#xff0c;使用了哪种索&#xff1b; 但…

饿了么大数据开发凉经

1 一个mapreduce进程会启动多少map进程多少reduce进程* 1&#xff09;map数量由处理的数据分成的block数量决定default_num total_size / split_size; 2&#xff09;reduce数量为job.setNumReduceTasks(x)中x 的大小。不设置的话默认为 1。 2 讲下shuffle的过程 shuffle分为…

【Python dxfgrabber+matplotlib】显示AutoCAD导出的.dxf格式文件

代码&#xff1a; import dxfgrabber,matplotlib import matplotlib.pyplot as plt from matplotlib.patches import Polygonmatplotlib.use(TkAgg)# 使用 dxfgrabber 库加载 DXF 文件 drawing dxfgrabber.readfile(files/Main board0.DXF)# 创建 Matplotlib 图形 fig, ax p…

【无线点对点网络时延分析和可视化】模拟无线点对点网络中的延迟以及物理层和数据链路层之间的相互作用(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…