跳至页脚内容
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();
}
Imports System
Imports System.Text
Imports System.Windows.Forms
Imports System.IO.Ports

Public Interface IScannerInput
    Event BarcodeScanned As EventHandler(Of String)
    Sub StartListening()
    Sub StopListening()
End Interface

Public Class KeyboardWedgeScanner
    Implements IScannerInput

    Public Event BarcodeScanned As EventHandler(Of String) Implements IScannerInput.BarcodeScanned
    Private ReadOnly _inputBox As TextBox
    Private ReadOnly _burstTimer As Timer
    Private ReadOnly _buffer As New StringBuilder()

    Public Sub New(inputBox As TextBox)
        _inputBox = inputBox
        _burstTimer = New Timer With {.Interval = 80}
        AddHandler _burstTimer.Tick, AddressOf OnBurstTimeout
        AddHandler _inputBox.KeyPress, AddressOf OnKeyPress
    End Sub

    Private Sub OnKeyPress(sender As Object, e As KeyPressEventArgs)
        If e.KeyChar = ChrW(Keys.Enter) Then
            _burstTimer.Stop()
            Dim value As String = _buffer.ToString().Trim()
            _buffer.Clear()
            If value.Length > 0 Then
                RaiseEvent BarcodeScanned(Me, value)
            End If
        Else
            _buffer.Append(e.KeyChar)
            _burstTimer.Stop()
            _burstTimer.Start()
        End If
        e.Handled = True
    End Sub

    Private Sub OnBurstTimeout(sender As Object, e As EventArgs)
        _burstTimer.Stop()
        _buffer.Clear() ' incomplete burst -- discard
    End Sub

    Public Sub StartListening() Implements IScannerInput.StartListening
        _inputBox.Focus()
    End Sub

    Public Sub StopListening() Implements IScannerInput.StopListening
        _inputBox.Enabled = False
    End Sub
End Class

Public Class SerialPortScanner
    Implements IScannerInput

    Public Event BarcodeScanned As EventHandler(Of String) Implements IScannerInput.BarcodeScanned
    Private ReadOnly _port As SerialPort

    Public Sub New(portName As String, Optional baudRate As Integer = 9600)
        _port = New SerialPort(portName, baudRate)
        AddHandler _port.DataReceived, AddressOf OnDataReceived
    End Sub

    Private Sub OnDataReceived(sender As Object, e As SerialDataReceivedEventArgs)
        Dim data As String = _port.ReadLine().Trim()
        If data.Length > 0 Then
            RaiseEvent BarcodeScanned(Me, data)
        End If
    End Sub

    Public Sub StartListening() Implements IScannerInput.StartListening
        _port.Open()
    End Sub

    Public Sub StopListening() Implements IScannerInput.StopListening
        _port.Close()
    End Sub
End Class
$vbLabelText   $csharpLabel

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

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

Enterprise环境中经常在同一楼层混合运行 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" }
        }
    };
}
Option Strict On



Public Class ScannerConfiguration
    Public Property ScannerType As String = "KeyboardWedge"
    Public Property PortName As String = "COM3"
    Public Property BaudRate As Integer = 9600
    Public Property Terminator As String = vbCrLf
    Public Property EnableBeep As Boolean = True
    Public Property BrandSettings As Dictionary(Of String, String) = New Dictionary(Of String, String)()

    Public Shared Function GetHoneywellConfig() As ScannerConfiguration
        Return New ScannerConfiguration() With {
            .ScannerType = "Serial",
            .BaudRate = 115200,
            .BrandSettings = New Dictionary(Of String, String) From {
                {"Prefix", "STX"},
                {"Suffix", "ETX"},
                {"TriggerMode", "Manual"}
            }
        }
    End Function

    Public Shared Function GetZebraConfig() As ScannerConfiguration
        Return New ScannerConfiguration() With {
            .ScannerType = "KeyboardWedge",
            .BrandSettings = New Dictionary(Of String, String) From {
                {"ScanMode", "Continuous"},
                {"BeepVolume", "High"}
            }
        }
    End Function
End Class
$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";
Imports IronBarCode

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; } = "";
}
Imports System
Imports System.Linq
Imports System.Threading.Tasks

Public Class BarcodeValidator
    Public Async Function ValidateAsync(scannedText As String, Optional preferredFormat As BarcodeEncoding = BarcodeEncoding.Code128) As Task(Of ValidationResult)
        Dim result As New ValidationResult With {.RawInput = scannedText}

        Try
            Dim barcode = BarcodeWriter.CreateBarcode(scannedText, preferredFormat)
            Dim readResults = Await BarcodeReader.ReadAsync(barcode.ToBitmap())

            If readResults.Any() Then
                Dim 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."
            End If
        Catch ex As Exception
            result.IsValid = False
            result.Error = ex.Message
        End Try

        Return result
    End Function
End Class

Public Class ValidationResult
    Public Property RawInput As String = ""
    Public Property IsValid As Boolean
    Public Property Format As BarcodeEncoding
    Public Property Value As String = ""
    Public Property Confidence As Single
    Public Property Error As String = ""
End Class
$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');
}
Public Shared Function ValidateEan13Checksum(value As String) As Boolean
    If value.Length <> 13 OrElse Not value.All(AddressOf Char.IsDigit) Then
        Return False
    End If

    Dim sum As Integer = 0
    For i As Integer = 0 To 11
        Dim digit As Integer = AscW(value(i)) - AscW("0"c)
        sum += If(i Mod 2 = 0, digit, digit * 3)
    Next

    Dim expectedCheck As Integer = (10 - (sum Mod 10)) Mod 10
    Return expectedCheck = (AscW(value(12)) - AscW("0"c))
End Function
$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;
    }
}
Imports System.IO
Imports System.Threading.Tasks

Public Class InventoryLabelGenerator
    Private ReadOnly _outputDirectory As String

    Public Sub New(outputDirectory As String)
        _outputDirectory = outputDirectory
        Directory.CreateDirectory(_outputDirectory)
    End Sub

    Public Async Function GenerateLabelAsync(internalCode As String, locationCode As String) As Task(Of String)
        Dim fullCode As String = $"{internalCode}|{locationCode}|{DateTime.UtcNow:yyyyMMdd}"

        ' Primary Code 128 label for scanners
        Dim 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
        Dim qrCode = BarcodeWriter.CreateQrCode(fullCode)
        qrCode.ResizeTo(200, 200)
        qrCode.SetMargins(8)

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

        Await Task.Run(Sub()
                           linearBarcode.SaveAsPng(pngPath)
                           linearBarcode.SaveAsPdf(pdfPath)
                       End Sub)

        Return pngPath
    End Function
End Class
$vbLabelText   $csharpLabel

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

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

![Windows Forms 应用程序界面,演示 IronBarcode 的双条形码生成功能。 界面显示已成功生成库存编号为"INV-20250917-helloworld"的 Code 128 线性条形码和二维码。 顶部的输入字段允许用户输入自定义库存代码,并有一个"生成"按钮来创建条形码。 成功消息"项目处理成功 - 已生成标签"确认操作已完成。 Code 128 条码被标记为主要的库存跟踪格式,而下方的二维码则被标记为移动友好型替代方案。该应用程序采用Professional的灰色背景和清晰的视觉层次结构,展示了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);
    }
}
Imports IronBarCode
Imports System.Collections.Concurrent

Public Partial Class HighVolumeScanner
    Inherits Form

    Private ReadOnly _scanQueue As New ConcurrentQueue(Of (Data As String, Timestamp As DateTime))()
    Private ReadOnly _semaphore As SemaphoreSlim
    Private ReadOnly _cts As New CancellationTokenSource()
    Private _scanner As IScannerInput

    Public Sub New()
        InitializeComponent()
        IronBarCode.License.LicenseKey = "YOUR-LICENSE-KEY"
        _semaphore = New SemaphoreSlim(Environment.ProcessorCount)
        InitializeScanner()
        _ = RunProcessingLoopAsync()
    End Sub

    Private Sub InitializeScanner()
        _scanner = If(System.IO.Ports.SerialPort.GetPortNames().Any(),
                      New SerialPortScanner("COM3", 115200),
                      New KeyboardWedgeScanner(txtScannerInput))

        AddHandler _scanner.BarcodeScanned, Sub(_, barcode)
                                                _scanQueue.Enqueue((barcode, DateTime.UtcNow))
                                            End Sub

        _scanner.StartListening()
    End Sub

    Private Async Function RunProcessingLoopAsync() As Task
        While Not _cts.Token.IsCancellationRequested
            Dim scan As (Data As String, Timestamp As DateTime)
            If _scanQueue.TryDequeue(scan) Then
                Await _semaphore.WaitAsync(_cts.Token)
                _ = Task.Run(Async Function()
                                 Try
                                     Await ProcessScanAsync(scan.Data, scan.Timestamp)
                                 Finally
                                     _semaphore.Release()
                                 End Try
                             End Function, _cts.Token)
            Else
                Await Task.Delay(10, _cts.Token)
            End If
        End While
    End Function

    Private Async Function ProcessScanAsync(rawData As String, scanTime As DateTime) As Task
        Dim options As New BarcodeReaderOptions With {
            .Speed = ReadingSpeed.均衡,
            .ExpectMultipleBarcodes = False,
            .ExpectBarcodeTypes = BarcodeEncoding.Code128 Or BarcodeEncoding.QRCode,
            .MaxParallelThreads = 1
        }

        Dim testBarcode = BarcodeWriter.CreateBarcode(rawData, BarcodeEncoding.Code128)
        Dim results = Await BarcodeReader.ReadAsync(testBarcode.ToBitmap(), options)

        If results.Any() Then
            Dim item = results.First()
            BeginInvoke(Sub() UpdateInventoryDisplay(item.Value, scanTime))
        Else
            BeginInvoke(Sub() LogRejectedScan(rawData, scanTime))
        End If
    End Function

    Protected Overrides Sub OnFormClosing(e As FormClosingEventArgs)
        _cts.Cancel()
        _scanner.StopListening()
        MyBase.OnFormClosing(e)
    End Sub
End Class
$vbLabelText   $csharpLabel

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

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

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

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

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

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

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

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

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

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 个字符,因此短于此长度的字符串可以视为键盘输入。

在排查串行扫描仪连接问题时,有关System.IO.Ports.SerialPort的 Microsoft .NET 文档非常有用,特别是关于 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 小时在线。
聊天
电子邮件
打电话给我