如何在C#中区分多个输入设备

27

我有一个条形码扫描器(它作为键盘),当然我也有一台连接到计算机的键盘。软件正在从扫描仪和键盘接受输入。我需要只接受扫描仪的输入。代码是用C#编写的。是否有一种方法可以“禁用”键盘输入,仅接受扫描仪输入?

注意: 键盘是笔记本电脑的一部分...所以无法拔掉。另外,我尝试了放置以下代码 protected override Boolean ProcessDialogKey(System.Windows.Forms.Keys keyData) { return true; } 但是除了忽略键盘击键之外,还会忽略条形码扫描仪的输入。

我不能让扫描仪发送哨兵字符,因为扫描仪正在被其他应用程序使用,添加哨兵字符流意味着修改其他代码。

此外,我不能使用定时方法来确定输入是否来自条形码扫描仪(如果是一堆字符后面跟着暂停),因为扫描的条形码可能是单个字符的条形码。

是的,我正在从数据流中读取数据。

我正在尝试跟随文章:在WinForms中区分条形码扫描仪和键盘。但我有以下问题:

  1. 我遇到了NativeMethods不可访问的错误。由于保护级别,似乎我需要导入dll;这是正确的吗?如果是,我该如何做?
  2. 应该使用哪个protected override void WndProc(ref Message m)定义,文章中有两个实现?
  3. 我遇到了与[SecurityPermission(SecurityAction.LinkDemand,Flags = SecurityPermissionFlag.UnmanagedCode)]相关的错误CS0246:找不到类型或命名空间“SecurityPermission”(是否缺少using指令或程序集引用?)。如何解决此错误?
  4. 在包含if ((from hardwareId in hardwareIds where deviceName.Contains(hardwareId) select hardwareId).Count() > 0) 的行上也会出现错误。错误是错误CS1026:)预期。
  5. 我应该将文章中的所有代码都放在一个名为BarcodeScannerListener.cs的.cs文件中吗?

关于Nicholas Piasecki在http://nicholas.piasecki.name/blog/2009/02/distinguishing-barcode-scanners-from-the-keyboard-in-winforms/中发布的C#解决方案源代码的后续问题:

  1. 我无法在VS 2005中打开该解决方案,因此我下载了Visual C# 2008 Express Edition,并成功运行了代码。然而,在连接我的条形码扫描仪并扫描一维码后,程序无法识别扫描结果。我在OnBarcodeScanned方法中设置了一个断点,但从未被命中。我已经使用“设备管理器”获取了我的条形码扫描仪的id,并更改了App.config文件。似乎有2个具有HID#Vid_0536&Pid_01c1的deviceNames(这是当扫描仪连接时从“设备管理器”中获得的)。我不知道这是否导致扫描不起作用。在迭代deviceNames时,以下是我使用调试器找到的设备列表:

"\??\HID#Vid_0536&Pid_01c1&MI_01#9&25ca5370&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}"

"\??\HID#Vid_0536&Pid_01c1&MI_00#9&38e10b9&0&0000#{884b96c3-56ef-11d1-bc8c-00a0c91405dd}"

"\??\HID#Vid_413c&Pid_2101&MI_00#8&1966e83d&0&0000#{884b96c3-56ef-11d1-bc8c-00a0c91405dd}"

"\??\HID#Vid_413c&Pid_3012#7&960fae0&0&0000#{378de44c-56ef-11d1-bc8c-00a0c91405dd}"
"\??\Root#RDP_KBD#0000#{884b96c3-56ef-11d1-bc8c-00a0c91405dd}" "\??\ACPI#PNP0303#4&2f94427b&0#{884b96c3-56ef-11d1-bc8c-00a0c91405dd}" "\??\Root#RDP_MOU#0000#{378de44c-56ef-11d1-bc8c-00a0c91405dd}" "\??\ACPI#PNP0F13#4&2f94427b&0#{378de44c-56ef-11d1-bc8c-00a0c91405dd}"

这里有两个 HID#Vid_0536&Pid_01c1 的条目,这可能导致扫描不起作用?

好的,看起来我得想出一种不依赖于扫描器发送 ASCII 0x04 字符的方法……因为我的扫描器没有发送该字符。在那之后,条形码扫描事件被触发并且带有条形码的弹出窗口显示出来。所以谢谢 Nicholas 的帮助。


1
我在文章底部添加了一个示例代码发布。祝你好运! - Nicholas Piasecki
2
@NicholasPiasecki,已经过去6年了,但为什么还是HTTP 410? - itsho
3
如果您的扫描仪支持USB HID,则最好使用Windows 10中提供的新API,并禁用键盘仿真:https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/BarcodeScanner ... 在旧版本上,您可以使用SetupDi ... 如果必须使用键盘仿真,请访问archive.org:https://web.archive.org/web/20150212020144/http://nicholas.piasecki.name/blog/2009/02/distinguishing-barcode-scanners-from-the-keyboard-in-winforms。HTTP 410有多种含义,包括“生命短暂”,“介质已过时”和“世界充满了混蛋”。 - Nicholas Piasecki
2
@NicholasPiasecki,你能否或者有人能分享一下Nicholas Piasecki的源代码。他的博客无法访问。我正在为Windows XP制作条形码扫描应用程序。 - Qeeet
@Qeeet,我也在寻找Nicholas Piasecki的源代码。你找到了吗? - Sérgio S. Filho
@Qeeet 试试这个:https://web.archive.org/web/20130625064315/http://nicholas.piasecki.name/blog/2009/02/distinguishing-barcode-scanners-from-the-keyboard-in-winforms/ - Blackclaws
7个回答

21

你可以使用原始输入API来区分键盘和扫描仪,就像我最近所做的那样。无论你连接了多少个键盘或类似键盘的设备,你都会在按键映射到通常在KeyDown事件中看到的设备无关虚拟键之前看到一个WM_INPUT

更简单的方法是按照其他人建议的配置扫描仪,在条形码之前和之后发送标志字符。(你通常是通过在扫描仪用户手册的背面扫描特殊的条形码来完成这个操作。)然后,你的主窗体的KeyPreview事件可以监视这些标志并在任何子控件正在读取条形码时吞咽键事件。或者,如果你想更高级一些,你可以使用SetWindowsHookEx()的低级键盘钩子来监视这些标志并在那里吞咽它们(这样做的优点是即使你的应用程序没有焦点,你仍然可以获得事件)。

我不能改变我们的条码扫描仪上的标志值等等,所以我不得不走复杂的路线。绝对是很痛苦的。如果可以的话,保持简单!

--

七年后的更新:如果您的使用场景是从USB条码扫描器读取数据,Windows 10内置了一个友好的API,名称为Windows.Devices.PointOfService.BarcodeScanner。它是一个UWP/WinRT API,但您也可以从常规桌面应用程序中使用它;这就是我现在正在做的事情。以下是一些示例代码,直接来自我的应用程序,以便让您了解:

{
    using System;
    using System.Linq;
    using System.Threading.Tasks;
    using System.Windows;
    using Windows.Devices.Enumeration;
    using Windows.Devices.PointOfService;
    using Windows.Storage.Streams;
    using PosBarcodeScanner = Windows.Devices.PointOfService.BarcodeScanner;

    public class BarcodeScanner : IBarcodeScanner, IDisposable
    {
        private ClaimedBarcodeScanner scanner;

        public event EventHandler<BarcodeScannedEventArgs> BarcodeScanned;

        ~BarcodeScanner()
        {
            this.Dispose(false);
        }

        public bool Exists
        {
            get
            {
                return this.scanner != null;
            }
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        public async Task StartAsync()
        {
            if (this.scanner == null)
            {
                var collection = await DeviceInformation.FindAllAsync(PosBarcodeScanner.GetDeviceSelector());
                if (collection != null && collection.Count > 0)
                {
                    var identity = collection.First().Id;
                    var device = await PosBarcodeScanner.FromIdAsync(identity);
                    if (device != null)
                    {
                        this.scanner = await device.ClaimScannerAsync();
                        if (this.scanner != null)
                        {
                            this.scanner.IsDecodeDataEnabled = true;
                            this.scanner.ReleaseDeviceRequested += WhenScannerReleaseDeviceRequested;
                            this.scanner.DataReceived += WhenScannerDataReceived;

                            await this.scanner.EnableAsync();
                        }
                    }
                }
            }
        }

        private void WhenScannerDataReceived(object sender, BarcodeScannerDataReceivedEventArgs args)
        {
            var data = args.Report.ScanDataLabel;

            using (var reader = DataReader.FromBuffer(data))
            {
                var text = reader.ReadString(data.Length);
                var bsea = new BarcodeScannedEventArgs(text);
                this.BarcodeScanned?.Invoke(this, bsea);
            }
        }

        private void WhenScannerReleaseDeviceRequested(object sender, ClaimedBarcodeScanner args)
        {
            args.RetainDevice();
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                this.scanner = null;
            }
        }
    }
}

需要注意的是,您需要一款支持USB HID POS而不仅仅是键盘楔的条形码扫描器。如果您的扫描器只是键盘楔,请考虑在eBay上购买类似于二手Honeywell 4600G之类的产品,价格约为25美元。相信我,您的心理健康值得这个投资。


1
我正在尝试跟随这篇文章。但是,我遇到了错误:NativeMethods因其保护级别而无法访问。看起来我需要导入一个dll;这正确吗?如果是的话,我该如何做呢?此外,我应该使用哪个受保护的重写void WndProc(ref Message m)定义? - Amar Premsaran Patel
2
同时我遇到了一个与[SecurityPermission( SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]相关的错误 error CS0246: 找不到类型或命名空间名称'SecurityPermission'(您是否缺少using指令或程序集引用?) - Amar Premsaran Patel
1
在包含以下代码行的错误: if ((from hardwareId in hardwareIds where deviceName.Contains(hardwareId) select hardwareId).Count() > 0)错误为 error CS1026: 需要 ')'。 - Amar Premsaran Patel
2
@jaredbaszler MSDN表示,同一API支持支持“SPP-SSI”的蓝牙扫描仪https://msdn.microsoft.com/en-us/library/windows/apps/mt426649.aspx,并且扫描事件确实会发送它来自哪个设备https://msdn.microsoft.com/en-us/library/windows/apps/windows.devices.pointofservice.claimedbarcodescanner.datareceived.aspx因此理论上您可以将DeviceIds映射到用户以告诉它来自哪里。 - Nicholas Piasecki
3
@NicholasPiasecki - 我想知道上面的代码所使用的框架是什么。我正在尝试将 Windows.Devices.PointOfService 库整合到一个现有的 Winforms 项目中,但并不顺利。我能够将库加入到我的项目中,但由于缺少其他 Win10 SDK 的参考而导致上述代码的某些部分无法编译。 - jaredbaszler
显示剩余9条评论

3

在类似的情况下,我所做的是通过观察输入速度来区分扫描和用户输入。

许多字符紧密地排在一起然后暂停是扫描。其他任何内容都是键盘输入。

我不知道你的具体要求,所以也许这对你来说不太适用,但这是我所能提供的最好的方法 :)


1
是的,直接从设备中查找和读取数据并不容易。你应该能够设置扫描仪以提供起始和停止序列 - 这些序列永远不会出现在键盘上。 - Adam Davis
1
你能提供一些关于你是如何实现这个的代码示例吗?我也找到了使用输入速度的解决方案。然而,这不是一个好选择。有时候按键之间的时间间隔非常大。例如:15 15 16 180 13 15 15 15(以毫秒为单位)。 - Dimitar Tsonev

1
我知道这是一个旧帖子,通过在WIN10中搜索条形码扫描找到它。 以下是一些注意事项,以防有人需要。
这些来自Honeywell的扫描器有几个USB接口。 其中一个是键盘+Hid销售点(复合设备)。 此外还有CDC-ACM(ComPort仿真)和单独的Hid销售点+更多。
默认情况下,扫描仪会公开一个序列号,因此主机可以区分许多设备(我曾经连接过20多个)。不过,有一个命令可以禁用序列号!
新型号在这方面表现相同。 如果您想实时查看,请尝试我的终端程序yat3(免费在我的网站上)。 它可以打开上述所有接口,并专为此类设备量身定制。
使用键盘接口的建议:
只有在万不得已的情况下使用它们。当涉及到异国情调的字符时,它们速度较慢、可靠性较低。唯一的好处是,如果您想将数据输入到现有应用程序中。如果您已经编写代码,则从ComPort/HidPos设备读取更容易。

1

1

这取决于您与设备交互的方式。无论如何,它都不会是C#解决方案,而是其他库。您是从流中读取数据吗?如果您只是获取按键,可能无法做任何事情。


0
我成功地完成了你们在这里寻找的东西。我有一个应用程序,可以接收来自Honeywell/Metrologic条形码扫描仪的所有条形码字符数据。系统上没有其他应用程序可以接收扫描仪发出的数据,键盘继续正常工作。
我的应用程序使用原始输入和可怕的低级键盘挂钩系统的组合。与这里所写的相反,我发现在键盘挂钩函数被调用之前,会先收到wm_input消息。我的处理wm_input消息的代码基本上设置一个布尔变量,以指定接收到的字符是不是来自扫描仪。键盘挂钩函数在wm_input被处理后立即被调用,它会吞下扫描仪的伪键盘数据,防止其他应用程序接收到数据。
键盘挂钩函数必须放置在dll中,因为您想要拦截所有系统键盘消息。此外,必须使用内存映射文件来进行wm_input处理代码与dll之间的通信。

1
这方面有什么新进展吗?看起来那个人在不同的页面上发布了几条关于这个问题的评论。 - MMM

0

我认为你可以通过DirectX API或者如果这不起作用,通过原始输入API来区分多个键盘。


1
(注意:条形码扫描仪在Windows中只是另一个键盘) - anon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接