Skaner kodów kreskowych USB w C#: Zbuduj kompletną aplikację skanującą
Skanery BarCode'ów USB łączą się z aplikacjami C# jako standardowe urządzenia wejściowe klawiatury, wysyłając zeskanowane dane jako wpisane znaki, po których następuje naciśnięcie klawisza Enter. Takie działanie klucza klawiatury HID sprawia, że integracja jest prosta — aplikacja odbiera dane tekstowe bez konieczności stosowania specjalnego sterownika lub zestawu SDK. IronBarcode przetwarza te surowe dane wejściowe w celu sprawdzenia poprawności formatów, wyodrębnienia danych strukturalnych i wygenerowania kodów kreskowych odpowiedzi, zamieniając proste skanowanie w kompletny strumień danych dla systemów zarządzania zapasami, punktów sprzedaży detalicznej i systemów śledzenia logistycznego.
Operacje w handlu detalicznym, magazynowaniu i produkcji zależą od dokładnego i szybkiego skanowania BARCODE-ów. Gdy programista podłącza skaner USB do aplikacji Windows Forms lub WPF, skaner zachowuje się identycznie jak klawiatura — dane trafiają do pola TextBox, a naciśnięcie klawisza Enter sygnalizuje, że otrzymano pełny BARCODE. Wyzwaniem nie jest pozyskanie danych; przetwarza to poprawnie. Weryfikacja kodów kreskowych IronBarcode sprawdza integralność formatu, wyodrębnia pola, takie jak numery partii lub identyfikatory aplikacji GS1, i może natychmiast wygenerować nowy kod kreskowy w odpowiedzi.
Ten przewodnik krok po kroku opisuje tworzenie gotowej do użycia aplikacji C# do skanowania kodów kreskowych za pomocą skanera USB. Zainstalujesz bibliotekę, przechwycisz dane ze skanera, zweryfikujesz formaty kodów kreskowych, wygenerujesz etykiety odpowiedzi i zbudujesz procesor oparty na kolejce do obsługi dużych ilości danych. Każda sekcja zawiera kompletny, działający kod przeznaczony dla platformy .NET 10, w odpowiednich miejscach w stylu instrukcji najwyższego poziomu.
Jak działają skanery BarCode USB w języku C#?
Dłączego tryb Wedge klawiatury HID ułatwia integrację?
Większość skanerów BARCODE USB jest domyślnie skonfigurowana w trybie HID keyboard wedge. Po podłączeniu urządzenia do komputera z systemem Windows system operacyjny rejestruje je zarówno jako urządzenie pamięci USB (do konfiguracji), jak i klawiaturę (do wprowadzania danych). Po zeskanowaniu kodu kreskowego urządzenie przekształca zdekodowaną wartość kodu kreskowego na sekwencję znaków i wysyła ją do okna aplikacji, które ma aktualnie fokus, dodając na końcu znak powrotu karetki.
Z punktu widzenia programisty C# oznacza to, że nie potrzebujesz SDK dostawców, bibliotek COM ani specjalnych interfejsów API USB. Do przechwytywania danych wejściowych wystarczy standardowe pole tekstowe z procedurą obsługi zdarzenia KeyDown. Głównym wyzwaniem związanym z integracją jest odróżnienie danych wprowadzonych za pomocą skanera od prawdziwego pisania na klawiaturze. Skanery zazwyczaj przekazują wszystkie znaki w bardzo krótkim czasie — często poniżej 50 milisekund — podczas gdy podczas pisania na klawiaturze przez człowieka naciśnięcia klawiszy rozkładają się na setki milisekund. Synchronizacja impulsu to niezawodny sposób na odfiltrowanie przypadkowych naciśnięć klawiszy.
Profesjonalne skanery obsługują również tryby szeregowe (RS-232 lub wirtualny port COM) oraz bezpośrednie tryby USB HID, co zapewnia większą kontrolę nad znakami przedrostków/przyrostków oraz wyzwalaczami skanowania. Poniższy wzorzec interfejsu obsługuje oba przypadki:
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
Kluczowym szczegółem jest licznik czasu w implementacji klawiatury typu wedge. Resetuje się przy każdym naciśnięciu klawisza i uruchamia się tylko wtedy, gdy przestają napływać znaki — co oznacza, że w przypadku prawdziwych użytkowników klawiatury, którzy piszą powoli, ich niekompletne dane wejściowe zostaną odrzucone, a nie potraktowane jako skan BARCODE.
Jak radzisz sobie z wieloma markami skanerów?
W środowiskach Enterprise często na tym samym piętrze używa się skanerów firm Honeywell, Zebra (dawniej Symbol/Motorola) i Datalogic. Każdy dostawca ma swoje własne domyślne znaki końcowe, prędkości transmisji oraz konwencje dotyczące przedrostków i przyrostków. Model konfiguracyjny zapewnia elastyczność aplikacji:
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
Przechowywanie tych konfiguracji w pliku ustawień lub bazie danych oznacza, że pracownicy magazynu mogą wymieniać modele skanerów bez konieczności ponownego wdrażania. Pole ScannerType określa, która implementacja IScannerInput zostanie zainicjowana podczas uruchamiania.
Jak zainstalować IronBarcode w projekcie C#?
Jaki jest najszybszy sposób dodania IronBarcode za pośrednictwem NuGet?
Otwórz konsolę menedżera pakietów w Visual Studio i uruchom:
Install-Package IronBarCode
Install-Package IronBarCode
Alternatywnie można użyć interfejsu CLI platformy .NET:
dotnet add package IronBarCode
dotnet add package IronBarCode
Oba polecenia pobierają aktualną wersję z NuGet.org i dodają odwołanie do zestawu do pliku projektu. Biblioteka jest przeznaczona dla .NET Standard 2.0, więc działa na platformach od .NET Framework 4.6.2 do .NET 10 bez żadnych dodatkowych nakładek kompatybilnościowych.
Po instalacji należy ustawić klucz licencyjny przed wywołaniem jakiejkolwiek metody IronBarcode. W celu opracowania i oceny dostępny jest bezpłatny klucz próbny na stronie licencyjnej IronBarcode:
IronBarCode.License.LicenseKey = "YOUR-LICENSE-KEY";
IronBarCode.License.LicenseKey = "YOUR-LICENSE-KEY";
Imports IronBarCode
IronBarCode.License.LicenseKey = "YOUR-LICENSE-KEY"
W przypadku wdrożeń kontenerowych IronBarcode współpracuje z Dockerem w systemie Linux, a w przypadku funkcji chmurowych obsługuje AWS Lambda i Azure Functions.
Jak weryfikować zeskanowane BarCodes za pomocą IronBarcode?
Jakie jest właściwe podejście do weryfikacji formatu?
IronBarcode obsługuje ponad 30 symboli kodów kreskowych, w tym Code 128, EAN-13, Code 39, kody QR i Data Matrix. W przypadku aplikacji skanerów USB wzorzec walidacji ponownie koduje zeskanowany ciąg znaków jako obraz BARCODE i natychmiast odczytuje go za pomocą dekodera. Ta operacja round-trip potwierdza, że ciąg znaków jest prawidłową wartością dla zadeklarowanego formatu:
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
W przypadku BARCODE'ów GS1-128 stosowanych w aplikacjach łańcucha dostaw zeskanowany ciąg znaków zawiera prefiksy identyfikatorów aplikacji w nawiasach, takie jak (01) dla GTIN i (17) dla daty ważności. IronBarcode automatycznie analizuje te identyfikatory aplikacji po wpisaniu BarcodeEncoding.GS1_128.
Którą logikę sum kontrolnych EAN-13 powinni wdrożyć programiści?
Aplikacje kasowe w handlu detalicznym często muszą samodzielnie weryfikować cyfry kontrolne EAN-13 przed przekazaniem wartości do wyszukiwania cen. Suma kontrolna typu Luhn dla EAN-13 stosuje naprzemiennie wagi 1 i 3 w pierwszych 12 cyfrach:
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
Ta czysto logiczna kontrola jest przeprowadzana przed kodowaniem, aby uniknąć obciążenia związanego z generowaniem obrazu w obie strony dla każdego skanowania w środowisku detalicznym o dużym natężeniu ruchu. Zgodnie ze specyfikacją GS1 algorytm cyfry kontrolnej jest identyczny dla kodu UPC-A (12 cyfr) po usunięciu początkowego zera.
Jak generować BarCodes odpowiedzi na podstawie zeskanowanych danych wejściowych?
Kiedy aplikacja powinna tworzyć nowe BarCodes po skanowaniu?
Typowym schematem w procesie przyjmowania towarów w magazynie jest procedura "skanowania i ponownego etykietowania": przychodząca pozycja posiada BarCode dostawcy (często EAN-13 lub ITF-14), a system zarządzania magazynem musi wydrukować wewnętrzną etykietę Code 128 z własnymi kodami lokalizacji i partii. Funkcje generowania IronBarcode pozwalają to zrobić w kilku wierszach:
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
Zapisywanie w formacie PDF jest szczególnie przydatne w przypadku drukarek etykiet, które akceptują pliki PDF przesyłane przez udział sieciowy. Można również wyeksportować plik jako SVG w celu uzyskania etykiety termicznej o jakości wektorowej lub wyeksportować jako strumień bajtów w celu wysłania bezpośrednio do interfejsu API drukarki etykiet.
IronBarcode obsługuje szerokie możliwości dostosowywania stylu, w tym niestandardowe kolory, regulację marginesów, nakładki tekstowe czytelne dla człowieka, a w przypadku kodów QR — osadzanie logo na etykietach mobilnych oznaczonych marką.

Jak zbudować kompletną aplikację do skanowania dużych ilości dokumentów?
Jak wygląda implementacja oparta na kolejce produkcyjnej?
W przypadku aplikacji przetwarzających dziesiątki skanów na minutę prosty synchroniczny handler w wątku interfejsu użytkownika staje się wąskim gardłem. Poniższy wzorzec oddziela przechwytywanie skanów od przetwarzania za pomocą ConcurrentQueue<t> i pętli przetwarzania w tle. Asynchroniczny interfejs API IronBarcode obsługuje walidację bez blokowania interfejsu użytkownika:
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.Zrównoważony,
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.Zrównoważony,
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
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.Zrównoważony,
.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 ogranicza liczbę równoczesnych zadań walidacji do liczby procesorów logicznych, zapobiegając niekontrolowanemu tworzeniu wątków podczas intensywnych operacji skanowania. BeginInvoke bezpiecznie przekazuje aktualizacje interfejsu użytkownika z powrotem do głównego wątku.
Jak dostosować wydajność do różnych wolumenów skanowania?
Właściwość BarcodeReaderOptions.Speed akceptuje wartości ReadingSpeed.Szybciej, ReadingSpeed.Zrównoważony oraz ReadingSpeed.Szczegółowe. W przypadku danych wejściowych ze skanera USB, gdzie wartość ciągu znaków jest już znana, odpowiednie jest użycie Zrównoważony — dekoder musi jedynie potwierdzić format, a nie lokalizować BARCODE na obrazie. Zgodnie z dokumentacją IronBarcode dotyczącą szybkości odczytu, tryb Szybciej pomija niektóre algorytmy korekcji zniekształceń, co jest bezpieczne w przypadku czystego wyniku skanera, ale może spowodować pominięcie uszkodzonych kodów kreskowych w scenariuszach opartych na obrazach.
Poniższa tabela podsumowuje, kiedy należy używać poszczególnych trybów prędkości:
| Tryb szybki | Najlepsze dla | Kompromis |
|---|---|---|
| Szybciej | Czyste dane wejściowe ze skanera USB, duża przepustowość | Może pomijać poważnie uszkodzone lub przekrzywione BARCODES |
| Zrównoważony | Mieszane dane wejściowe — skaner USB Plus oraz import obrazów | Umiarkowane obciążenie procesora, dobra dokładność |
| Szczegółowe | Uszkodzone etykiety, druk o niskim kontraście, import plików PDF | Wyższe zużycie procesora, najniższa przepustowość |
W przypadku aplikacji, które oprócz danych z skanera USB przetwarzają również obrazy lub pliki PDF, IronBarcode może odczytywać kody kreskowe z dokumentów PDF i wielostronicowych plików TIFF przy użyciu tego samego interfejsu API.




