关于扫描枪对包含中文的二维码扫描的处理,请参考使用c#捕获usb扫描枪扫描二维码、条形码结果(支持中文版),本文之后的评论不再回复关于中文扫码的问题
另外,请仔细阅读本文,因为中文支持博文仅修改了本文中的一个类,其他代码沿用本文
-----------------------------------------------------------------
最近公司买了一些扫描枪,要做个展会门票扫描,门票格式为一个网址,生成方式是qr二维码
以前没玩过这东西,尝试一下看看,结果发现扫描枪根本就是个纯输入设备,和键盘一个性质
好吧,先不管这些,把扫描结果记录下来才是正经的,于是在网上搜了一下“c# winform 无焦点监听键盘输入”,然后一大堆同质化的内容,虽然包含了usb扫描枪数据捕捉,但是。。。。这tmd的全是条形码的!
好吧,根据这个咱们自己调整调整吧
先发一个类,用来支持扫描枪扫描结果获取
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace WinForm_Scaner
{
public class ScanerHook
{
public delegate void ScanerDelegate(ScanerCodes codes);
public event ScanerDelegate ScanerEvent;
delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
private int hKeyboardHook = 0;
private ScanerCodes codes = new ScanerCodes();
private HookProc hookproc;
[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", 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);
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)
{
return UnhookWindowsHookEx(hKeyboardHook);
}
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 0;
}
public class ScanerCodes
{
private int ts = 300; // 指定输入间隔为300毫秒以内时为连续输入
private List> _keys = new List>();
private List _keydown = new List(); // 保存组合键状态
private List _result = new List(); // 返回结果集
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 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());
_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());
_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)
{
// 根据键盘状态和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')
{
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;
}
}
}
基本原理与其他文章的没有什么不同,区别在于,扫描二维码时,结果不仅仅是数字,并且字母有大小写区分,还可能包含标点符号什么的
当然,我这个程序没有对8位ascii以上的字符集做支持,也就是不支持汉字什么的,需要对汉字支持的自己进行魔改好了
KeyboardHookProc 是用来捕捉键盘输入时间的,我的处理方式和网上其他文章的区别在于,每次捕捉到的内容都直接扔到数组中去,然后再触发事件返回给调用者,而扔给数组的方法就是Add方法了,这里的处理才是真正的核心
因为条形码中只有数字和部分符号,所以基本可以不用考虑键盘状态或组合键的情况,但二维码就不能不考虑了,尤其是有些验证信息还区分大小写的情况,例如二维码扫描结果是这样的内容“c=a0$nLD7_49vSF9-1&n=1”,我使用的是科密ew-9200扫描枪,因为要区分扫描枪,所以对每个扫描枪追加的后缀设置,也就是&n=1部分,前面的才是扫描结果
那么通过事件监听,发现在键盘状态为小写时,a还是a,但是$是4,L是l,也就是说,我们无法通过键盘状态来获取我们的预期结果,但是我们发现了另一个现象,那就是相邻的几个EventMsg中paramH为0时,是表示按下了对应按钮,paramH大于零时表示松开了对应按钮,那么就有一个有意思的现象了,当出现字母a时,EventMsg中message相同的两次,一次paramH=0,一次paramH=128,而出现字母L时,相关的EventMsg为四个,分别是message=160¶mH=0,message=76¶mH=0,message=76¶mH=128,message=160¶mH=129,于是就有了一个辅助记录是否组合键的变量及代码段
#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
可以根据_keydown.Count来判断当前按下了几个键,可以使用_keydown.Contains(160)判断是否按下了Shift键,然后使用ShiftChar方法来获取最终的按键对应字符
至于结果保存,我使用了一个数组List>,对每次输入都单独进行记录,大家也可以自行修改以符合自己的习惯
最后,就是事件触发了
if (ScanerEvent != null && msg.message == 13 && msg.paramH > 0 && !string.IsNullOrEmpty(codes.Result))
{
ScanerEvent(codes);
}
当触发事件有定义,且当前按键时回车键松开,且已记录的字符串非空,则触发返回事件,其实这么看起来,感觉还是不算很难
好了,类说明大概就这样了,调用方法就更简单了
public partial class Scaner : Form
{
private ScanerHook listener = new ScanerHook();
public Scaner()
{
InitializeComponent();
listener.ScanerEvent += Listener_ScanerEvent;
}
private void Listener_ScanerEvent(ScanerHook.ScanerCodes codes)
{
dgv_lst.Rows.Add(new object[] { codes.KeyDownCount, codes.Event.message, codes.Event.paramH, codes.Event.paramL, codes.CurrentChar, codes.Result, codes.isShift, codes.CurrentKey });
}
private void Scaner_Load(object sender, System.EventArgs e)
{
listener.Start();
}
private void Scaner_FormClosing(object sender, FormClosingEventArgs e)
{
listener.Stop();
}
}
直接实例化一个ScanerHook,然后Start开始监听,对ScanerEvent进行接收就可以了,至于是要保存还是要发送请求,亦或者是显示到winform里,那都不是事,自己玩去吧