跳過到頁腳內容
使用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

鍵盤楔形實作中的突發計時器是關鍵細節。 每次按鍵都會重置,並且只有在字元停止輸入時才會觸發——這意味著打字速度較慢的真正鍵盤用戶,其未完成的輸入將被丟棄,而不是被當作條碼掃描處理。

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

企業環境中經常在同一樓層混合運行 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.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 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 條碼被標記為主要的庫存追蹤格式,而下方的二維碼則被標記為行動友善替代方案。該應用程式採用專業的灰色背景和清晰的視覺層次結構,展示了 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掃描器加影像導入 CPU佔用率適中,準確率高
詳細的 標籤破損、列印對比度低、PDF導入 CPU 使用率高,吞吐量最慢

對於除了 USB 掃描器輸入外,還處理影像或 PDF 的應用程序,IronBarcode 可以使用相同的 API 介面從 PDF 文件和多頁 TIFF 檔案中讀取條碼

![專業 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 支援掃描各種條碼符號,包括 QR 碼、UPC、Code 39 等,這使其在各種應用中具有多功能性。

IronBarcode 能夠提取掃描條碼中的結構化信息嗎?

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

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

要開始在 C# 中構建 USB 條碼掃描器應用程式,您可以利用 IronBarcode,並結合提供的代碼示例和文檔來指導您的開發過程。

Jordi Bardia
軟體工程師
Jordi 在 Python、C# 和 C++ 上最得心應手,當他不在 Iron Software 展現技術時,便在做遊戲編程。在分担產品测测试,產品開發和研究的责任時,Jordi 為持续的產品改進增值。他说这种多样化的经验使他受到挑战并保持参与, 而这也是他与 Iron Software 中工作一大乐趣。Jordi 在佛罗里达州迈阿密长大,曾在佛罗里达大学学习计算机科学和统计学。

鋼鐵支援團隊

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