跳至页脚内容
USING IRONBARCODE

C# USB条码扫描器:构建完整的扫描应用程序

USB 条形码扫描器作为标准键盘输入设备连接到 C# 应用程序,将扫描的数据作为键入的字符发送,然后按 Enter 键。 这种 HID 键盘楔形行为使集成变得简单——您的应用程序无需任何特殊驱动程序或 SDK 即可接收文本输入。 IronBarcode处理原始输入以验证格式、提取结构化数据并生成响应条形码,将简单的扫描事件转化为库存管理、零售销售点和物流跟踪系统的完整数据管道。

零售、仓储和制造运营都依赖于准确、快速的条形码扫描。 当开发人员将 USB 扫描仪连接到 Windows Forms 或 WPF 应用程序时,扫描仪的行为与键盘完全相同——数据会显示在文本框中,按下 Enter 键表示已接收到完整的条形码。 真正的挑战不在于如何获取数据; 它正在正确处理。 IronBarcode 的条形码验证功能可检查格式完整性,提取批号或 GS1 应用标识符等字段,并可立即生成新的条形码作为响应。

本指南将逐步引导您构建一个可用于生产环境的 C# USB 条码扫描器应用程序。您将安装库、捕获扫描器输入、验证条码格式、生成响应标签,并组装一个高吞吐量的基于队列的处理器。 每个部分都包含完整的、可运行的代码,目标框架为.NET 10,并在适当情况下采用顶级语句样式。

如何使用 C# 实现 USB 条形码扫描器?

为什么 HID 键盘楔形模式能够简化集成?

大多数 USB 条码扫描器出厂时默认配置为 HID 键盘楔形模式。 当您将 USB 设备插入 Windows 电脑时,操作系统会将其注册为 USB 存储设备(用于配置)和键盘(用于数据输入)。 当扫描条形码时,设备会将解码后的条形码值转换为按键,并将其发送到当前具有焦点的应用程序窗口,并在末尾添加回车符。

从 C# 开发人员的角度来看,这意味着您不需要供应商 SDK、COM 库或特殊的 USB API。只需一个带有 KeyDown 事件处理程序的标准 TextBox 即可捕获输入。 主要的集成挑战在于区分扫描仪输入和真正的键盘输入。 扫描器通常会在很短的时间内(通常不到 50 毫秒)完成所有字符的扫描,而人类打字则会将击键过程分散到数百毫秒内。 控制连击时间是过滤掉意外按键的可靠方法。

专业级扫描仪还支持串行(RS-232 或虚拟 COM 端口)和直接 USB HID 模式,让您可以更好地控制前缀/后缀字符和扫描触发器。 下面这种接口模式可以处理这两种情况:

public interface IScannerInput
{
    event EventHandler<string> BarcodeScanned;
    void StartListening();
    void StopListening();
}

public class KeyboardWedgeScanner : IScannerInput
{
    public event EventHandler<string> BarcodeScanned;
    private readonly TextBox _inputBox;
    private readonly System.Windows.Forms.Timer _burstTimer;
    private readonly System.Text.StringBuilder _buffer = new();

    public KeyboardWedgeScanner(TextBox inputBox)
    {
        _inputBox = inputBox;
        _burstTimer = new System.Windows.Forms.Timer { Interval = 80 };
        _burstTimer.Tick += OnBurstTimeout;
        _inputBox.KeyPress += OnKeyPress;
    }

    private void OnKeyPress(object sender, KeyPressEventArgs e)
    {
        if (e.KeyChar == (char)Keys.Enter)
        {
            _burstTimer.Stop();
            string value = _buffer.ToString().Trim();
            _buffer.Clear();
            if (value.Length > 0)
                BarcodeScanned?.Invoke(this, value);
        }
        else
        {
            _buffer.Append(e.KeyChar);
            _burstTimer.Stop();
            _burstTimer.Start();
        }
        e.Handled = true;
    }

    private void OnBurstTimeout(object sender, EventArgs e)
    {
        _burstTimer.Stop();
        _buffer.Clear(); // incomplete burst -- discard
    }

    public void StartListening() => _inputBox.Focus();
    public void StopListening() => _inputBox.Enabled = false;
}

public class SerialPortScanner : IScannerInput
{
    public event EventHandler<string> BarcodeScanned;
    private readonly System.IO.Ports.SerialPort _port;

    public SerialPortScanner(string portName, int baudRate = 9600)
    {
        _port = new System.IO.Ports.SerialPort(portName, baudRate);
        _port.DataReceived += OnDataReceived;
    }

    private void OnDataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
    {
        string data = _port.ReadLine().Trim();
        if (data.Length > 0)
            BarcodeScanned?.Invoke(this, data);
    }

    public void StartListening() => _port.Open();
    public void StopListening() => _port.Close();
}
public interface IScannerInput
{
    event EventHandler<string> BarcodeScanned;
    void StartListening();
    void StopListening();
}

public class KeyboardWedgeScanner : IScannerInput
{
    public event EventHandler<string> BarcodeScanned;
    private readonly TextBox _inputBox;
    private readonly System.Windows.Forms.Timer _burstTimer;
    private readonly System.Text.StringBuilder _buffer = new();

    public KeyboardWedgeScanner(TextBox inputBox)
    {
        _inputBox = inputBox;
        _burstTimer = new System.Windows.Forms.Timer { Interval = 80 };
        _burstTimer.Tick += OnBurstTimeout;
        _inputBox.KeyPress += OnKeyPress;
    }

    private void OnKeyPress(object sender, KeyPressEventArgs e)
    {
        if (e.KeyChar == (char)Keys.Enter)
        {
            _burstTimer.Stop();
            string value = _buffer.ToString().Trim();
            _buffer.Clear();
            if (value.Length > 0)
                BarcodeScanned?.Invoke(this, value);
        }
        else
        {
            _buffer.Append(e.KeyChar);
            _burstTimer.Stop();
            _burstTimer.Start();
        }
        e.Handled = true;
    }

    private void OnBurstTimeout(object sender, EventArgs e)
    {
        _burstTimer.Stop();
        _buffer.Clear(); // incomplete burst -- discard
    }

    public void StartListening() => _inputBox.Focus();
    public void StopListening() => _inputBox.Enabled = false;
}

public class SerialPortScanner : IScannerInput
{
    public event EventHandler<string> BarcodeScanned;
    private readonly System.IO.Ports.SerialPort _port;

    public SerialPortScanner(string portName, int baudRate = 9600)
    {
        _port = new System.IO.Ports.SerialPort(portName, baudRate);
        _port.DataReceived += OnDataReceived;
    }

    private void OnDataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
    {
        string data = _port.ReadLine().Trim();
        if (data.Length > 0)
            BarcodeScanned?.Invoke(this, data);
    }

    public void StartListening() => _port.Open();
    public void StopListening() => _port.Close();
}
$vbLabelText   $csharpLabel

键盘楔形实现中的突发计时器是关键细节。 每次按键都会重置,并且只有在字符停止输入时才会触发——这意味着打字速度较慢的真正键盘用户,其未完成的输入将被丢弃,而不是被当作条形码扫描处理。

如何处理多个扫描仪品牌?

企业环境中经常在同一楼层混合运行 Honeywell、Zebra(以前称为 Symbol/Motorola)和 Datalogic 扫描仪。 每个厂商都有自己的默认终止符、波特率和前缀/后缀约定。 配置模型使您的应用程序具有灵活性:

public class ScannerConfiguration
{
    public string ScannerType { get; set; } = "KeyboardWedge";
    public string PortName { get; set; } = "COM3";
    public int BaudRate { get; set; } = 9600;
    public string Terminator { get; set; } = "\r\n";
    public bool EnableBeep { get; set; } = true;
    public Dictionary<string, string> BrandSettings { get; set; } = new();

    public static ScannerConfiguration GetHoneywellConfig() => new()
    {
        ScannerType = "Serial",
        BaudRate = 115200,
        BrandSettings = new Dictionary<string, string>
        {
            { "Prefix", "STX" },
            { "Suffix", "ETX" },
            { "TriggerMode", "Manual" }
        }
    };

    public static ScannerConfiguration GetZebraConfig() => new()
    {
        ScannerType = "KeyboardWedge",
        BrandSettings = new Dictionary<string, string>
        {
            { "ScanMode", "Continuous" },
            { "BeepVolume", "High" }
        }
    };
}
public class ScannerConfiguration
{
    public string ScannerType { get; set; } = "KeyboardWedge";
    public string PortName { get; set; } = "COM3";
    public int BaudRate { get; set; } = 9600;
    public string Terminator { get; set; } = "\r\n";
    public bool EnableBeep { get; set; } = true;
    public Dictionary<string, string> BrandSettings { get; set; } = new();

    public static ScannerConfiguration GetHoneywellConfig() => new()
    {
        ScannerType = "Serial",
        BaudRate = 115200,
        BrandSettings = new Dictionary<string, string>
        {
            { "Prefix", "STX" },
            { "Suffix", "ETX" },
            { "TriggerMode", "Manual" }
        }
    };

    public static ScannerConfiguration GetZebraConfig() => new()
    {
        ScannerType = "KeyboardWedge",
        BrandSettings = new Dictionary<string, string>
        {
            { "ScanMode", "Continuous" },
            { "BeepVolume", "High" }
        }
    };
}
$vbLabelText   $csharpLabel

将这些配置存储在设置文件或数据库中,意味着仓库工作人员可以更换扫描仪型号而无需重新部署。 ScannerType 字段决定在启动时实例化哪个 IScannerInput 实现。

如何在 C# 项目中安装IronBarcode ?

通过NuGet添加IronBarcode的最快方法是什么?

在 Visual Studio 中打开软件包管理器控制台并运行:

Install-Package IronBarCode
Install-Package IronBarCode
SHELL

或者,使用.NET CLI:

dotnet add package IronBarCode
dotnet add package IronBarCode
SHELL

这两条命令都会从NuGet获取最新版本,并将程序集引用添加到您的项目文件中。该库面向.NET Standard 2.0,因此无需任何额外的兼容性补丁即可在.NET Framework 4.6.2 到.NET 10 上运行。

安装完成后,请在调用任何IronBarcode方法之前设置您的许可证密钥。 用于开发和评估的免费试用密钥可从IronBarcode许可页面获取:

IronBarCode.License.LicenseKey = "YOUR-LICENSE-KEY";
IronBarCode.License.LicenseKey = "YOUR-LICENSE-KEY";
$vbLabelText   $csharpLabel

对于容器化部署, IronBarcode可与 Linux 上的 Docker 配合使用;对于云函数,它支持AWS LambdaAzure Functions

如何使用IronBarcode验证扫描的条形码?

格式验证的正确方法是什么?

IronBarcode支持 30 多种条形码符号体系,包括Code 128EAN-13Code 39QR 码Data Matrix 。 对于 USB 扫描器应用,验证模式会将扫描的字符串重新编码为条形码图像,并立即通过解码器将其读取回来。 这次往返验证确认该字符串是声明格式的有效值:

public class BarcodeValidator
{
    public async Task<ValidationResult> ValidateAsync(string scannedText, BarcodeEncoding preferredFormat = BarcodeEncoding.Code128)
    {
        var result = new ValidationResult { RawInput = scannedText };

        try
        {
            var barcode = BarcodeWriter.CreateBarcode(scannedText, preferredFormat);
            var readResults = await BarcodeReader.ReadAsync(barcode.ToBitmap());

            if (readResults.Any())
            {
                var first = readResults.First();
                result.IsValid = true;
                result.Format = first.BarcodeType;
                result.Value = first.Value;
                result.Confidence = first.Confidence;
            }
            else
            {
                result.IsValid = false;
                result.Error = "No barcode could be decoded from the scanned input.";
            }
        }
        catch (Exception ex)
        {
            result.IsValid = false;
            result.Error = ex.Message;
        }

        return result;
    }
}

public record ValidationResult
{
    public string RawInput { get; init; } = "";
    public bool IsValid { get; set; }
    public BarcodeEncoding Format { get; set; }
    public string Value { get; set; } = "";
    public float Confidence { get; set; }
    public string Error { get; set; } = "";
}
public class BarcodeValidator
{
    public async Task<ValidationResult> ValidateAsync(string scannedText, BarcodeEncoding preferredFormat = BarcodeEncoding.Code128)
    {
        var result = new ValidationResult { RawInput = scannedText };

        try
        {
            var barcode = BarcodeWriter.CreateBarcode(scannedText, preferredFormat);
            var readResults = await BarcodeReader.ReadAsync(barcode.ToBitmap());

            if (readResults.Any())
            {
                var first = readResults.First();
                result.IsValid = true;
                result.Format = first.BarcodeType;
                result.Value = first.Value;
                result.Confidence = first.Confidence;
            }
            else
            {
                result.IsValid = false;
                result.Error = "No barcode could be decoded from the scanned input.";
            }
        }
        catch (Exception ex)
        {
            result.IsValid = false;
            result.Error = ex.Message;
        }

        return result;
    }
}

public record ValidationResult
{
    public string RawInput { get; init; } = "";
    public bool IsValid { get; set; }
    public BarcodeEncoding Format { get; set; }
    public string Value { get; set; } = "";
    public float Confidence { get; set; }
    public string Error { get; set; } = "";
}
$vbLabelText   $csharpLabel

对于供应链应用中使用的GS1-128 条形码,扫描的字符串包含括号中的应用标识符前缀,例如 GTIN 的 (01) 和到期日期的 (17)。 当您指定 BarcodeEncoding.GS1_128 时, IronBarcode会自动解析这些应用程序标识符。

开发人员应该实现哪种EAN-13校验和逻辑?

零售POS应用通常需要在将EAN-13值传递给定价查询之前,独立验证EAN-13校验位。EAN-13的Luhn式校验和在前12位数字中交替使用权重1和3:

public static bool ValidateEan13Checksum(string value)
{
    if (value.Length != 13 || !value.All(char.IsDigit))
        return false;

    int sum = 0;
    for (int i = 0; i < 12; i++)
    {
        int digit = value[i] - '0';
        sum += (i % 2 == 0) ? digit : digit * 3;
    }

    int expectedCheck = (10 - (sum % 10)) % 10;
    return expectedCheck == (value[12] - '0');
}
public static bool ValidateEan13Checksum(string value)
{
    if (value.Length != 13 || !value.All(char.IsDigit))
        return false;

    int sum = 0;
    for (int i = 0; i < 12; i++)
    {
        int digit = value[i] - '0';
        sum += (i % 2 == 0) ? digit : digit * 3;
    }

    int expectedCheck = (10 - (sum % 10)) % 10;
    return expectedCheck == (value[12] - '0');
}
$vbLabelText   $csharpLabel

这种纯逻辑检查在编码之前运行,以避免在高容量零售环境中每次扫描时产生往返图像生成的开销。 根据GS1 规范,当去掉前导零时,UPC-A(12 位数字)的校验位算法是相同的。

如何根据扫描的输入生成响应条形码?

应用程序何时应该在扫描后创建新条形码?

仓库收货中常见的模式是"扫描和重新贴标签"工作流程:入库物品带有供应商条形码(通常是 EAN-13 或 ITF-14),仓库管理系统需要打印一个带有其自身位置和批号的内部 Code 128 标签。 IronBarcode 的生成功能只需几行代码即可完成:

public class InventoryLabelGenerator
{
    private readonly string _outputDirectory;

    public InventoryLabelGenerator(string outputDirectory)
    {
        _outputDirectory = outputDirectory;
        Directory.CreateDirectory(_outputDirectory);
    }

    public async Task<string> GenerateLabelAsync(string internalCode, string locationCode)
    {
        string fullCode = $"{internalCode}|{locationCode}|{DateTime.UtcNow:yyyyMMdd}";

        // Primary Code 128 label for scanners
        var linearBarcode = BarcodeWriter.CreateBarcode(fullCode, BarcodeEncoding.Code128);
        linearBarcode.ResizeTo(500, 140);
        linearBarcode.SetMargins(12);
        linearBarcode.AddAnnotationTextAboveBarcode(fullCode);
        linearBarcode.ChangeBarCodeColor(IronSoftware.Drawing.Color.Black);

        // QR code companion for mobile apps
        var qrCode = BarcodeWriter.CreateQrCode(fullCode);
        qrCode.ResizeTo(200, 200);
        qrCode.SetMargins(8);

        string timestamp = DateTime.UtcNow.ToString("yyyyMMddHHmmss");
        string pngPath = Path.Combine(_outputDirectory, $"{internalCode}_{timestamp}.png");
        string pdfPath = Path.Combine(_outputDirectory, $"{internalCode}_{timestamp}.pdf");

        await Task.Run(() =>
        {
            linearBarcode.SaveAsPng(pngPath);
            linearBarcode.SaveAsPdf(pdfPath);
        });

        return pngPath;
    }
}
public class InventoryLabelGenerator
{
    private readonly string _outputDirectory;

    public InventoryLabelGenerator(string outputDirectory)
    {
        _outputDirectory = outputDirectory;
        Directory.CreateDirectory(_outputDirectory);
    }

    public async Task<string> GenerateLabelAsync(string internalCode, string locationCode)
    {
        string fullCode = $"{internalCode}|{locationCode}|{DateTime.UtcNow:yyyyMMdd}";

        // Primary Code 128 label for scanners
        var linearBarcode = BarcodeWriter.CreateBarcode(fullCode, BarcodeEncoding.Code128);
        linearBarcode.ResizeTo(500, 140);
        linearBarcode.SetMargins(12);
        linearBarcode.AddAnnotationTextAboveBarcode(fullCode);
        linearBarcode.ChangeBarCodeColor(IronSoftware.Drawing.Color.Black);

        // QR code companion for mobile apps
        var qrCode = BarcodeWriter.CreateQrCode(fullCode);
        qrCode.ResizeTo(200, 200);
        qrCode.SetMargins(8);

        string timestamp = DateTime.UtcNow.ToString("yyyyMMddHHmmss");
        string pngPath = Path.Combine(_outputDirectory, $"{internalCode}_{timestamp}.png");
        string pdfPath = Path.Combine(_outputDirectory, $"{internalCode}_{timestamp}.pdf");

        await Task.Run(() =>
        {
            linearBarcode.SaveAsPng(pngPath);
            linearBarcode.SaveAsPdf(pdfPath);
        });

        return pngPath;
    }
}
$vbLabelText   $csharpLabel

将文件保存为 PDF 格式对于可以通过网络共享接收 PDF 输入的标签打印机来说尤其有用。 您还可以导出为 SVG 格式以获得矢量质量的热敏标签输出,或者导出为字节流以直接发送到标签打印机 API。

IronBarcode支持广泛的样式自定义,包括自定义颜色、边距调整、人可读文本叠加,以及为二维码嵌入徽标,以制作品牌标记的移动标签。

![Windows Forms 应用程序界面,演示 IronBarcode 的双条形码生成功能。 界面显示已成功生成库存编号为"INV-20250917-helloworld"的 Code 128 线性条形码和二维码。 顶部的输入字段允许用户输入自定义库存代码,并有一个"生成"按钮来创建条形码。 成功消息"项目处理成功 - 已生成标签"确认操作已完成。 Code 128 条码被标记为主要的库存跟踪格式,而下方的二维码则被标记为移动友好型替代方案。该应用程序采用专业的灰色背景和清晰的视觉层次结构,展示了IronBarcode如何帮助开发人员创建多格式条码生成系统,以实现完整的库存管理。

如何构建一个完整的高容量扫描应用程序?

基于生产队列的实现方式是怎样的?

对于每分钟处理数十次扫描的应用程序来说,UI 线程上的简单同步处理程序会成为瓶颈。 以下模式使用 ConcurrentQueue<t> 和后台处理循环将扫描捕获与处理解耦。IronBarcode的异步 API处理验证,而不会阻塞用户界面:

using IronBarCode;
using System.Collections.Concurrent;

public partial class HighVolumeScanner : Form
{
    private readonly ConcurrentQueue<(string Data, DateTime Timestamp)> _scanQueue = new();
    private readonly SemaphoreSlim _semaphore;
    private readonly CancellationTokenSource _cts = new();
    private IScannerInput _scanner;

    public HighVolumeScanner()
    {
        InitializeComponent();
        IronBarCode.License.LicenseKey = "YOUR-LICENSE-KEY";
        _semaphore = new SemaphoreSlim(Environment.ProcessorCount);
        InitializeScanner();
        _ = RunProcessingLoopAsync();
    }

    private void InitializeScanner()
    {
        _scanner = System.IO.Ports.SerialPort.GetPortNames().Any()
            ? new SerialPortScanner("COM3", 115200)
            : new KeyboardWedgeScanner(txtScannerInput);

        _scanner.BarcodeScanned += (_, barcode) =>
            _scanQueue.Enqueue((barcode, DateTime.UtcNow));

        _scanner.StartListening();
    }

    private async Task RunProcessingLoopAsync()
    {
        while (!_cts.Token.IsCancellationRequested)
        {
            if (_scanQueue.TryDequeue(out var scan))
            {
                await _semaphore.WaitAsync(_cts.Token);
                _ = Task.Run(async () =>
                {
                    try { await ProcessScanAsync(scan.Data, scan.Timestamp); }
                    finally { _semaphore.Release(); }
                }, _cts.Token);
            }
            else
            {
                await Task.Delay(10, _cts.Token);
            }
        }
    }

    private async Task ProcessScanAsync(string rawData, DateTime scanTime)
    {
        var options = new BarcodeReaderOptions
        {
            Speed = ReadingSpeed.均衡,
            ExpectMultipleBarcodes = false,
            ExpectBarcodeTypes = BarcodeEncoding.Code128 | BarcodeEncoding.QRCode,
            MaxParallelThreads = 1
        };

        var testBarcode = BarcodeWriter.CreateBarcode(rawData, BarcodeEncoding.Code128);
        var results = await BarcodeReader.ReadAsync(testBarcode.ToBitmap(), options);

        if (results.Any())
        {
            var item = results.First();
            BeginInvoke(() => UpdateInventoryDisplay(item.Value, scanTime));
        }
        else
        {
            BeginInvoke(() => LogRejectedScan(rawData, scanTime));
        }
    }

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        _cts.Cancel();
        _scanner.StopListening();
        base.OnFormClosing(e);
    }
}
using IronBarCode;
using System.Collections.Concurrent;

public partial class HighVolumeScanner : Form
{
    private readonly ConcurrentQueue<(string Data, DateTime Timestamp)> _scanQueue = new();
    private readonly SemaphoreSlim _semaphore;
    private readonly CancellationTokenSource _cts = new();
    private IScannerInput _scanner;

    public HighVolumeScanner()
    {
        InitializeComponent();
        IronBarCode.License.LicenseKey = "YOUR-LICENSE-KEY";
        _semaphore = new SemaphoreSlim(Environment.ProcessorCount);
        InitializeScanner();
        _ = RunProcessingLoopAsync();
    }

    private void InitializeScanner()
    {
        _scanner = System.IO.Ports.SerialPort.GetPortNames().Any()
            ? new SerialPortScanner("COM3", 115200)
            : new KeyboardWedgeScanner(txtScannerInput);

        _scanner.BarcodeScanned += (_, barcode) =>
            _scanQueue.Enqueue((barcode, DateTime.UtcNow));

        _scanner.StartListening();
    }

    private async Task RunProcessingLoopAsync()
    {
        while (!_cts.Token.IsCancellationRequested)
        {
            if (_scanQueue.TryDequeue(out var scan))
            {
                await _semaphore.WaitAsync(_cts.Token);
                _ = Task.Run(async () =>
                {
                    try { await ProcessScanAsync(scan.Data, scan.Timestamp); }
                    finally { _semaphore.Release(); }
                }, _cts.Token);
            }
            else
            {
                await Task.Delay(10, _cts.Token);
            }
        }
    }

    private async Task ProcessScanAsync(string rawData, DateTime scanTime)
    {
        var options = new BarcodeReaderOptions
        {
            Speed = ReadingSpeed.均衡,
            ExpectMultipleBarcodes = false,
            ExpectBarcodeTypes = BarcodeEncoding.Code128 | BarcodeEncoding.QRCode,
            MaxParallelThreads = 1
        };

        var testBarcode = BarcodeWriter.CreateBarcode(rawData, BarcodeEncoding.Code128);
        var results = await BarcodeReader.ReadAsync(testBarcode.ToBitmap(), options);

        if (results.Any())
        {
            var item = results.First();
            BeginInvoke(() => UpdateInventoryDisplay(item.Value, scanTime));
        }
        else
        {
            BeginInvoke(() => LogRejectedScan(rawData, scanTime));
        }
    }

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        _cts.Cancel();
        _scanner.StopListening();
        base.OnFormClosing(e);
    }
}
$vbLabelText   $csharpLabel

SemaphoreSlim 将并发验证任务限制为逻辑处理器的数量,防止在突发扫描事件期间创建失控的线程。 BeginInvoke 调用安全地将 UI 更新序列化回主线程。

如何针对不同的扫描范围调整性能?

BarcodeReaderOptions.Speed 属性接受 ReadingSpeed.均衡ReadingSpeed.详细的。 对于已知字符串值的 USB 扫描器输入,均衡 是合适的——解码器只需要确认格式,不需要在图像中查找条形码。 根据IronBarcode 的读取速度文档快点 模式会跳过一些失真校正算法,这对于干净的扫描器输出来说是安全的,但在基于图像的场景中可能会漏掉损坏的条形码。

下表总结了每种速度模式的使用时机:

IronBarcode读取速度模式及其适用场景
速度模式 最适合 权衡
快点 清洁的USB扫描仪输入,高吞吐量 可能漏检严重损坏或倾斜的条形码
均衡 混合输入——USB扫描仪加图像导入 CPU占用率适中,准确率高
详细的 标签破损、打印对比度低、PDF导入 CPU 使用率高,吞吐量最慢

对于除了 USB 扫描仪输入外,还处理图像或 PDF 的应用程序,IronBarcode 可以使用相同的 API 接口从 PDF 文档和多页 TIFF 文件IronBarcode读取条形码

![专业 Windows Forms 条形码扫描器应用程序,展示了 IronBarcode 的实时库存跟踪功能。 界面采用简洁的双面板设计,搭配精致的深蓝色标题栏。 左侧面板显示扫描历史记录列表,其中显示了四个成功扫描的库存项目(INV-001 至 INV-004),并带有精确的时间戳和扫描状态指示器。 每个项目都包含详细的元数据,例如条形码类型和置信度。 右侧面板显示动态生成的摘要条形码,显示"商品数量:4",并具有专业的样式和适当的边距。 底部的操作按钮包括"清除列表"、"导出数据"和"打印标签",用于完整的库存管理。 状态栏显示"扫描仪:已连接" | 模式:连续 | 上次扫描时间:2 秒前,这展示了IronBarcode为生产库存系统提供的实时监控功能和专业的企业级设计。

如何处理扫描仪应用程序中的极端情况和错误?

开发人员应该预见哪些故障模式?

USB扫描仪应用程序的故障方式是可以预见的。 最常见的问题及其缓解措施如下:

扫描仪断开连接——当 USB 扫描仪拔出时,键盘楔形文本框将失去其虚拟键盘。 最简单的缓解措施是使用一个周期性定时器来检查 _inputBox.Focused,如果扫描器仍然列在已连接的 HID 设备中,则重新聚焦它。 对于串行扫描仪,SerialPort.GetPortNames() 检测重新连接。

条形码格式不明确——有些产品带有条形码,这些条形码在多种条形码格式中均有效。 例如,一个 12 位数字字符串既是有效的 UPC-A,也是有效的 Code 128。在 BarcodeReaderOptions 中指定 ExpectBarcodeTypes 可以将解码器限制为预期格式,并消除歧义。 IronBarcode故障排除指南涵盖了特定格式的识别技巧。

无效格式异常-- 如果 BarcodeWriter.CreateBarcode 接收到违反所选编码规则的字符串(例如,在仅包含数字的 EAN-13 字段中包含字母字符),则会抛出 IronBarCode.Exceptions.InvalidBarcodeException 异常。 将调用包装在 try-catch 语句中,并回退到仅字符串验证路径,可以保持应用程序运行。

按键时间冲突——在操作员也手动向同一个文本框中输入内容的环境中,前面描述的突发计时器方法是主要的防御措施。 二级保护是最小长度:大多数真正的条形码至少有 8 个字符,因此短于此长度的字符串可以视为键盘输入。

在排查串行扫描仪连接问题时,Microsoft .NET文档中关于System.IO.Ports.SerialPort 的内容非常有用,特别是关于 ReadTimeoutWriteTimeout 设置的问题。 为了符合零售业的监管规定, GS1 通用规范为每个应用标识符定义了有效值范围。

如何将应用程序扩展到移动和网络平台?

上面显示的扫描仪接口模式 -- IScannerInputBarcodeScanned 事件 -- 将硬件与处理逻辑隔离开来。 交换实现方式可以让相同的验证和生成代码在不同的平台上运行:

  • .NET MAUI为用作移动接收站的 Android 和 iOS 平板电脑提供了一个基于摄像头的扫描器实现。 Blazor服务器支持基于浏览器的扫描,通过JavaScript访问摄像头,并将数据输入到同一个 BarcodeScanned 事件中。
  • AndroidiOS原生实现为移动开发者提供了相机扫描功能,后端使用相同的IronBarcode解码器。

对于云原生架构,验证和标签生成步骤可以作为Azure Functions运行,由队列消息触发,而桌面应用程序仅充当扫描仪输入网关。 当需要集中管理标签打印逻辑以进行合规性审核时,这种分离方式尤其有用。

下一步计划是什么?

使用IronBarcode构建 USB 条形码扫描器应用程序涉及四个具体阶段:通过突发时序检测捕获键盘楔形输入、通过 IronBarcode 的解码器验证扫描值、生成所需格式的响应标签以及使用并发队列处理大量扫描。 每个阶段都是独立的,可以单独进行测试。

在此基础上,可以考虑扩展应用程序,例如为批量处理场景添加多条形码读取功能、为基于图像的输入添加作物区域优化功能,或者为较旧的仓库设备添加MSI 条形码支持功能IronBarcode文档涵盖了所有支持的格式和高级读取器配置选项。

立即开始免费试用,获取开发许可证密钥,并开始将IronBarcode集成到您的扫描应用程序中。

常见问题解答

什么是 IronBarcode,它与 USB 条码扫描器有何关系?

IronBarcode 是一个库,可以让开发人员构建用于 USB 条码扫描的强大 C# 应用程序。它提供如条码验证、数据提取和条码生成等功能。

IronBarcode 能否验证来自 USB 扫描器的条码数据?

是的,IronBarcode 可以验证从 USB 扫描器捕获的条码数据,确保 C# 应用程序中的数据完整性和准确性。

IronBarcode 如何处理条码生成?

IronBarcode 可以实时生成新的条码,开发人员可以在其 C# 应用程序中轻松创建和打印条码。

IronBarcode 在 USB 条码扫描中是否支持错误处理?

是的,IronBarcode 包括全面的错误处理,可以管理 USB 条码扫描和处理过程中可能出现的常见问题。

IronBarcode 可以扫描哪些类型的条形码?

IronBarcode 支持扫描广泛的条码符号体系,包括二维码、UPC、Code 39 等,使其适用于各种应用程序。

IronBarcode 能否从扫描的条码中提取结构化信息?

是的,IronBarcode 可以从扫描的条码中提取结构化信息,有助于高效的数据处理和管理。

如何开始在 C# 中构建 USB 条码扫描器应用程序?

要开始在 C# 中构建 USB 条码扫描器应用程序,您可以利用 IronBarcode 以及提供的代码示例和文档来指导您的开发过程。

Jordi Bardia
软件工程师
Jordi 最擅长 Python、C# 和 C++,当他不在 Iron Software 利用这些技能时,他就在游戏编程。分享产品测试、产品开发和研究的责任,Jordi 在持续的产品改进中增加了巨大的价值。多样的经验使他面临挑战并保持投入,他表示这是在 Iron Software 工作的最喜欢的方面之一。Jordi 在佛罗里达州迈阿密长大,并在佛罗里达大学学习计算机科学和统计学。

钢铁支援团队

我们每周 5 天,每天 24 小时在线。
聊天
电子邮件
打电话给我