跳至頁尾內容
使用IRONBARCODE

C# USB條碼掃描器:構建完整的掃描應用程式

USB 條碼掃描器作為標準鍵盤輸入裝置連接到 C# 應用程式,將掃描的資料作為輸入字元並附帶迴車按鍵發送。 這種 HID 鍵盤楔模式的行為使整合變得簡單,您的應用程式收到文字輸入而不需要任何特殊的驅動程式或 SDK。 IronBarcode 處理這些原始輸入來驗證格式、提取結構化資料,並生成回應條碼,將簡單的掃描事件轉變為完整的資料管道,用於庫存管理、零售銷售點和物流跟蹤系統。

零售、倉儲和製造運營都依賴於準確且快速的條碼掃描。 當開發者將 USB 掃描器連接到 Windows Forms 或 WPF 應用程式時,掃描器的表現如同鍵盤一樣,資料到達 TextBox,按下 Enter 表示已接收到完整條碼。 挑戰不在於捕捉資料; 而是在於正確地處理它。 IronBarcode 的條碼驗證檢查格式完整性,提取批次號或 GS1 應用識別碼等字段,並可立即生成新條碼作為回應。

本指南逐步說明如何構建可以投入生產的 C# USB 條碼掃描器應用程式。您將安裝程式庫、捕獲掃描器輸入、驗證條碼格式、生成回應標籤,並組裝高容量的基於隊列的處理器。 每個部分包含完整、可運行的程式碼,針對 .NET 10 的頂級語句風格。

USB 條碼掃描器與 C# 如何協同工作?

為什麼 HID 鍵盤楔模式使整合變得簡單?

大多數 USB 條碼掃描器出廠時預設配置為 HID 鍵盤楔模式。 當您將其插入 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

鍵盤楔實現中的突發計時器是關鍵細節。 它每次按鍵時都會重設,只有當字元停止到達時才會觸發——這意味著真正的鍵盤使用者若噴射過於緩慢則會被其不完整輸入丟棄而不是被視為條碼掃描。

如何處理多種掃描器品牌?

企業環境中經常在同一層樓運行混合的 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 BarCode

或者,使用.NET CLI:

dotnet add package BarCode

這兩個命令從 NuGet.org 提取當前發佈版本並將組件引用新增到您的專案文件中。程式庫目標是 .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 功能

如何使用 IronBarcode 驗證掃描的條碼?

格式驗證的正確方法是什麼?

IronBarcode supports over 30 barcode symbologies including Code 128, EAN-13, Code 39, QR codes, and 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 條碼,掃描的字串包括在括號中的是應用標識符前綴,例如 (01) 用於 GTIN 和 (17) 用於到期日。 當您指定 BarcodeEncoding.GS1_128 時,IronBarcode 自動解析這些應用標識符。

開發者應實施哪種 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 規範,當您刪除開頭的零時,UPCA(12 位)的校驗位算法與 UPC-A 相同。

如何從掃描輸入中生成回應條碼?

應用程式何時在掃描後建立新條碼?

倉庫收貨中的常見模式是"掃描和重新標籤"工作流程:進貨商品帶有供應商條碼(通常是 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 支持豐富的樣式自定義包括自定義顏色、邊距調整、可讀文字覆蓋,對於 QR code,標誌嵌入用於品牌標示的行動標籤。

Windows Forms 應用程式介面展示 IronBarcode 的雙條碼生成功能。 介面顯示對庫存編號 'INV-20250917-helloworld' 成功生成 Code 128 線性條碼和 QR code。 頂部的輸入區域允許使用者輸入自定義庫存程式碼,並使用生成按鈕建立條碼。 成功訊息項目成功處理 - 已生成標籤確認操作已完成。 Code 128 條碼標示為主要庫存跟蹤格式,而下面的 QR code 則標示為行動友好的替代方案。該應用程式使用專業的灰色背景和清晰的視覺層次結構,展示了如何使用 IronBarcode 使開發者能夠建立多格式條碼生成系統以管理完整的庫存系統。

如何構建完整的高容量掃描應用程式?

什麼是生產隊列為基礎的實現樣式?

對於每分鐘處理數十次掃描的應用程式,UI 執行緒上的簡單同步處理程式會成為瓶頸。 下面的模式使用 ConcurrentQueue<t> 将掃描捕獲與處理解耦,並設置後台處理迴圈。IronBarcode 的異步 API處理驗證而不會阻礙 UI:

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.Balanced,
            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.Balanced,
            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
Imports System.Threading
Imports System.Threading.Tasks

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.Balanced,
            .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.詳細。 對於已知字串值的 USB 掃描器輸入,平衡 是合適的選擇——解碼器只需要確認格式,而不是在圖像中尋找條碼。 根據IronBarcode 的讀取速度文件較快 模式跳過了一些變形糾正算法,這對於乾淨的掃描器輸出來說是安全的,但可能會錯過影像場景中的損壞條碼。

下表總結了每種速度模式何時使用:

IronBarcode 讀取速度模式及其適用情況
速度模式 最佳適用於 權衡
較快 乾淨的 USB 掃描器輸入,高容量吞吐量 可能漏掉嚴重損壞或傾斜的條碼
平衡 混合輸入——USB 掃描器加上圖像匯入 中等 CPU 使用,優良的準確性
詳細 損壞標籤、低對比印刷、PDF 匯入 較高的 CPU 使用,最慢的處理速度

對於同時處理影像或 PDF 與 USB 掃描器輸入的應用程式,IronBarcode 可以從 PDF 文件中讀取條碼,以及多頁 TIFF 檔案使用相同的 API 表面。

階檯專業的 Windows Forms 條碼掃描器應用程式展示 IronBarcode 即時庫存跟蹤功能。 介面設計有整潔的雙面板設計,帶有精緻的深藍色標題。 左側面板顯示掃描歷史列表,顯示四個成功掃描的庫存項目(INV-001 至 INV-004),帶有精確的時間戳和掃描狀態指示。 每個項目包括詳細的市資料如條碼型別和信心水平。 右側面板顯示動態生成的摘要條碼,展示項目:4並具有專業樣式和適當邊距。 底部的動作按鈕包括清除列表、導出資料和列印標籤以完成庫存管理。 狀態欄顯示掃描器:已連接|模式:連續|最後掃描:2秒前,展示應用程式的實時監控功能以及 IronBarcode 對生產庫存系統提供的專業企業級設計。

如何在掃描應用程式中處理邊界情況和錯誤?

開發者應預料那些失效模式?

USB 掃描器應用程式以可預測的方式失效。 最常見的問題及其緩解措施如下:

掃描器斷開——當 USB 掃描器拔出時,鍵盤楔 TextBox 失去了其虛擬鍵盤。 最簡單的緩解措施是負責檢查 _inputBox.Focused 並在掃描器仍在連接的 HID 裝置清單中重新聚焦它。 對於串行掃描器,SerialPort.GetPortNames() 檢測重新連接。

模糊的條碼格式——有些產品攜帶的條碼在多個標誌中有效。 例如,一個12位字串同時是合法的 UPC-A 和 Code 128。在 BarcodeReaderOptions 中指定 ExpectBarcodeTypes 可將解碼器限制到您預期格式,消除歧義。 IronBarcode 故障排解指南涵蓋格式特定的識別技巧。

無效格式的例外狀況——如果 BarcodeWriter.CreateBarcode 收到違反選擇編碼規則的字串(例如,數字專用 EAN-13 字段中的字母字元),它會拋出一個 IronBarCode.Exceptions.InvalidBarcodeException。 將調用包裝在 try-catch 中並退回到字串專用驗證路徑可保持應用程式運行。

按鍵計時衝突——在操作員也在同一 TextBox 中手動鍵入的環境中,前面描述的突發計時方法是主要防禦。 次要防護是最小長度:大多數真實條碼至少有 8 個字元,因此可以將短於該長度的字串視為鍵盤輸入。

Microsoft .NET 文件中關於 System.IO.Ports.SerialPort 的內容在排除串行掃描器連接方面特別是ReadTimeoutWriteTimeout 設置方面非常有用。 在零售中的法規合規性方面,GS1 通用規範為每個應用標識符定義了有效值範圍。

如何擴展應用程式到行動和 Web 平台?

上面顯示的掃描器接口模式——IScannerInputBarcodeScanned 事件——將硬體從處理邏輯中抽象出來。 替換實現允許相同的驗證和生成程式碼在不同平台上運行:

  • .NET MAUI 提供了用於 Android 和 iOS 平板電腦的基於相機的掃描器實現,用作行動接收站
  • Blazor Server 支持基於瀏覽器的掃描,JavaScript 相機存取餵入相同的 BarcodeScanned 事件
  • AndroidiOS 原生實現使行動開發者具有相機掃描功能,後端使用相同的 IronBarcode 解碼器

對於雲原生架構,可將驗證和標籤生成步驟作為由佇列消息觸發的 Azure Functions 來運行,桌面應用程式僅作為掃描器輸入閘道。 當標籤列印邏輯必須集中化以進行合規審核時,這種分離特別有用。

您的下一步是什麼?

using 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支援掃描廣泛的條碼符號學,包括QR碼、UPC、Code 39等,使其適用於各種應用程式。

IronBarcode能夠從掃描的條碼中提取結構化資訊嗎?

是的,IronBarcode可以從掃描條碼中提取結構化資訊,幫助提高資料處理和管理效率。

如何開始構建C#中的USB條碼掃描器應用程式?

要開始構建C#中的USB條碼掃描器應用程式,您可以利用IronBarcode以及提供的程式碼範例和文件來指導您的開發過程。

Curtis Chau
技術作家

Curtis Chau擁有Carleton大學的電腦科學學士學位,專精於前端開發,擁有Node.js、TypeScript、JavaScript和React的專業知識。Curtis熱衷於建立直觀且美觀的使用者介面,喜愛使用現代框架並建立結構良好、視覺吸引力的手冊。

除了開發,Curtis對物聯網(IoT)有濃厚的興趣,探索創新的方法來整合硬體和軟體。在空閒時間,他喜歡玩遊戲和建立Discord機器人,結合他對技術的熱愛與創造力。

Iron 支援團隊

我們線上24小時,每週5天。
聊天
電子郵件
給我打電話