1.扫描枪获取数据原理基本相当于键盘数据,获取扫描枪扫描出来的数据,一般分为两种实现方式。
a)文本框输入获取焦点,扫描后自动显示在文本框内。
b)使用键盘钩子,勾取扫描枪虚拟按键,根据按键频率进行手动输入和扫描枪扫描判断。
2.要实现系统钩子其实很简单,调用三个Win32的API即可。
SetWindowsHookEx 用于设置钩子。(设立一道卡子,盘查需要的信息)
CallNextHookEx 用于传递钩子(消息是重要的,所以从哪里来,就应该回到哪里去,除非你决定要封锁消息)
UnhookWindowsHookEx 卸载钩子(卸载很重要,卡子设多了会造成拥堵)
版本一:
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Diagnostics;
namespace SaomiaoTest2
{
/// <summary>
/// 获取键盘输入或者USB扫描枪数据 可以是没有焦点 应为使用的是全局钩子
/// USB扫描枪 是模拟键盘按下
/// 这里主要处理扫描枪的值,手动输入的值不太好处理
/// </summary>
public class BardCodeHooK
{
public delegate void BardCodeDeletegate(BarCodes barCode);
public event BardCodeDeletegate BarCodeEvent;
//定义成静态,这样不会抛出回收异常
private static HookProc hookproc;
public struct BarCodes
{
public int VirtKey;//虚拟吗
public int ScanCode;//扫描码
public string KeyName;//键名
public uint Ascll;//Ascll
public char Chr;//字符
public string BarCode;//条码信息 保存最终的条码
public bool IsValid;//条码是否有效
public DateTime Time;//扫描时间,
}
private struct EventMsg
{
public int message;
public int paramL;
public int paramH;
public int Time;
public int hwnd;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern bool UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);
[DllImport("user32", EntryPoint = "GetKeyNameText")]
private static extern int GetKeyNameText(int IParam, StringBuilder lpBuffer, int nSize);
[DllImport("user32", EntryPoint = "GetKeyboardState")]
private static extern int GetKeyboardState(byte[] pbKeyState);
[DllImport("user32", EntryPoint = "ToAscii")]
private static extern bool ToAscii(int VirtualKey, int ScanCode, byte[] lpKeySate, ref uint lpChar, int uFlags);
[DllImport("kernel32.dll")]
public static extern IntPtr GetModuleHandle(string name);
delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
BarCodes barCode = new BarCodes();
int hKeyboardHook = 0;
string strBarCode = "";
private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
{
if (nCode == 0)
{
EventMsg msg = (EventMsg)Marshal.PtrToStructure(lParam, typeof(EventMsg));
if (wParam == 0x100)//WM_KEYDOWN=0x100
{
barCode.VirtKey = msg.message & 0xff;//虚拟吗
barCode.ScanCode = msg.paramL & 0xff;//扫描码
StringBuilder strKeyName = new StringBuilder(225);
if (GetKeyNameText(barCode.ScanCode * 65536, strKeyName, 255) > 0)
{
barCode.KeyName = strKeyName.ToString().Trim(new char[] { ' ', '\0' });
}
else
{
barCode.KeyName = "";
}
byte[] kbArray = new byte[256];
uint uKey = 0;
GetKeyboardState(kbArray);
if (ToAscii(barCode.VirtKey, barCode.ScanCode, kbArray, ref uKey, 0))
{
barCode.Ascll = uKey;
barCode.Chr = Convert.ToChar(uKey);
}
TimeSpan ts = DateTime.Now.Subtract(barCode.Time);
if (ts.TotalMilliseconds > 50)
{//时间戳,大于50 毫秒表示手动输入
strBarCode = barCode.Chr.ToString();
}
else
{
if ((msg.message & 0xff) == 13 && strBarCode.Length > 3)
{//回车
barCode.BarCode = strBarCode;
barCode.IsValid = true;
}
strBarCode += barCode.Chr.ToString();
}
barCode.Time = DateTime.Now;
if (BarCodeEvent != null)
BarCodeEvent(barCode);//触发事件
barCode.IsValid = false;
}
}
return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}
//安装钩子
public bool Start()
{
if (hKeyboardHook == 0)
{
hookproc = new HookProc(KeyboardHookProc);
//GetModuleHandle 函数 替代 Marshal.GetHINSTANCE
//防止在 framework4.0中 注册钩子不成功
IntPtr modulePtr = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
//WH_KEYBOARD_LL=13
//全局钩子 WH_KEYBOARD_LL
// hKeyboardHook = SetWindowsHookEx(13, hookproc, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);
hKeyboardHook = SetWindowsHookEx(13, hookproc, modulePtr, 0);
}
return (hKeyboardHook != 0);
}
//卸载钩子
public bool Stop()
{
if (hKeyboardHook != 0)
{
return UnhookWindowsHookEx(hKeyboardHook);
}
return true;
}
}
}
在实践过程中,发现版本一的代码只能扫描条形码,如伴随二维码中的字母出现就不能正确获取数据。
版本二:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace BarcodeScanner
{
public class ScanerHook
{
public delegate void ScanerDelegate(ScanerCodes codes);
public event ScanerDelegate ScanerEvent;
//private const int WM_KEYDOWN = 0x100;//KEYDOWN //private const int WM_KEYUP = 0x101;//KEYUP //private const int WM_SYSKEYDOWN = 0x104;//SYSKEYDOWN //private const int WM_SYSKEYUP = 0x105;//SYSKEYUP
//private static int HookProc(int nCode, Int32 wParam, IntPtr lParam);
private int hKeyboardHook = 0;//声明键盘钩子处理的初始值
private ScanerCodes codes = new ScanerCodes();//13为键盘钩子
//定义成静态,这样不会抛出回收异常
private static HookProc hookproc;
delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] //设置钩子
private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] //卸载钩子
private static extern bool UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] //继续下个钩子
private static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);
[DllImport("user32", EntryPoint = "GetKeyNameText")]
private static extern int GetKeyNameText(int IParam, StringBuilder lpBuffer, int nSize);
[DllImport("user32", EntryPoint = "GetKeyboardState")] //获取按键的状态
private static extern int GetKeyboardState(byte[] pbKeyState);
[DllImport("user32", EntryPoint = "ToAscii")] //ToAscii职能的转换指定的虚拟键码和键盘状态的相应字符或字符
private static extern bool ToAscii(int VirtualKey, int ScanCode, byte[] lpKeySate, ref uint lpChar, int uFlags);
//int VirtualKey //[in] 指定虚拟关键代码进行翻译。 //int uScanCode, // [in] 指定的硬件扫描码的关键须翻译成英文。高阶位的这个值设定的关键,如果是(不压) //byte[] lpbKeyState, // [in] 指针,以256字节数组,包含当前键盘的状态。每个元素(字节)的数组包含状态的一个关键。如果高阶位的字节是一套,关键是下跌(按下)。在低比特,如/果设置表明,关键是对切换。在此功能,只有肘位的CAPS LOCK键是相关的。在切换状态的NUM个锁和滚动锁定键被忽略。 //byte[] lpwTransKey, // [out] 指针的缓冲区收到翻译字符或字符。 //uint fuState); // [in] Specifies whether a menu is active. This parameter must be 1 if a menu is active, or 0 otherwise.
[DllImport("kernel32.dll")] //使用WINDOWS API函数代替获取当前实例的函数,防止钩子失效
public static extern IntPtr GetModuleHandle(string name);
public ScanerHook()
{
}
public bool Start()
{
if (hKeyboardHook == 0)
{
hookproc = new HookProc(KeyboardHookProc);
//GetModuleHandle 函数 替代 Marshal.GetHINSTANCE
//防止在 framework4.0中 注册钩子不成功
IntPtr modulePtr = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
//WH_KEYBOARD_LL=13
//全局钩子 WH_KEYBOARD_LL
// hKeyboardHook = SetWindowsHookEx(13, hookproc, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);
hKeyboardHook = SetWindowsHookEx(13, hookproc, modulePtr, 0);
}
return (hKeyboardHook != 0);
}
public bool Stop()
{
if (hKeyboardHook != 0)
{
bool retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
hKeyboardHook = 0;
return retKeyboard;
}
return true;
}
private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
{
EventMsg msg = (EventMsg)Marshal.PtrToStructure(lParam, typeof(EventMsg));
codes.Add(msg);
if (ScanerEvent != null && msg.message == 13 && msg.paramH > 0 && !string.IsNullOrEmpty(codes.Result))
{
ScanerEvent(codes);
}
return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}
public class ScanerCodes
{
private int ts = 100; // 指定输入间隔为300毫秒以内时为连续输入
private List<List<EventMsg>> _keys = new List<List<EventMsg>>();
private List<int> _keydown = new List<int>(); // 保存组合键状态
private List<string> _result = new List<string>(); // 返回结果集
private DateTime _last = DateTime.Now;
private byte[] _state = new byte[256];
private string _key = string.Empty;
private string _cur = string.Empty;
public EventMsg Event
{
get
{
if (_keys.Count == 0)
{
return new EventMsg();
}
else
{
return _keys[_keys.Count - 1][_keys[_keys.Count - 1].Count - 1];
}
}
}
public List<int> KeyDowns
{
get
{
return _keydown;
}
}
public DateTime LastInput
{
get
{
return _last;
}
}
public byte[] KeyboardState
{
get
{
return _state;
}
}
public int KeyDownCount
{
get
{
return _keydown.Count;
}
}
public string Result
{
get
{
if (_result.Count > 0)
{
return _result[_result.Count - 1].Trim();
}
else
{
return null;
}
}
}
public string CurrentKey
{
get
{
return _key;
}
}
public string CurrentChar
{
get
{
return _cur;
}
}
public bool isShift
{
get
{
return _keydown.Contains(160);
}
}
public void Add(EventMsg msg)
{
#region 记录按键信息
// 首次按下按键
if (_keys.Count == 0)
{
_keys.Add(new List<EventMsg>());
_keys[0].Add(msg);
_result.Add(string.Empty);
}
// 未释放其他按键时按下按键
else if (_keydown.Count > 0)
{
_keys[_keys.Count - 1].Add(msg);
}
// 单位时间内按下按键
else if (((TimeSpan)(DateTime.Now - _last)).TotalMilliseconds < ts)
{
_keys[_keys.Count - 1].Add(msg);
}
// 从新记录输入内容
else
{
_keys.Add(new List<EventMsg>());
_keys[_keys.Count - 1].Add(msg);
_result.Add(string.Empty);
}
#endregion
_last = DateTime.Now;
#region 获取键盘状态
// 记录正在按下的按键
if (msg.paramH == 0 && !_keydown.Contains(msg.message))
{
_keydown.Add(msg.message);
}
// 清除已松开的按键
if (msg.paramH > 0 && _keydown.Contains(msg.message))
{
_keydown.Remove(msg.message);
}
#endregion
#region 计算按键信息
int v = msg.message & 0xff;
int c = msg.paramL & 0xff;
StringBuilder strKeyName = new StringBuilder(500);
if (GetKeyNameText(c * 65536, strKeyName, 255) > 0)
{
_key = strKeyName.ToString().Trim(new char[] { ' ', '\0' });
GetKeyboardState(_state);
if (_key.Length == 1 && msg.paramH == 0)// && msg.paramH == 0
{
// 根据键盘状态和shift缓存判断输出字符
_cur = ShiftChar(_key, isShift, _state).ToString();
_result[_result.Count - 1] += _cur;
} // 备选 else
{
_cur = string.Empty;
}
}
#endregion
}
private char ShiftChar(string k, bool isShiftDown, byte[] state)
{
bool capslock = state[0x14] == 1;
bool numlock = state[0x90] == 1;
bool scrolllock = state[0x91] == 1;
bool shiftdown = state[0xa0] == 1;
char chr = (capslock ? k.ToUpper() : k.ToLower()).ToCharArray()[0];
if (isShiftDown)
{
if (chr >= 'a' && chr <= 'z')
{
chr = (char)((int)chr - 32);
}
else if (chr >= 'A' && chr <= 'Z')
{
if (chr=='Z')
{
string s = "";
}
chr = (char)((int)chr + 32);
}
else
{
string s = "`1234567890-=[];',./";
string u = "~!@#$%^&*()_+{}:\"<>?";
if (s.IndexOf(chr) >= 0)
{
return (u.ToCharArray())[s.IndexOf(chr)];
}
}
}
return chr;
}
}
public struct EventMsg
{
public int message;
public int paramL;
public int paramH;
public int Time;
public int hwnd;
}
}
}
版本二中的代码,实践中发现出现了获取扫描数据却省略“+”加号的情况出现。
因此在版本二中备选处添加
//判断是+ 强制添加+
else if (_key.Length == 5 && msg.paramH == 0&&msg.paramL==78&&msg.message==107)// && msg.paramH == 0
{
// 根据键盘状态和shift缓存判断输出字符
_cur = Convert.ToChar('+').ToString();
_result[_result.Count - 1] += _cur;
}
3.winform在无焦点情况下的使用方式:
using BarcodeScanner;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace BarCodeTest
{
public partial class Form1 : Form
{
private ScanerHook listener = new ScanerHook();
public Form1()
{
InitializeComponent();
listener.ScanerEvent += Listener_ScanerEvent;
}
private void Listener_ScanerEvent(ScanerHook.ScanerCodes codes)
{
textBox3.Text = codes.Result;
}
private void Form1_Load(object sender, EventArgs e)
{
listener.Start();
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
listener.Stop();
}
}
}
BardCodeHooK的使用方法:
定义全局 BardCodeHooK BarCode = new BardCodeHooK();
挂载事件:BarCode.BarCodeEvent += new BardCodeHooK.BardCodeDeletegate(BarCode_BarCodeEvent);
开始方法: BarCode.Start();
结束方法:BarCode.Stop();
事件具体内容:
public void BarCode_BarCodeEvent(BardCodeHooK.BarCodes barCode)
{
if (barCode.BarCode!=null)
{
IDCardInfos model = new IDCardInfos();
model.CardNo = barCode.BarCode;
model.CardType = EIDCardType.白名单操作;
ListAll.Add(model);
// if (CBZhuangtai.SelectedIndex == (int)EIDCardType.白名单操作)
//{
BindingDGV(EIDCardType.白名单操作);
//}
}
}
转载地址: https://www.cnblogs.com/TBW-Superhero/p/8659306.html
当然在使用过程中,发现在不用扫码枪的情况下,使用读卡器,则必须最后一个键是回车键。也就 ascII码是十进制 13 或者 十六进制 0D ,这样才能标注结束符号,然后数据才可正确扫码完成。