Search code examples
c#winformsbarcode-scannerwndproc

Winforms keypress and barcode scanner


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!


Solution

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