Winforms 按键和条形码扫描器

Winforms keypress and barcode scanner

我在我的 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 here,但我不知何故难以理解如何使用 WndProc,因为我需要知道:

a) 我真正需要在 WndProc 方法中处理什么事件

b) 我如何识别触发事件的设备

任何帮助将不胜感激! 干杯!

我结束了使用以下方法:

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;
}

我post这里是一段代码,我在使用上面的版本之前用了一段时间。来源于here,我没有重新测试。原样,希望它能有所帮助...

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);