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
鍵盤楔實現中的突發計時器是關鍵細節。 它每次按鍵時都會重設,只有當字元停止到達時才會觸發——這意味著真正的鍵盤使用者若噴射過於緩慢則會被其不完整輸入丟棄而不是被視為條碼掃描。
如何處理多種掃描器品牌?
企業環境中經常在同一層樓運行混合的 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
在設置文件或資料庫中儲存這些配置意味著倉庫工作人員可以在不需要重新部署的情況下更換掃描器型號。 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"
對於容器化部署,IronBarcode 支持在 Linux 上的 Docker,對於雲端功能,支持 AWS Lambda 和 Azure 功能。
如何使用 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
對於供應鏈應用中使用的 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
此純邏輯檢查在編碼之前運行,以避免在高容量零售環境中每次掃描生成的圖像往返開銷。 根據 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
保存為 PDF 對於接受 PDF 輸入的網路共享標籤列印機特別有用。 您還可以導出為 SVG以獲得矢量品質的熱感應標籤輸出,或導出為字節流直接發送至標籤列印機 API。
IronBarcode 支持豐富的樣式自定義包括自定義顏色、邊距調整、可讀文字覆蓋,對於 QR code,標誌嵌入用於品牌標示的行動標籤。

如何構建完整的高容量掃描應用程式?
什麼是生產隊列為基礎的實現樣式?
對於每分鐘處理數十次掃描的應用程式,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
SemaphoreSlim 限制同時運行的驗證任務數量至邏輯處理器數,以防止突發掃描事件期間的失控執行緒建立。 BeginInvoke 調用將 UI 更新安全地編組回主執行緒。
如何為不同的掃描量調整性能?
BarcodeReaderOptions.Speed 屬性接受 ReadingSpeed.平衡 和 ReadingSpeed.詳細。 對於已知字串值的 USB 掃描器輸入,平衡 是合適的選擇——解碼器只需要確認格式,而不是在圖像中尋找條碼。 根據IronBarcode 的讀取速度文件,較快 模式跳過了一些變形糾正算法,這對於乾淨的掃描器輸出來說是安全的,但可能會錯過影像場景中的損壞條碼。
下表總結了每種速度模式何時使用:
| 速度模式 | 最佳適用於 | 權衡 |
|---|---|---|
| 較快 | 乾淨的 USB 掃描器輸入,高容量吞吐量 | 可能漏掉嚴重損壞或傾斜的條碼 |
| 平衡 | 混合輸入——USB 掃描器加上圖像匯入 | 中等 CPU 使用,優良的準確性 |
| 詳細 | 損壞標籤、低對比印刷、PDF 匯入 | 較高的 CPU 使用,最慢的處理速度 |
對於同時處理影像或 PDF 與 USB 掃描器輸入的應用程式,IronBarcode 可以從 PDF 文件中讀取條碼,以及多頁 TIFF 檔案使用相同的 API 表面。

如何在掃描應用程式中處理邊界情況和錯誤?
開發者應預料那些失效模式?
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 的內容在排除串行掃描器連接方面特別是ReadTimeout 和 WriteTimeout 設置方面非常有用。 在零售中的法規合規性方面,GS1 通用規範為每個應用標識符定義了有效值範圍。
如何擴展應用程式到行動和 Web 平台?
上面顯示的掃描器接口模式——IScannerInput 與 BarcodeScanned 事件——將硬體從處理邏輯中抽象出來。 替換實現允許相同的驗證和生成程式碼在不同平台上運行:
- .NET MAUI 提供了用於 Android 和 iOS 平板電腦的基於相機的掃描器實現,用作行動接收站
- Blazor Server 支持基於瀏覽器的掃描,JavaScript 相機存取餵入相同的
BarcodeScanned事件 - Android 和 iOS 原生實現使行動開發者具有相機掃描功能,後端使用相同的 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以及提供的程式碼範例和文件來指導您的開發過程。




