4

I managed using bar-code scanner successfully in my WPF project using a keyboard hook as follows (I skip some details, but basically, I can rely on the fact that I know which keyboard is my scanner).

/// <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).");
}

The approach then processes WM_INPUT messages to retrieve information about any keyboard events that occur and handles the event accordingly if it is coming from the Bar-code scanner that is already known.

Now the thing is that in Winforms I should not use hooks but override WndProc as stated here, but I am somehow struggling to understand how I can use WndProc as I need to know:

a) what event I really need to handle in the WndProc method

b) how I can identify the device that fired the event

Any help would be very appreciated! Cheers!

Community
  • 1
  • 1
neggenbe
  • 1,697
  • 2
  • 24
  • 62
  • 2
    I've used a modified version of [this code](http://www.codeproject.com/Articles/17123/Using-Raw-Input-from-C-to-handle-multiple-keyboard) with great success. It's... not exactly trivial, but it's one of the only ways I've been able to isolate VID/PIDs as well as hook the input at a low enough level for Winforms to not get in the way. – helrich Apr 06 '15 at 11:51
  • Hummm... I actually already use something quite similar in fact. What is rather annoying is the restriction to non-terminal-server environments... Is this because of the raw-input or is it another reason? – neggenbe Apr 06 '15 at 16:45
  • Possibly. I haven't used that code with a terminal server but could see how that could cause issues. I'm afraid I might not be able to help you further in that regard. – helrich Apr 06 '15 at 17:41
  • Could you put the answer if you were found? Thank you. – qakmak Mar 07 '16 at 08:06
  • qakmak - I ended up listening to the keyboard... – neggenbe Mar 07 '16 at 08:34

2 Answers2

5

I ended using the following approach:

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

Then, add the following lines of code to your form which is listening to your scanner:

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;
}
neggenbe
  • 1,697
  • 2
  • 24
  • 62
  • But I still think not good as the original wpf code, You know in original code it will only listen the last input device. But in this code, If you remove the limit from `_barcode.Length > 7` then every time when you press Enter use keyboard, It'll execute. So can't we just remote it trig from keyboard? like original code? – qakmak Mar 07 '16 at 11:54
  • I'll post my previously working code as well, then. All credits to the following articel: http://www.codeproject.com/Articles/17123/Using-Raw-Input-from-C-to-handle-multiple-keyboard – neggenbe Mar 07 '16 at 15:13
1

I post here a piece of code I used for a while before using the above version. It is derived from here, and I didn't re-test. Comes as is, hope it helps anyway...

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);
neggenbe
  • 1,697
  • 2
  • 24
  • 62