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.pswp.cn/news/39556.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/39556.shtml
英文地址,請注明出處:http://en.pswp.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;…