如何在 C# 中設置錯誤修正 | IronQR
從ZXing.Net.MAUI移轉至IronBarcode
本指南為.NET MAUI開發者提供從ZXing.Net.MAUI到IronBarcode的完整遷移路徑。 它涵蓋了團隊進行此遷移的原因、用於決策的功能比較、替換套件和更新專案文件的機械步驟、每個主要使用模式的前後程式碼範例、API轉換參考、為遷移期間出現的問題提供疑難解答部份、一個用於追蹤進度的遷移檢查清單,以及遷移所帶來的結果總結。
為何從ZXing.Net.MAUI移轉
遷移離開ZXing.Net.MAUI的決定通常是由一個或多個具體專案情況引發的。 這些不是風格偏好,而是程式庫架構或維護狀況無法滿足需求的情況。
Windows MAUI不支援: ZXing.Net.MAUI沒有Windows相機實施,也沒有公開計劃建構一個。 該程式庫是圍繞iOS和Android平台相機API構建的。如果一個MAUI專案在初始構建後新增Windows目標(這是在以移動裝置優先的團隊中常見的模式)——ZXing.Net.MAUI完全不能為該目標提供服務。 沒有替代方案,沒有後備計劃,也沒有變通辦法。
iPhone 15 Pro自動對焦問題: Redth/ZXing.Net.Maui的GitHub問題跟蹤器記錄了iPhone 15 Pro和Pro Max裝置(CameraBarcodeReaderView時無法實現穩定的條碼檢測對焦。 條碼在鏡頭畫面中可見,但自動對焦系統無法鎖定到足夠清晰的程度以提取結果。 唯一記錄的減輕措施是指導使用者手動調整裝置和條碼之間的距離——這項指示需要明顯的使用者介面支持和使用者的耐心,這在生產工作流程中是不被接受的結果。
相機資源洩露: IDisposable。 當使用者導航離開掃描頁面時,相機資源未通過標準的處理模式釋放。 記錄的應對方法是設定OnDisappearing()中,這減少了影響,但不能正式釋放相機。 頻繁在掃描頁面上來回導航的應用累積的資源消耗可能表現為記憶體增長、電池消耗和返回掃描頁面時偶發的相機初始化失敗。
格式規格靜默失敗: ZXing.Net.MAUI繼承了ZXing.Net的需求,在掃描開始之前宣告每一個要掃描的條碼格式。 即便在相機畫面中明顯可見的格式,從BarcodeReaderOptions.Formats省略也被靜默忽略。 未提前預見到此格式的開發者,看不見錯誤——應用程式根本偵測不到任何東西。 在條碼格式由外部供應商、客戶或第三方系統控制的環境中,這種靜默失誤成為一個持續的支持問題。
Pre-1.0穩定度: ZXing.Net.MAUI發行於v0.7.4——在NuGet上是一個穩定版本但在語義版本控制下仍為1.0之前。 在1.0之前,小版本API變更仍可能發生,修補頻率取決於社區維護者的可用性,而且沒有商業支援SLA。 對於受制於依賴性審核或軟體構成分析的企業應用程式,沒有商業支援的1.0之前社區程式庫可能無法通過審批過程。
基本問題
結構問題在於CameraBarcodeReaderView將開發者鎖定到相機為中心的事件迴圈中,具有手動生命週期管理和固定格式清單。對於iOS和Android以外的已知條碼格式的基本相機掃描,架構達到了其邊界:
// ZXing.Net.MAUI: event loop, format list, lifecycle boilerplate on every scan page
public partial class ScannerPage : ContentPage
{
public BarcodeReaderOptions ReaderOptions { get; }
public ScannerPage()
{
InitializeComponent();
ReaderOptions = new BarcodeReaderOptions
{
Formats = BarcodeFormats.QRCode |
BarcodeFormats.Code128 |
BarcodeFormats.Ean13 |
BarcodeFormats.UpcA,
TryHarder = true,
AutoRotate = true
};
BindingContext = this;
}
private void OnBarcodesDetected(object sender, BarcodeDetectionEventArgs e)
{
MainThread.BeginInvokeOnMainThread(() =>
{
foreach (var barcode in e.Results)
ResultLabel.Text = $"{barcode.Format}: {barcode.Value}";
});
CameraView.IsDetecting = false;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
CameraView.IsDetecting = false; // Required — no Dispose() available
}
protected override void OnAppearing()
{
base.OnAppearing();
CameraView.IsDetecting = true;
}
}
// ZXing.Net.MAUI: event loop, format list, lifecycle boilerplate on every scan page
public partial class ScannerPage : ContentPage
{
public BarcodeReaderOptions ReaderOptions { get; }
public ScannerPage()
{
InitializeComponent();
ReaderOptions = new BarcodeReaderOptions
{
Formats = BarcodeFormats.QRCode |
BarcodeFormats.Code128 |
BarcodeFormats.Ean13 |
BarcodeFormats.UpcA,
TryHarder = true,
AutoRotate = true
};
BindingContext = this;
}
private void OnBarcodesDetected(object sender, BarcodeDetectionEventArgs e)
{
MainThread.BeginInvokeOnMainThread(() =>
{
foreach (var barcode in e.Results)
ResultLabel.Text = $"{barcode.Format}: {barcode.Value}";
});
CameraView.IsDetecting = false;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
CameraView.IsDetecting = false; // Required — no Dispose() available
}
protected override void OnAppearing()
{
base.OnAppearing();
CameraView.IsDetecting = true;
}
}
Imports ZXing.Net.MAUI
Public Partial Class ScannerPage
Inherits ContentPage
Public ReadOnly Property ReaderOptions As BarcodeReaderOptions
Public Sub New()
InitializeComponent()
ReaderOptions = New BarcodeReaderOptions With {
.Formats = BarcodeFormats.QRCode Or
BarcodeFormats.Code128 Or
BarcodeFormats.Ean13 Or
BarcodeFormats.UpcA,
.TryHarder = True,
.AutoRotate = True
}
BindingContext = Me
End Sub
Private Sub OnBarcodesDetected(sender As Object, e As BarcodeDetectionEventArgs)
MainThread.BeginInvokeOnMainThread(Sub()
For Each barcode In e.Results
ResultLabel.Text = $"{barcode.Format}: {barcode.Value}"
Next
End Sub)
CameraView.IsDetecting = False
End Sub
Protected Overrides Sub OnDisappearing()
MyBase.OnDisappearing()
CameraView.IsDetecting = False ' Required — no Dispose() available
End Sub
Protected Overrides Sub OnAppearing()
MyBase.OnAppearing()
CameraView.IsDetecting = True
End Sub
End Class
IronBarcode用一個按鈕點擊的單個異步呼叫取代了事件迴圈,完全消除了格式配置,並從頁面中消除了生命週期管理:
// NuGet: dotnet add package IronBarcode
// IronBarcode: stateless, all platforms, auto-detection, no lifecycle boilerplate
using IronBarCode;
public partial class ScannerPage : ContentPage
{
public ScannerPage() => InitializeComponent();
private async void ScanButton_Clicked(object sender, EventArgs e)
{
var photo = await MediaPicker.CapturePhotoAsync();
if (photo == null) return;
using var stream = await photo.OpenReadAsync();
using var ms = new MemoryStream();
await stream.CopyToAsync(ms);
var results = BarcodeReader.Read(ms.ToArray());
ResultLabel.Text = results.FirstOrDefault()?.Value ?? "No barcode found";
}
//否OnAppearing / OnDisappearing needed
}
// NuGet: dotnet add package IronBarcode
// IronBarcode: stateless, all platforms, auto-detection, no lifecycle boilerplate
using IronBarCode;
public partial class ScannerPage : ContentPage
{
public ScannerPage() => InitializeComponent();
private async void ScanButton_Clicked(object sender, EventArgs e)
{
var photo = await MediaPicker.CapturePhotoAsync();
if (photo == null) return;
using var stream = await photo.OpenReadAsync();
using var ms = new MemoryStream();
await stream.CopyToAsync(ms);
var results = BarcodeReader.Read(ms.ToArray());
ResultLabel.Text = results.FirstOrDefault()?.Value ?? "No barcode found";
}
//否OnAppearing / OnDisappearing needed
}
Imports IronBarCode
Public Partial Class ScannerPage
Inherits ContentPage
Public Sub New()
InitializeComponent()
End Sub
Private Async Sub ScanButton_Clicked(sender As Object, e As EventArgs)
Dim photo = Await MediaPicker.CapturePhotoAsync()
If photo Is Nothing Then Return
Using stream = Await photo.OpenReadAsync()
Using ms As New MemoryStream()
Await stream.CopyToAsync(ms)
Dim results = BarcodeReader.Read(ms.ToArray())
ResultLabel.Text = If(results.FirstOrDefault()?.Value, "No barcode found")
End Using
End Using
End Sub
'否OnAppearing / OnDisappearing needed
End Class
IronBarcode對比ZXing.Net.MAUI:功能比較
| 功能 | ZXing.Net.MAUI | IronBarcode |
|---|---|---|
| 發佈狀態 | 穩定,Pre-1.0(v0.7.4) | 穩定,商業發佈 |
| iOS MAUI | 是(iPhone 15 Pro對焦壞) | 是 |
| Android MAUI | 是(相機1.5.0構建問題) | 是 |
| Windows MAUI | 不支持 | 是 |
| macOS MAUI | 不支持 | 是 |
| 伺服器端/ASP.NET Core | 否 | 是 |
| 實時相機取景器 | 是 | 否(MediaPicker系統UI) |
| 需要格式規格 | 是 | 否(自動偵測,50+格式) |
| 相機生命週期管理 | 手動(IsDetecting) | 不適用 |
| Dispose() 實施 | 否 | 不適用—無狀態 |
| iPhone 15 Pro 自動對焦 | 壞(已記錄) | 不適用 |
| PDF條碼提取 | 否 | 是 |
| 檔案路徑輸入 | 否(僅相機) | 是 |
| 損壞條碼恢復 | 僅限TryHarder | 是(由ML驅動) |
| 條碼生成 | 是(通過ZXing.Net) | 是 |
| 商業支持 | None | 是 |
| 許可證 | MIT(免費) | 商業 |
快速開始
步驟1:移除ZXing.Net.Maui.Controls並清理MauiProgram.cs
移除ZXing.Net.MAUI NuGet套件:
dotnet remove package ZXing.Net.Maui.Controls
dotnet remove package ZXing.Net.Maui.Controls
ZXing.Net.MAUI需要在MauiProgram.cs中進行一次性註冊呼叫。 如果存在請移除此行:
// 移除 this line from MauiProgram.cs
builder.UseBarcodeReader();
// 移除 this line from MauiProgram.cs
builder.UseBarcodeReader();
支持此呼叫的MauiProgram.cs中移除。
步驟2:安裝 IronBarcode
dotnet add package IronBarcode
dotnet add package IronBarcode
.NET MAUI條碼掃描器教程涵蓋完整的專案配置,包括iOS的AndroidManifest.xml權限聲明。
步驟3:更新命名空間並初始化授權
從所有文件中移除ZXing.Net.MAUI命名空間匯入:
// 移除 these from every .cs file that imported them
using ZXing.Net.Maui;
using ZXing.Net.Maui.Controls;
// 移除 these from every .cs file that imported them
using ZXing.Net.Maui;
using ZXing.Net.Maui.Controls;
在應用啟動時新增IronBarcode命名空間並初始化授權金鑰。合適的位置是在執行任何條碼操作之前的App.xaml.cs。
using IronBarCode;
// In MauiProgram.cs CreateMauiApp() or App.xaml.cs constructor
IronBarCode.License.LicenseKey = "YOUR-LICENSE-KEY";
using IronBarCode;
// In MauiProgram.cs CreateMauiApp() or App.xaml.cs constructor
IronBarCode.License.LicenseKey = "YOUR-LICENSE-KEY";
Imports IronBarCode
' In MauiProgram.vb CreateMauiApp() or App.xaml.vb constructor
IronBarCode.License.LicenseKey = "YOUR-LICENSE-KEY"
程式碼遷移範例
替換XAML相機控制和後台程式碼
CameraBarcodeReaderView XAML控制及其支持的後台程式碼是主要的遷移目標。 替換移除了頁面佈局中的相機視圖,並且用按鈕觸發的異步捕獲取代事件驅動的掃描模式。
ZXing.Net.MAUI方法:
XAML:
<ContentPage xmlns:zxing="clr-namespace:ZXing.Net.Maui.Controls;assembly=ZXing.Net.MAUI.Controls">
<StackLayout>
<zxing:CameraBarcodeReaderView
x:Name="CameraView"
Options="{Binding ReaderOptions}"
BarcodesDetected="OnBarcodesDetected"
VerticalOptions="FillAndExpand" />
<Label x:Name="ResultLabel" Text="Scanning..." />
</StackLayout>
</ContentPage>
<ContentPage xmlns:zxing="clr-namespace:ZXing.Net.Maui.Controls;assembly=ZXing.Net.MAUI.Controls">
<StackLayout>
<zxing:CameraBarcodeReaderView
x:Name="CameraView"
Options="{Binding ReaderOptions}"
BarcodesDetected="OnBarcodesDetected"
VerticalOptions="FillAndExpand" />
<Label x:Name="ResultLabel" Text="Scanning..." />
</StackLayout>
</ContentPage>
後台程式碼:
using ZXing.Net.Maui;
using ZXing.Net.Maui.Controls;
public partial class ScannerPage : ContentPage
{
public BarcodeReaderOptions ReaderOptions { get; }
public ScannerPage()
{
InitializeComponent();
ReaderOptions = new BarcodeReaderOptions
{
Formats = BarcodeFormats.QRCode | BarcodeFormats.Code128 | BarcodeFormats.Ean13,
TryHarder = true,
AutoRotate = true
};
BindingContext = this;
}
private void OnBarcodesDetected(object sender, BarcodeDetectionEventArgs e)
{
MainThread.BeginInvokeOnMainThread(() =>
{
foreach (var barcode in e.Results)
ResultLabel.Text = $"{barcode.Format}: {barcode.Value}";
});
CameraView.IsDetecting = false;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
CameraView.IsDetecting = false;
}
protected override void OnAppearing()
{
base.OnAppearing();
CameraView.IsDetecting = true;
}
}
using ZXing.Net.Maui;
using ZXing.Net.Maui.Controls;
public partial class ScannerPage : ContentPage
{
public BarcodeReaderOptions ReaderOptions { get; }
public ScannerPage()
{
InitializeComponent();
ReaderOptions = new BarcodeReaderOptions
{
Formats = BarcodeFormats.QRCode | BarcodeFormats.Code128 | BarcodeFormats.Ean13,
TryHarder = true,
AutoRotate = true
};
BindingContext = this;
}
private void OnBarcodesDetected(object sender, BarcodeDetectionEventArgs e)
{
MainThread.BeginInvokeOnMainThread(() =>
{
foreach (var barcode in e.Results)
ResultLabel.Text = $"{barcode.Format}: {barcode.Value}";
});
CameraView.IsDetecting = false;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
CameraView.IsDetecting = false;
}
protected override void OnAppearing()
{
base.OnAppearing();
CameraView.IsDetecting = true;
}
}
Imports ZXing.Net.Maui
Imports ZXing.Net.Maui.Controls
Public Partial Class ScannerPage
Inherits ContentPage
Public ReadOnly Property ReaderOptions As BarcodeReaderOptions
Public Sub New()
InitializeComponent()
ReaderOptions = New BarcodeReaderOptions With {
.Formats = BarcodeFormats.QRCode Or BarcodeFormats.Code128 Or BarcodeFormats.Ean13,
.TryHarder = True,
.AutoRotate = True
}
BindingContext = Me
End Sub
Private Sub OnBarcodesDetected(sender As Object, e As BarcodeDetectionEventArgs)
MainThread.BeginInvokeOnMainThread(Sub()
For Each barcode In e.Results
ResultLabel.Text = $"{barcode.Format}: {barcode.Value}"
Next
End Sub)
CameraView.IsDetecting = False
End Sub
Protected Overrides Sub OnDisappearing()
MyBase.OnDisappearing()
CameraView.IsDetecting = False
End Sub
Protected Overrides Sub OnAppearing()
MyBase.OnAppearing()
CameraView.IsDetecting = True
End Sub
End Class
IronBarcode 方法:
XAML:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui">
<StackLayout>
<Button Text="Scan Barcode" Clicked="ScanButton_Clicked" />
<Label x:Name="ResultLabel" Text="Tap to scan..." />
</StackLayout>
</ContentPage>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui">
<StackLayout>
<Button Text="Scan Barcode" Clicked="ScanButton_Clicked" />
<Label x:Name="ResultLabel" Text="Tap to scan..." />
</StackLayout>
</ContentPage>
後台程式碼:
// NuGet: dotnet add package IronBarcode
using IronBarCode;
public partial class ScannerPage : ContentPage
{
public ScannerPage() => InitializeComponent();
private async void ScanButton_Clicked(object sender, EventArgs e)
{
var photo = await MediaPicker.CapturePhotoAsync();
if (photo == null) return;
using var stream = await photo.OpenReadAsync();
using var ms = new MemoryStream();
await stream.CopyToAsync(ms);
var results = BarcodeReader.Read(ms.ToArray());
var first = results.FirstOrDefault();
ResultLabel.Text = first != null
? $"{first.Format}: {first.Value}"
: "No barcode found";
}
//否OnAppearing or OnDisappearing required — no camera state exists between scans
}
// NuGet: dotnet add package IronBarcode
using IronBarCode;
public partial class ScannerPage : ContentPage
{
public ScannerPage() => InitializeComponent();
private async void ScanButton_Clicked(object sender, EventArgs e)
{
var photo = await MediaPicker.CapturePhotoAsync();
if (photo == null) return;
using var stream = await photo.OpenReadAsync();
using var ms = new MemoryStream();
await stream.CopyToAsync(ms);
var results = BarcodeReader.Read(ms.ToArray());
var first = results.FirstOrDefault();
ResultLabel.Text = first != null
? $"{first.Format}: {first.Value}"
: "No barcode found";
}
//否OnAppearing or OnDisappearing required — no camera state exists between scans
}
Imports IronBarCode
Public Partial Class ScannerPage
Inherits ContentPage
Public Sub New()
InitializeComponent()
End Sub
Private Async Sub ScanButton_Clicked(sender As Object, e As EventArgs)
Dim photo = Await MediaPicker.CapturePhotoAsync()
If photo Is Nothing Then Return
Using stream = Await photo.OpenReadAsync()
Using ms As New MemoryStream()
Await stream.CopyToAsync(ms)
Dim results = BarcodeReader.Read(ms.ToArray())
Dim first = results.FirstOrDefault()
ResultLabel.Text = If(first IsNot Nothing, $"{first.Format}: {first.Value}", "No barcode found")
End Using
End Using
End Sub
'否OnAppearing or OnDisappearing required — no camera state exists between scans
End Class
zxing: XAML命名空間、MainThread.BeginInvokeOnMainThread()封裝和兩種生命周期覆蓋完全被淘汰。 async方法的延續運行在呼叫上下文中——即UI事件處理程式的主執行緒,所以不需要明確的執行緒編排。
移除格式規格
任何在程式碼庫中多頁面或掃描場景中零散的BarcodeReaderOptions配置塊都被刪除。 IronBarcode在50多種支持的格式中進行自動格式偵測,無需預先配置。
ZXing.Net.MAUI方法:
// ZXing.Net.MAUI: every anticipated format must be listed explicitly
// Formats not listed here will silently fail to detect
var readerOptions = new BarcodeReaderOptions
{
Formats = BarcodeFormats.QRCode |
BarcodeFormats.DataMatrix |
BarcodeFormats.Aztec |
BarcodeFormats.Pdf417 |
BarcodeFormats.Code128 |
BarcodeFormats.Code39 |
BarcodeFormats.Ean13 |
BarcodeFormats.UpcA |
BarcodeFormats.Codabar,
TryHarder = true
};
// ZXing.Net.MAUI: every anticipated format must be listed explicitly
// Formats not listed here will silently fail to detect
var readerOptions = new BarcodeReaderOptions
{
Formats = BarcodeFormats.QRCode |
BarcodeFormats.DataMatrix |
BarcodeFormats.Aztec |
BarcodeFormats.Pdf417 |
BarcodeFormats.Code128 |
BarcodeFormats.Code39 |
BarcodeFormats.Ean13 |
BarcodeFormats.UpcA |
BarcodeFormats.Codabar,
TryHarder = true
};
' ZXing.Net.MAUI: every anticipated format must be listed explicitly
' Formats not listed here will silently fail to detect
Dim readerOptions As New BarcodeReaderOptions With {
.Formats = BarcodeFormats.QRCode Or
BarcodeFormats.DataMatrix Or
BarcodeFormats.Aztec Or
BarcodeFormats.Pdf417 Or
BarcodeFormats.Code128 Or
BarcodeFormats.Code39 Or
BarcodeFormats.Ean13 Or
BarcodeFormats.UpcA Or
BarcodeFormats.Codabar,
.TryHarder = True
}
IronBarcode 方法:
// IronBarcode: no format configuration needed
// All formats are detected automatically on every read call
var results = BarcodeReader.Read(imageBytes);
// Optional: restrict to specific formats for performance tuning (not required for correctness)
var options = new BarcodeReaderOptions
{
ExpectBarcodeTypes = BarcodeEncoding.QRCode | BarcodeEncoding.Code128
};
var tunedResults = BarcodeReader.Read(imageBytes, options);
// IronBarcode: no format configuration needed
// All formats are detected automatically on every read call
var results = BarcodeReader.Read(imageBytes);
// Optional: restrict to specific formats for performance tuning (not required for correctness)
var options = new BarcodeReaderOptions
{
ExpectBarcodeTypes = BarcodeEncoding.QRCode | BarcodeEncoding.Code128
};
var tunedResults = BarcodeReader.Read(imageBytes, options);
Imports IronBarcode
' IronBarcode: no format configuration needed
' All formats are detected automatically on every read call
Dim results = BarcodeReader.Read(imageBytes)
' Optional: restrict to specific formats for performance tuning (not required for correctness)
Dim options As New BarcodeReaderOptions With {
.ExpectBarcodeTypes = BarcodeEncoding.QRCode Or BarcodeEncoding.Code128
}
Dim tunedResults = BarcodeReader.Read(imageBytes, options)
IronBarcode在對性能敏感的場景中提供格式提示,但這是可選的。 在選項物件中未列出的格式的條碼仍會被偵測和返回。
移除IsDetecting生命周期管理
移除所有現有的用於切換OnDisappearing覆蓋。 如果這些方法覆蓋包含其他頁面生命週期邏輯,保留該邏輯,只移除IsDetecting行。
ZXing.Net.MAUI方法:
// Required boilerplate on every page — omitting this causes camera resource leaks
protected override void OnDisappearing()
{
base.OnDisappearing();
if (CameraView != null)
CameraView.IsDetecting = false;
}
protected override void OnAppearing()
{
base.OnAppearing();
if (CameraView != null)
CameraView.IsDetecting = true;
}
// Required boilerplate on every page — omitting this causes camera resource leaks
protected override void OnDisappearing()
{
base.OnDisappearing();
if (CameraView != null)
CameraView.IsDetecting = false;
}
protected override void OnAppearing()
{
base.OnAppearing();
if (CameraView != null)
CameraView.IsDetecting = true;
}
' Required boilerplate on every page — omitting this causes camera resource leaks
Protected Overrides Sub OnDisappearing()
MyBase.OnDisappearing()
If CameraView IsNot Nothing Then
CameraView.IsDetecting = False
End If
End Sub
Protected Overrides Sub OnAppearing()
MyBase.OnAppearing()
If CameraView IsNot Nothing Then
CameraView.IsDetecting = True
End If
End Sub
IronBarcode 方法:
// Delete both methods if they contain only IsDetecting management.
// If they contain other logic, remove only the IsDetecting lines and keep the rest.
//IronBarcodeis stateless — there is no camera view running between button taps.
// Delete both methods if they contain only IsDetecting management.
// If they contain other logic, remove only the IsDetecting lines and keep the rest.
//IronBarcodeis stateless — there is no camera view running between button taps.
' Delete both methods if they contain only IsDetecting management.
' If they contain other logic, remove only the IsDetecting lines and keep the rest.
' IronBarcode is stateless — there is no camera view running between button taps.
Windows MAUI: 相同程式碼,無條件編譯
使用ZXing.Net.MAUI時,向專案中新增Windows目標要麼因為Windows實現從未編寫而編譯失敗,要麼需要平臺專用存根。 使用IronBarcode,相同的程式碼在iOS和Android上運行並編譯,且可以在Windows上不改動地運行。
ZXing.Net.MAUI方法:
// Windows MAUI: either fails to compile or requires a platform-specific stub
// There is no documented path to Windows support
#if ANDROID || IOS
// ZXing.Net.MAUI scanning — Windows has no implementation
#endif
// Windows MAUI: either fails to compile or requires a platform-specific stub
// There is no documented path to Windows support
#if ANDROID || IOS
// ZXing.Net.MAUI scanning — Windows has no implementation
#endif
IronBarcode 方法:
// NuGet: dotnet add package IronBarcode
//否platform conditionals — same code runs on iOS, Android, Windows, and macOS
private async void ScanButton_Clicked(object sender, EventArgs e)
{
var photo = await MediaPicker.CapturePhotoAsync();
if (photo == null) return;
using var stream = await photo.OpenReadAsync();
using var ms = new MemoryStream();
await stream.CopyToAsync(ms);
var results = BarcodeReader.Read(ms.ToArray());
ResultLabel.Text = results.FirstOrDefault()?.Value ?? "No barcode found";
}
// NuGet: dotnet add package IronBarcode
//否platform conditionals — same code runs on iOS, Android, Windows, and macOS
private async void ScanButton_Clicked(object sender, EventArgs e)
{
var photo = await MediaPicker.CapturePhotoAsync();
if (photo == null) return;
using var stream = await photo.OpenReadAsync();
using var ms = new MemoryStream();
await stream.CopyToAsync(ms);
var results = BarcodeReader.Read(ms.ToArray());
ResultLabel.Text = results.FirstOrDefault()?.Value ?? "No barcode found";
}
' NuGet: dotnet add package IronBarcode
'否platform conditionals — same code runs on iOS, Android, Windows, and macOS
Private Async Sub ScanButton_Clicked(sender As Object, e As EventArgs)
Dim photo = Await MediaPicker.CapturePhotoAsync()
If photo Is Nothing Then Return
Using stream = Await photo.OpenReadAsync()
Using ms As New MemoryStream()
Await stream.CopyToAsync(ms)
Dim results = BarcodeReader.Read(ms.ToArray())
ResultLabel.Text = If(results.FirstOrDefault()?.Value, "No barcode found")
End Using
End Using
End Sub
在Windows上,MediaPicker.CapturePhotoAsync()映射到Windows文件選擇器,允許使用者選取圖片文件——這是適合桌面環境的行為。 MAUI桌面條碼指南詳述Windows和macOS MAUI配置。
PDF條碼閱讀(新功能)
ZXing.Net.MAUI沒有從PDF文件讀取條碼的API。 如果此為遷移啟用的新需求,適用以下模式:
ZXing.Net.MAUI方法:
// ZXing.Net.MAUI: no API for PDF or file-based barcode reading
// Cannot fulfill this requirement — a separate library is required
// ZXing.Net.MAUI: no API for PDF or file-based barcode reading
// Cannot fulfill this requirement — a separate library is required
' ZXing.Net.MAUI: no API for PDF or file-based barcode reading
' Cannot fulfill this requirement — a separate library is required
IronBarcode 方法:
// NuGet: dotnet add package IronBarcode
using IronBarCode;
// Read all barcodes from all pages of a PDF
var pdfResults = BarcodeReader.Read("invoice.pdf");
foreach (var barcode in pdfResults)
Console.WriteLine($"Page {barcode.PageNumber}: {barcode.Format} — {barcode.Value}");
// Read from a user-selected file using MAUI FilePicker
private async void ReadFileButton_Clicked(object sender, EventArgs e)
{
var file = await FilePicker.PickAsync(new PickOptions
{
PickerTitle = "Select image or PDF"
});
if (file == null) return;
var fileResults = BarcodeReader.Read(file.FullPath);
foreach (var result in fileResults)
ResultLabel.Text += $"\n{result.Format}: {result.Value}";
}
// NuGet: dotnet add package IronBarcode
using IronBarCode;
// Read all barcodes from all pages of a PDF
var pdfResults = BarcodeReader.Read("invoice.pdf");
foreach (var barcode in pdfResults)
Console.WriteLine($"Page {barcode.PageNumber}: {barcode.Format} — {barcode.Value}");
// Read from a user-selected file using MAUI FilePicker
private async void ReadFileButton_Clicked(object sender, EventArgs e)
{
var file = await FilePicker.PickAsync(new PickOptions
{
PickerTitle = "Select image or PDF"
});
if (file == null) return;
var fileResults = BarcodeReader.Read(file.FullPath);
foreach (var result in fileResults)
ResultLabel.Text += $"\n{result.Format}: {result.Value}";
}
Imports IronBarCode
' Read all barcodes from all pages of a PDF
Dim pdfResults = BarcodeReader.Read("invoice.pdf")
For Each barcode In pdfResults
Console.WriteLine($"Page {barcode.PageNumber}: {barcode.Format} — {barcode.Value}")
Next
' Read from a user-selected file using MAUI FilePicker
Private Async Sub ReadFileButton_Clicked(sender As Object, e As EventArgs)
Dim file = Await FilePicker.PickAsync(New PickOptions With {
.PickerTitle = "Select image or PDF"
})
If file Is Nothing Then Return
Dim fileResults = BarcodeReader.Read(file.FullPath)
For Each result In fileResults
ResultLabel.Text &= $"\n{result.Format}: {result.Value}"
Next
End Sub
完整的PDF條碼閱讀流程——包括多頁文件、頁碼元資料和混合格式文件——在從PDF閱讀條碼指南中已記錄。
ZXing.Net.MAUI API至IronBarcode對應參考
| ZXing.Net.MAUI | IronBarcode | 注意事項 |
|---|---|---|
builder.UseBarcodeReader() |
不需要 | 從MauiProgram.cs移除 |
using ZXing.Net.Maui; |
using IronBarCode; |
命名空間替換 |
using ZXing.Net.Maui.Controls; |
不需要 | 移除 |
xmlns:zxing="clr-namespace:ZXing.Net.Maui.Controls;..." |
不需要 | 從XAML移除 |
<zxing:CameraBarcodeReaderView> |
<Button> + MediaPicker.CapturePhotoAsync() |
架構替代 |
Options="{Binding ReaderOptions}" |
不需要 | 移除綁定 |
BarcodesDetected="OnBarcodesDetected" |
BarcodeReader.Read()返回值 |
事件→異步返回 |
| `new BarcodeReaderOptions { Formats = BarcodeFormats.X | ... } | 不需要 | 自動偵測取代了格式清單 |
BarcodeDetectionEventArgs e |
IEnumerable<BarcodeResult> |
不同的結果傳遞 |
e.Results |
BarcodeReader.Read()的返回值 |
|
barcode.Value |
result.Value |
相同的屬性名稱 |
barcode.Format |
result.Format |
相同的屬性名稱 |
BarcodeFormats.QRCode |
BarcodeEncoding.QRCode |
枚舉重命名 |
BarcodeFormats.Code128 |
BarcodeEncoding.Code128 |
枚舉重命名 |
BarcodeFormats.Ean13 |
BarcodeEncoding.EAN13 |
枚舉重命名 |
BarcodeFormats.UpcA |
BarcodeEncoding.UPCA |
枚舉重命名 |
CameraView.IsDetecting = false |
不需要 | 從OnDisappearing移除 |
CameraView.IsDetecting = true |
不需要 | 從OnAppearing移除 |
| 沒有檔案輸入API | BarcodeReader.Read("path/to/image.png") |
新功能 |
| 無PDF輸入API | BarcodeReader.Read("document.pdf") |
新功能 |
| 僅限iOS和Android | iOS,Android,Windows,macOS,伺服器 | 平台擴展 |
常見遷移問題及解決方案
問題1:沒有活取景器等效品
ZXing.Net.MAUI: CameraBarcodeReaderView在頁面佈局中顯示連續的相機畫面,向使用者提供帶有疊加掃描反饋的實時預覽。
解決方案: IronBarcode不提供實時取景控制。 替代模式使用MediaPicker.CapturePhotoAsync(),開啟平台系統相機UI。 系統相機提供自己的實時預覽和對焦指示器。 在使用者捕獲圖像並確認後,結果傳遞到BarcodeReader.Read()。 如果持久的應用內取景器是必需的使用者體驗元素,且不能被系統相機UI取代,則需單獨建構相機整合層,使用Microsoft.Maui.Media或平台相機API,讓IronBarcode處理解碼步驟。
問題2:相機權限聲明
ZXing.Net.MAUI: 相機權限可能已在專案中作為ZXing.Net.MAUI設置指令的一部分被宣告。
解決方案: 相機權限仍然對IronBarcode的MAUI模式使用的MediaPicker.CapturePhotoAsync()呼叫是必要的。 確認在iOS的<uses-permission android:name="android.permission.CAMERA" />。 IronBarcode本身不直接存取相機——它處理影像——但提供影像的MediaPicker呼叫需要相機權限。 權限設置在.NET MAUI條碼掃描器教程中涵蓋。
問題3:Windows編譯現在成功,而之前失敗
ZXing.Net.MAUI: 將ZXing.Net.MAUI的專案嘗試包含Windows目標時,通常會遇到編譯錯誤或需要通過條件MSBuild邏輯將程式庫排除在Windows編譯之外。
解決方案: 移除ZXing.Net.MAUI並安裝IronBarcode後,Windows MAUI編譯在無任何平台條件的情況下成功。 移除任何#if ANDROID ||IOS警衛指令以排除來自Windows編譯的ZXing.Net.MAUI呼叫。IronBarcodeBarcodeReader.Read()呼叫在所有目標框架上編譯並運行。 如果MediaPicker.CapturePhotoAsync()排除於Windows編譯,那麼也可以移除該排除——該方法在Windows MAUI中得到支援並映射到文件選擇器。 確認在移除條件後,完整解決方案對所有目標框架的編譯均乾淨。
問題4:BarcodeFormats Enum引用
ZXing.Net.MAUI: BarcodeReaderOptions配置中大量使用。 一旦套件移除,任何剩餘的引用都會產生編譯錯誤。
解決方案: 刪除所有用來配置格式清單的BarcodeReaderOptions初始化塊。 IronBarcode不需要格式規格就能正確操作。 如果任何剩下的程式碼為記錄、顯示或比較目的引用BarcodeEncoding值取代它們。 在包被移除後運行grep -rn "BarcodeFormats\." --include="*.cs" .以找到所有剩餘的引用。
ZXing.Net.MAUI遷移檢查清單
遷移前任務
在做出變更之前,審核程式碼庫中所有ZXing.Net.MAUI使用情況:
grep -rn "ZXing.Net.Maui" --include="*.cs" --include="*.xaml" .
grep -rn "CameraBarcodeReaderView" --include="*.cs" --include="*.xaml" .
grep -rn "BarcodeDetectionEventArgs" --include="*.cs" .
grep -rn "BarcodeReaderOptions" --include="*.cs" .
grep -rn "BarcodeFormats\." --include="*.cs" .
grep -rn "IsDetecting" --include="*.cs" .
grep -rn "UseBarcodeReader" --include="*.cs" .
grep -rn "zxing:" --include="*.xaml" .
grep -rn "e\.Results" --include="*.cs" .
grep -rn "ZXing.Net.Maui" --include="*.cs" --include="*.xaml" .
grep -rn "CameraBarcodeReaderView" --include="*.cs" --include="*.xaml" .
grep -rn "BarcodeDetectionEventArgs" --include="*.cs" .
grep -rn "BarcodeReaderOptions" --include="*.cs" .
grep -rn "BarcodeFormats\." --include="*.cs" .
grep -rn "IsDetecting" --include="*.cs" .
grep -rn "UseBarcodeReader" --include="*.cs" .
grep -rn "zxing:" --include="*.xaml" .
grep -rn "e\.Results" --include="*.cs" .
記錄審核識別的所有掃描頁。 記錄IsDetecting管理的(要刪除)與包含其他邏輯的(將部分修改)。 記錄任何可能以除檢測配置之外的方式使用格式清單的BarcodeReaderOptions實例。
程式碼更新任務
- 從專案文件中移除
ZXing.Net.Maui.ControlsNuGet套件 - 從
builder.UseBarcodeReader() - 從所有文件中移除
using ZXing.Net.Maui.Controls;命名空間匯入 - 安裝
IronBarcodeNuGet套件 - 將
using IronBarCode;新增到將使用條碼閱讀的所有文件中 - 在應用啟動時新增
IronBarCode.License.LicenseKey = "YOUR-LICENSE-KEY"; - 從所有XAML文件中刪除
xmlns:zxing命名空間宣告 - 從XAML文件中刪除所有
<zxing:CameraBarcodeReaderView>元素 - 用XAML文件中的
<Button>控制取代已移除的相機視圖 - 新增
Clicked="ScanButton_Clicked"到每個掃描按鈕 - 從所有後台程式碼文件中刪除
OnBarcodesDetected事件處理程式方法 - 在每個頁面中新增
MediaPicker.CapturePhotoAsync()+BarcodeReader.Read() - 刪除所有
BarcodeReaderOptions初始化塊 - 刪除為
OnAppearing覆蓋 - 從包含其他邏輯的任何
IsDetecting行 - 移除任何隔離ZXing.Net.MAUI不進行Windows編譯的
#if ANDROID || IOS條件編譯警衛 - 用
BarcodeFormats.X枚舉引用
遷移後測試
在遷移編譯乾淨後,確認以下:
- Android MAUI:掃描按鈕開啟系統相機,拍照並返回正確的條碼結果——用Android掃描指南確認
- iOS MAUI:相同流程在iOS上正常工作,包括先前發生自動對焦問題的iPhone 15 Pro硬體上
- Windows MAUI:Windows編譯沒有錯誤,掃描按鈕開啟文件選擇器並從所選圖像返回正確的結果
- 格式:嘗試掃描以前未包括在老
BarcodeFormats清單中的格式條碼以確認自動偵測 - 頁面導航:多次往返掃描頁面,確認沒有記憶體增長或相機初始化失敗發生
- PDF閱讀:如果遷移新增PDF條碼閱讀作為新功能,確認多頁PDF返回帶有正確頁碼元資料的結果
遷移到IronBarcode的主要優勢
擴展平台覆蓋: 遷移後,同一個套件和相同掃描模式支持Windows和macOS MAUI目標,以及iOS和Android。 先前需要平台專用存根或對Windows條碼功能排除的專案,無需額外程式碼便獲得完整覆蓋。
新一代硬體可靠性: CameraBarcodeReaderView自動對焦模型影響,該模型在iPhone 15 Pro和Pro Max硬體上失效。 系統相機獨立處理對焦,使用者確認拍攝後由IronBarcode處理捕獲的圖像。
消除相機資源管理: 移除CameraBarcodeReaderView消除了整類相機資源泄露錯誤。 沒有OnDisappearing樣板,也不存在跨導航週期累積的相機資源。 無狀態API使掃描頁在資源生命周期方面無法區分於任何其他頁面。
無需配置的格式覆蓋: 現場遇到的任何條碼格式均會自動偵測。 因BarcodeFormats清單中缺少條目造成的掃描失敗被消除。 不再收到供應商更改標籤格式而使使用者條碼被靜默忽略的支持請求。
文件和文件處理: 遷移開啟了從PDF文件、圖片文件和字節流中讀取條碼的能力,無需額外程式庫。 以前超出ZXing.Net.MAUI範疇的工作流程——從上傳的發票中讀取條碼、處理數位票據、批量掃描圖片目錄——通過用於相機捕獲的相同BarcodeReader.Read()呼叫得以實現。
生成級穩定性: IronBarcode以穩定的商業版本發行,擁有活躍的開發節奏,商業支援,以及跟踪.NET版本發行的定期更新。 依賴審核、軟體構成分析和企業批准流程遇到的是擁有已記錄維護承諾的支援程式庫,而不是社區預售版本包。
常見問題
為什麼要從ZXing.Net.MAUI遷移到IronBarcode?
常見的理由包括簡化授權(去除SDK +運行時鑰匙的複雜性)、消除吞吐量限制、獲得原生PDF支持、改善Docker/CI/CD部署,並減少生產程式碼中的API樣板。
如何用IronBarcode替換ZXing.Net.MAUI的API呼叫?
用IronBarCode.License.LicenseKey = "key"替換實例建立和授權樣板。用BarcodeReader.Read(path)替換讀取呼叫,用BarcodeWriter.CreateBarcode(data, encoding)替換寫入呼叫。靜態方法不需要實例管理。
從ZXing.Net.MAUI遷移到IronBarcode時要改多少程式碼?
大多數遷移會導致較少的程式碼行。授權樣板、實例構造函式和顯式格式配置被移除。核心讀/寫操作映射為較短的IronBarcode等價物,且結果物件更清晰。
在遷移期間是否需要同時安裝ZXing.Net.MAUI和IronBarcode?
不需要。大多數遷移是直接替換而不是並行操作。一次遷移一個服務類,替換NuGet引用,並更新實例化和API呼叫模式後再移動到下一個類。
IronBarcode的NuGet包名稱是什麼?
包名是'IronBarCode'(大寫B和C)。可以用'Install-Package IronBarCode'或'dotnet add package IronBarCode'安裝。程式碼中的using指令是'using IronBarCode;'。
IronBarcode如何簡化Docker部署,相對於ZXing.Net.MAUI?
IronBarcode是沒有外部SDK文件或掛載許可配置的NuGet封裝。在Docker中,設置IRONBARCODE_LICENSE_KEY環境變數,包在啟動時處理許可驗證。
從ZXing.Net.MAUI遷移後,IronBarcode能否自動偵測所有條碼格式?
是的。IronBarcode自動檢測所有支持的格式的符號學。不需要顯式的BarcodeTypes枚舉。如果格式已經知道並且性能很重要,BarcodeReaderOptions允許限制搜索空間作為優化。
IronBarcode是否可以不使用單獨的庫讀取PDF中的條碼?
可以。BarcodeReader.Read("document.pdf")原生處理PDF文件。結果包括每個條碼發現的頁碼、格式、值和置信度。不需要外部PDF渲染步驟。
IronBarcode如何處理並行條碼處理?
IronBarcode的靜態方法是無狀態且執行緒安全的。直接使用Parallel.ForEach遍歷文件列表,不需要每個執行緒的實例管理。BarcodeReaderOptions.MaxParallelThreads控制內部執行緒預算。
遷移從ZXing.Net.MAUI到IronBarcode時,哪些結果屬性會改變?
常見的重命名:BarcodeValue變為Value,BarcodeType變為Format。IronBarcode結果還新增了Confidence和PageNumber。整個解決方案的搜索和替換可處理現有結果處理程式碼中的重新命名。
我如何在CI/CD管道中設置IronBarcode授權?
將IRONBARCODE_LICENSE_KEY作為管道機密儲存,在應用程式啟動程式碼中分配IronBarCode.License.LicenseKey。一個機密涵蓋所有環境,包括開發、測試、分期和生產。
IronBarcode是否支持生成帶有自定義樣式的QR碼?
支持。QRCodeWriter.CreateQrCode()支持通過ChangeBarCodeColor()自定義顏色,通過AddBrandLogo()嵌入logo,可配置錯誤校正級別,以及包括PNG、JPG、PDF和流在內的多種輸出格式。

