Winforms клавиша и сканер штрих-кода
Мне удалось успешно использовать сканер штрих-кода в моем проекте WPF с помощью перехвата клавиатуры следующим образом (я пропускаю некоторые детали, но в основном я могу рассчитывать на то, что знаю, какая клавиатура является моим сканером).
/// <summary>
/// Add this KeyboardHook to a window
/// </summary>
/// <param name="window">The window to add to</param>
public void AddHook(Window window) {
if (form == null)
throw new ArgumentNullException("window");
if (mHwndSource != null)
throw new InvalidOperationException("Hook already present");
WindowInteropHelper w = new WindowInteropHelper(window);
IntPtr hwnd = w.Handle;
mHwndSource = HwndSource.FromHwnd(hwnd);
if (mHwndSource == null)
throw new ApplicationException("Failed to receive window source");
mHwndSource.AddHook(WndProc);
RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[1];
rid[0].usUsagePage = 0x01;
rid[0].usUsage = 0x06;
rid[0].dwFlags = RIDEV_INPUTSINK;
rid[0].hwndTarget = hwnd;
if (!RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0])))
throw new ApplicationException("Failed to register raw input device(s).");
}
Затем этот подход обрабатывает сообщения WM_INPUT для извлечения информации о любых событиях клавиатуры и соответственно обрабатывает событие, если оно поступает от сканера штрих-кода, который уже известен.
Теперь дело в том, что в Winforms я не должен использовать хуки, а переопределять WndProc, как указано здесь, но я как-то изо всех сил пытаюсь понять, как я могу использовать WndProc, поскольку мне нужно знать:
а) какое событие мне действительно нужно обработать в методе WndProc
б) как я могу определить устройство, которое запустило событие
Любая помощь будет очень ценится! Ура!
2 ответа
Я закончил, используя следующий подход:
public class BarcodeScannedEventArgs : EventArgs {
public BarcodeScannedEventArgs(string text) {
mText = text;
}
public string ScannedText { get { return mText; } }
private readonly string mText;
}
public class BarCodeListener : IDisposable {
DateTime _lastKeystroke = new DateTime(0);
string _barcode = string.Empty;
Form _form;
bool isKeyPreview;
public bool ProcessCmdKey(ref Message msg, Keys keyData) {
bool res = processKey(keyData);
return keyData == Keys.Enter ? res : false;
}
protected bool processKey(Keys key) {
// check timing (>7 keystrokes within 50 ms ending with "return" char)
TimeSpan elapsed = (DateTime.Now - _lastKeystroke);
if (elapsed.TotalMilliseconds > 50) {
_barcode = string.Empty;
}
// record keystroke & timestamp -- do NOT add return at the end of the barcode line
if (key != Keys.Enter) {
_barcode += (char)key;
}
_lastKeystroke = DateTime.Now;
// process barcode only if the return char is entered and the entered barcode is at least 7 digits long.
// This is a "magical" rule working well for EAN-13 and EAN-8, which both have at least 8 digits...
if (key == Keys.Enter && _barcode.Length > 7) {
if (BarCodeScanned != null) {
BarCodeScanned(_form, new BarcodeScannedEventArgs(_barcode));
}
_barcode = string.Empty;
return true;
}
return false;
}
public event EventHandler<BarcodeScannedEventArgs> BarCodeScanned;
public BarCodeListener(Form f) {
_form = f;
isKeyPreview = f.KeyPreview;
// --- set preview and register event...
f.KeyPreview = true;
}
public void Dispose() {
if (_form != null) {
_form.KeyPreview = isKeyPreview;
//_form.KeyPress -= KeyPress_scanner_preview;
}
}
}
}
Затем добавьте следующие строки кода в форму, которая прослушивает ваш сканер:
private BarCodeListener ScannerListener;
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) {
bool res = false;
if (ScannerListener != null) {
res = ScannerListener.ProcessCmdKey(ref msg, keyData);
}
res = keyData == Keys.Enter ? res : base.ProcessCmdKey(ref msg, keyData);
return res;
}
Я публикую здесь фрагмент кода, который я использовал некоторое время, прежде чем использовать вышеуказанную версию. Это получено отсюда, и я не перепроверял. Приходит как есть, надеюсь, это поможет в любом случае...
public BarCodeListener(IntPtr handle) {
SetDeviceHook(handle);
}
public void SetDeviceHook(IntPtr handle) {
if (string.IsNullOrWhiteSpace(LibConst.ScannerDeviceName)) {
return;
}
var hook = new KeyboardHook();
var availableScanners = KeyboardHook.GetKeyboardDevices();
foreach (string s in availableScanners) {
// you need to figure out the device name and put it here!!!!
if (s.Contains("VID_0C2E&PID_0200")) {
hook.SetDeviceFilter(s);
hook.KeyPressed += OnBarcodeKeyWpf;
hook.AddHook(handle);
}
}
}
string InputText;
void OnBarcodeKey(object sender, KeyPressEventArgs e) {
if (this.isInitialisationChar(e.KeyChar.ToString())) {
InputText = String.Empty;
}
else if (this.isTerminationChar(e.KeyChar.ToString())) {
if ((BarCodeScanned != null) && (!string.IsNullOrEmpty(InputText))) {
BarCodeScanned(this, new BarcodeScannedEventArgs(InputText));
InputText = String.Empty;
}
}
else {
InputText += e.KeyChar.ToString();
}
}
void OnBarcodeKeyWpf(object sender, KeyPressedEventArgs e) {
if (this.isInitialisationChar(e.Text)){
InputText = String.Empty;
}
else if (this.isTerminationChar(e.Text)){
if ((BarCodeScanned != null) && (!string.IsNullOrEmpty(InputText))) {
BarCodeScanned(this, new BarcodeScannedEventArgs(InputText));
InputText = String.Empty;
}
}
else{
InputText += e.Text;
}
}
bool isInitialisationChar(string s) {
return string.IsNullOrEmpty(s);
}
bool isTerminationChar(string s) {
return ((s == "\r") || string.IsNullOrEmpty(s));
}
}
public class KeyPressedEventArgs : EventArgs
{
public KeyPressedEventArgs(string text) {
mText = text;
}
public string Text { get { return mText; } }
private readonly string mText;
}
public partial class KeyboardHook
: IDisposable
{
private static readonly Regex DeviceNamePattern = new Regex(@"#([^#]+)");
public event EventHandler<KeyPressedEventArgs> KeyPressed;
/// <summary>
/// Set the device to use in keyboard hook
/// </summary>
/// <param name="deviceId">Name of device</param>
/// <returns>true if device is found</returns>
public bool SetDeviceFilter(string deviceId) {
Dictionary<string, IntPtr> devices = FindAllKeyboardDevices();
return devices.TryGetValue(deviceId, out mHookDeviceId);
}
/// <summary>
/// Add this KeyboardHook to a window
/// </summary>
/// <param name="window">The window to add to</param>
public void AddHook(BaseForm form) {
if (form == null)
throw new ArgumentNullException("window");
if (mHwndSource != null)
throw new InvalidOperationException("Hook already present");
//--- NEG: wpf code:
//WindowInteropHelper w = new WindowInteropHelper(form);
//IntPtr hwnd = w.Handle;
//---
IntPtr hwnd = form.Handle;
mHwndSource = HwndSource.FromHwnd(hwnd);
if (mHwndSource == null)
throw new ApplicationException("Failed to receive window source");
mHwndSource.AddHook(WndProc);
RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[1];
rid[0].usUsagePage = 0x01;
rid[0].usUsage = 0x06;
rid[0].dwFlags = RIDEV_INPUTSINK;
rid[0].hwndTarget = hwnd;
if (!RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0])))
throw new ApplicationException("Failed to register raw input device(s).");
}
/// <summary>
/// Add this KeyboardHook to a window
/// </summary>
/// <param name="window">The window to add to</param>
public void AddHook(IntPtr handle) {
if (handle == null)
throw new ArgumentNullException("window");
if (mHwndSource != null)
throw new InvalidOperationException("Hook already present");
//--- NEG: wpf code:
//WindowInteropHelper w = new WindowInteropHelper(form);
//IntPtr hwnd = w.Handle;
//---
IntPtr hwnd = handle;
mHwndSource = HwndSource.FromHwnd(hwnd);
if (mHwndSource == null)
throw new ApplicationException("Failed to receive window source");
mHwndSource.AddHook(WndProc);
RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[1];
rid[0].usUsagePage = 0x01;
rid[0].usUsage = 0x06;
rid[0].dwFlags = RIDEV_INPUTSINK;
rid[0].hwndTarget = hwnd;
if (!RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0])))
throw new ApplicationException("Failed to register raw input device(s).");
}
/// <summary>
/// Remove this keyboard hook from window (if it is added)
/// </summary>
public void RemoveHook() {
if (mHwndSource == null)
return; // not an error
RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[1];
rid[0].usUsagePage = 0x01;
rid[0].usUsage = 0x06;
rid[0].dwFlags = 0x00000001;
rid[0].hwndTarget = IntPtr.Zero;
RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0]));
mHwndSource.RemoveHook(WndProc);
mHwndSource.Dispose();
mHwndSource = null;
}
public void Dispose() {
RemoveHook();
}
private IntPtr mHookDeviceId;
private HwndSource mHwndSource;
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
switch (msg) {
case WM_INPUT:
if (ProcessInputCommand(mHookDeviceId, lParam)) {
MSG message;
PeekMessage(out message, IntPtr.Zero, WM_KEYDOWN, WM_KEYDOWN, PM_REMOVE);
}
break;
}
return IntPtr.Zero;
}
/// <summary>
/// Get a list of keyboard devices available
/// </summary>
/// <returns>Collection of devices available</returns>
public static ICollection<string> GetKeyboardDevices() {
return FindAllKeyboardDevices().Keys;
}
private static Dictionary<string, IntPtr> FindAllKeyboardDevices() {
Dictionary<string, IntPtr> deviceNames = new Dictionary<string, IntPtr>();
uint deviceCount = 0;
int dwSize = (Marshal.SizeOf(typeof(RAWINPUTDEVICELIST)));
if (GetRawInputDeviceList(IntPtr.Zero, ref deviceCount, (uint)dwSize) == 0) {
IntPtr pRawInputDeviceList = Marshal.AllocHGlobal((int)(dwSize*deviceCount));
try {
GetRawInputDeviceList(pRawInputDeviceList, ref deviceCount, (uint)dwSize);
for (int i = 0; i < deviceCount; i++) {
uint pcbSize = 0;
var rid = (RAWINPUTDEVICELIST)Marshal.PtrToStructure(
new IntPtr((pRawInputDeviceList.ToInt32() + (dwSize*i))),
typeof(RAWINPUTDEVICELIST));
GetRawInputDeviceInfo(rid.hDevice, RIDI_DEVICENAME, IntPtr.Zero, ref pcbSize);
if (pcbSize > 0) {
IntPtr pData = Marshal.AllocHGlobal((int)pcbSize);
try {
GetRawInputDeviceInfo(rid.hDevice, RIDI_DEVICENAME, pData, ref pcbSize);
string deviceName = Marshal.PtrToStringAnsi(pData);
// The list will include the "root" keyboard and mouse devices
// which appear to be the remote access devices used by Terminal
// Services or the Remote Desktop - we're not interested in these
// so the following code with drop into the next loop iteration
if (deviceName.ToUpper().Contains("ROOT"))
continue;
// If the device is identified as a keyboard or HID device,
// Check if it is the one we're looking for
if (rid.dwType == RIM_TYPEKEYBOARD || rid.dwType == RIM_TYPEHID) {
Match match = DeviceNamePattern.Match(deviceName);
if (match.Success)
deviceNames.Add(match.Groups[1].Value, rid.hDevice);
}
}
finally {
Marshal.FreeHGlobal(pData);
}
}
}
}
finally {
Marshal.FreeHGlobal(pRawInputDeviceList);
}
}
return deviceNames;
}
/// <summary>
/// Processes WM_INPUT messages to retrieve information about any
/// keyboard events that occur.
/// </summary>
/// <param name="deviceId">Device to process</param>
/// <param name="lParam">The WM_INPUT message to process.</param>
private bool ProcessInputCommand(IntPtr deviceId, IntPtr lParam) {
uint dwSize = 0;
try {
// First call to GetRawInputData sets the value of dwSize
// dwSize can then be used to allocate the appropriate amount of memory,
// storing the pointer in "buffer".
GetRawInputData(lParam, RID_INPUT, IntPtr.Zero,ref dwSize, (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER)));
IntPtr buffer = Marshal.AllocHGlobal((int)dwSize);
try {
// Check that buffer points to something, and if so,
// call GetRawInputData again to fill the allocated memory
// with information about the input
if (buffer != IntPtr.Zero &&
GetRawInputData(lParam, RID_INPUT, buffer, ref dwSize, (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER))) == dwSize) {
// Store the message information in "raw", then check
// that the input comes from a keyboard device before
// processing it to raise an appropriate KeyPressed event.
RAWINPUT raw = (RAWINPUT)Marshal.PtrToStructure(buffer, typeof(RAWINPUT));
if (raw.header.hDevice != deviceId)
return false;
if (raw.header.dwType != RIM_TYPEKEYBOARD)
return false;
if (raw.keyboard.Message != WM_KEYDOWN && raw.keyboard.Message != WM_SYSKEYDOWN)
return false;
// On most keyboards, "extended" keys such as the arrow or page
// keys return two codes - the key's own code, and an "extended key" flag, which
// translates to 255. This flag isn't useful to us, so it can be
// disregarded.
if (raw.keyboard.VKey > VK_LAST_KEY)
return false;
if (KeyPressed != null) {
string scannedText = null;
lock (mLocalBuffer) {
if (GetKeyboardState(mKeyboardState)) {
if (ToUnicode(raw.keyboard.VKey, raw.keyboard.MakeCode, mKeyboardState, mLocalBuffer, 64, 0) > 0) {
if (mLocalBuffer.Length > 0) {
scannedText = mLocalBuffer.ToString();
}
}
}
}
if (!string.IsNullOrEmpty(scannedText))
KeyPressed(this, new KeyPressedEventArgs(scannedText));
}
return true;
}
}
finally {
Marshal.FreeHGlobal(buffer);
}
}
catch (Exception e) {
throw new AppException(SCL_Languages.getValue("internalerror"), e.Message, e);
}
return false;
}
private static readonly StringBuilder mLocalBuffer = new StringBuilder(64);
private static readonly byte[] mKeyboardState = new byte[256];
}
public partial class KeyboardHook
{
private const int RIDEV_INPUTSINK = 0x00000100;
private const int RIDEV_REMOVE = 0x00000001;
private const int RID_INPUT = 0x10000003;
private const int FAPPCOMMAND_MASK = 0xF000;
private const int FAPPCOMMAND_MOUSE = 0x8000;
private const int FAPPCOMMAND_OEM = 0x1000;
private const int RIM_TYPEMOUSE = 0;
private const int RIM_TYPEKEYBOARD = 1;
private const int RIM_TYPEHID = 2;
private const int RIDI_DEVICENAME = 0x20000007;
private const int WM_KEYDOWN = 0x0100;
private const int WM_SYSKEYDOWN = 0x0104;
private const int WM_INPUT = 0x00FF;
private const int VK_OEM_CLEAR = 0xFE;
private const int VK_LAST_KEY = VK_OEM_CLEAR; // this is a made up value used as a sentinal
private const int PM_REMOVE = 0x01;
[StructLayout(LayoutKind.Sequential)]
private struct RAWINPUTDEVICELIST
{
public IntPtr hDevice;
[MarshalAs(UnmanagedType.U4)]
public int dwType;
}
[StructLayout(LayoutKind.Explicit)]
private struct RAWINPUT
{
[FieldOffset(0)]
public RAWINPUTHEADER header;
[FieldOffset(16)]
public RAWMOUSE mouse;
[FieldOffset(16)]
public RAWKEYBOARD keyboard;
[FieldOffset(16)]
public RAWHID hid;
}
[StructLayout(LayoutKind.Sequential)]
private struct RAWINPUTHEADER
{
[MarshalAs(UnmanagedType.U4)]
public int dwType;
[MarshalAs(UnmanagedType.U4)]
public int dwSize;
public IntPtr hDevice;
[MarshalAs(UnmanagedType.U4)]
public int wParam;
}
[StructLayout(LayoutKind.Sequential)]
private struct RAWHID
{
[MarshalAs(UnmanagedType.U4)]
public int dwSizHid;
[MarshalAs(UnmanagedType.U4)]
public int dwCount;
}
[StructLayout(LayoutKind.Sequential)]
private struct BUTTONSSTR
{
[MarshalAs(UnmanagedType.U2)]
public ushort usButtonFlags;
[MarshalAs(UnmanagedType.U2)]
public ushort usButtonData;
}
[StructLayout(LayoutKind.Explicit)]
private struct RAWMOUSE
{
[MarshalAs(UnmanagedType.U2)]
[FieldOffset(0)]
public ushort usFlags;
[MarshalAs(UnmanagedType.U4)]
[FieldOffset(4)]
public uint ulButtons;
[FieldOffset(4)]
public BUTTONSSTR buttonsStr;
[MarshalAs(UnmanagedType.U4)]
[FieldOffset(8)]
public uint ulRawButtons;
[FieldOffset(12)]
public int lLastX;
[FieldOffset(16)]
public int lLastY;
[MarshalAs(UnmanagedType.U4)]
[FieldOffset(20)]
public uint ulExtraInformation;
}
[StructLayout(LayoutKind.Sequential)]
private struct RAWKEYBOARD
{
[MarshalAs(UnmanagedType.U2)]
public ushort MakeCode;
[MarshalAs(UnmanagedType.U2)]
public ushort Flags;
[MarshalAs(UnmanagedType.U2)]
public ushort Reserved;
[MarshalAs(UnmanagedType.U2)]
public ushort VKey;
[MarshalAs(UnmanagedType.U4)]
public uint Message;
[MarshalAs(UnmanagedType.U4)]
public uint ExtraInformation;
}
[StructLayout(LayoutKind.Sequential)]
private struct RAWINPUTDEVICE
{
[MarshalAs(UnmanagedType.U2)]
public ushort usUsagePage;
[MarshalAs(UnmanagedType.U2)]
public ushort usUsage;
[MarshalAs(UnmanagedType.U4)]
public int dwFlags;
public IntPtr hwndTarget;
}
[DllImport("User32.dll")]
private static extern uint GetRawInputDeviceList(IntPtr pRawInputDeviceList, ref uint uiNumDevices, uint cbSize);
[DllImport("User32.dll")]
private static extern uint GetRawInputDeviceInfo(IntPtr hDevice, uint uiCommand, IntPtr pData, ref uint pcbSize);
[DllImport("User32.dll")]
private static extern bool RegisterRawInputDevices(RAWINPUTDEVICE[] pRawInputDevice, uint uiNumDevices, uint cbSize);
[DllImport("User32.dll")]
private static extern uint GetRawInputData(IntPtr hRawInput, uint uiCommand, IntPtr pData, ref uint pcbSize, uint cbSizeHeader);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetKeyboardState(byte[] lpKeyState);
[DllImport("user32.dll")]
private static extern int ToUnicode(uint wVirtKey, uint wScanCode, byte[] lpKeyState, [Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)] StringBuilder pwszBuff,
int cchBuff, uint wFlags);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool PeekMessage(out MSG lpmsg, IntPtr hwnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);