How To Set Error Correction in 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 自动对焦问题:GitHub问题跟踪器中的CameraBarcodeReaderView时,iPhone 15 Pro和Pro Max设备(iPhone16,2)不能实现可靠的条形码检测对焦。 条形码在相机取景框中可见,但自动对焦系统无法清晰锁定,解码器无法提取结果。 唯一有记录的缓解措施是指导用户手动调整设备和条形码之间的距离——这一指导需要明显的用户体验提示和用户的耐心,对于主要工作流程而言,这不是一个可接受的生产结果。
相机资源泄漏:IDisposable。 当用户离开扫描页面时,相机资源不会通过标准处置模式释放。 记录的解决方法是设置OnDisappearing()中,这减少了影响,但并没有正式释放相机。 频繁往返于扫描页面的应用程序会累积资源消耗,这可能导致内存增长、电池耗尽以及返回扫描页面时相机初始化间歇性失败。
格式规范静默失败: .NET继承了 ZXing.Net 的要求,即在扫描开始之前声明要扫描的每个条形码格式。 从BarcodeReaderOptions.Formats中省略的格式,即使在相机画面中清晰可见,也会被默默忽略。 如果用户将设备对准开发者未预料到的条形码格式,则不会看到任何错误——应用程序根本检测不到任何东西。 在条形码格式由外部供应商、客户或第三方系统控制的环境中,这种悄无声息的疏忽会演变成持续存在的支持问题。
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 |
|---|---|---|
| 发布状态 | 稳定,1.0前版本(v0.7.4) | 稳定商业版 |
| iOS 主界面 | 是的(iPhone 15 Pro 对焦功能失效) | 是 |
| Android MAUI | 是的(摄像头 1.5.0 版本构建问题) | 是 |
| Windows MAUI | 不支持 | 是 |
| macOS MAUI | 不支持 | 是 |
| 服务器端ASP.NET Core | 否 | 是 |
| 实时相机取景器 | 是 | 否(MediaPicker 系统 UI) |
| 格式规范要求 | 是 | 否(自动检测,50多种格式) |
| 相机生命周期管理 | 手动(检测中) | 不适用 |
| Dispose() 实现 | 否 | 不适用——无国籍 |
| iPhone 15 Pro 自动对焦 | 已损坏(已记录) | 不适用 |
| PDF条形码提取 | 否 | 是 |
| 文件路径输入 | 否(仅限相机) | 是 |
| 条形码损坏恢复 | 努力者 | 是的(基于机器学习) |
| 条形码生成 | 是的(通过.NET) | 是 |
| 商业支持 | None | 是 |
| 许可证 | 麻省理工学院(免费) | 商业翻译 |
快速入门
步骤 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;
Imports ZXing.Net.Maui
Imports 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:相同的代码,无需条件编译
using 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";
}
Imports System
Imports System.IO
Imports System.Linq
Imports IronBarCode
' 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、服务器 | 平台扩展 |
常见迁移问题和解决方案
问题一:没有实时取景器
ZXing.Net.MAUI:在页面布局中,CameraBarcodeReaderView显示连续的相机实时影像,向用户展示带有覆盖扫描反馈的实时预览。
解决方案: IronBarcode不提供实时取景器控制。 替代模式使用MediaPicker.CapturePhotoAsync()打开平台系统相机界面。 系统摄像头提供实时预览和对焦指示器。 当用户捕捉图像并确认后,结果被传递给BarcodeReader.Read()。 如果需要一个持久的应用内取景器,且不能被系统相机界面替代,必须通过Microsoft.Maui.Media或平台相机API单独构建相机集成层,由IronBarcode处理解码步骤。
问题二:摄像机使用许可声明
ZXing .NET.MAUI:相机权限可能已在项目中作为 ZXing .NET.MAUI 设置说明的一部分进行声明。
解决方案:相机权限对IronBarcode的MAUI模式使用的MediaPicker.CapturePhotoAsync()调用仍是必要的。 验证iOS的AndroidManifest.xml。 IronBarcode自身不直接访问相机——它处理图像——但向其提供图像的MediaPicker调用需要相机权限。 权限设置已在.NET MAUI条形码扫描器教程中介绍。
问题 3:Windows 构建现在可以成功,而之前却失败了
ZXing .NET.MAUI:尝试将 ZXing .NET.MAUI 包含到 Windows 目标中的项目通常会遇到编译错误,或者需要通过条件 MSBuild 逻辑将该库从 Windows 构建中排除。
解决方案:卸载 ZXing .NET.MAUI 并安装IronBarcode后,Windows MAUI 构建无需任何平台条件即可成功。 移除所有 #if ANDROID || 在 ZXing .NET.MAUI 调用周围放置了 IOS 保护,以将其排除在 Windows 构建之外。 IronBarcode的BarcodeReader.Read()调用在所有目标框架上编译并运行。 如果MediaPicker.CapturePhotoAsync()`从Windows构建中排除,该排除也可以移除——该方法在Windows MAUI上受到支持,并映射到文件选择器。 移除条件语句后,验证完整解决方案能否针对所有目标框架顺利构建。
问题 4:条形码格式枚举引用
ZXing.Net.MAUI:来自BarcodeReaderOptions配置中。 移除该软件包后,任何剩余的引用都会导致编译错误。
解决方案:删除所有用于配置格式列表的BarcodeReaderOptions初始化块。 IronBarcode无需指定格式即可正常运行。 如果剩余的代码引用IronBarCode命名空间。 运行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实例,其用途超出检测配置。
代码更新任务
1.从项目文件中移除ZXing.Net.Maui.Controls NuGet包
2.从builder.UseBarcodeReader()
3.从所有文件中移除using ZXing.Net.Maui.Controls;命名空间导入
4.安装IronBarcode NuGet包
5.将using IronBarCode;添加到所有将使用条码阅读的文件
6.将IronBarCode.License.LicenseKey = "YOUR-LICENSE-KEY";添加到应用程序启动
7.从所有XAML文件中移除xmlns:zxing命名空间声明
8.从XAML文件中移除所有<zxing:CameraBarcodeReaderView>元素
9.在每个XAML文件中用<Button>控件替换已移除的相机视图
10.在每个扫描按钮中添加Clicked="ScanButton_Clicked"
11.从所有代码文件的后端代码中删除OnBarcodesDetected事件处理方法
12.在每个页面实施MediaPicker.CapturePhotoAsync() + async void ScanButton_Clicked方法
13.删除所有BarcodeReaderOptions初始化块
14.删除所有仅为OnAppearing重写
15.从任何包含其他逻辑的IsDetecting行
- 删除所有
#if ANDROID || iOS 的条件编译保护机制将 ZXing .NET.MAUI 与 Windows 构建隔离开来。 17.用BarcodeFormats.X`枚举引用
迁移后测试
迁移编译完成后,请验证以下内容:
- Android MAUI:扫描按钮会打开系统相机,拍摄照片,并返回正确的条形码结果——请参考Android 扫描指南进行确认。
- iOS MAUI:同样的流程也适用于 iOS,包括之前出现自动对焦问题的 iPhone 15 Pro 机型。
- Windows MAUI:Windows 版本编译无误,扫描按钮会打开文件选择器,并从选定的图像返回正确的结果。
- 格式:测试扫描未包括在旧
BarcodeFormats列表中的格式条码,以确认自动检测 - 页面导航:多次往返扫描页面,并验证是否出现内存占用增加或相机初始化失败的情况。
- PDF 阅读:如果迁移新增了 PDF 条形码阅读功能,请验证多页 PDF 是否能返回包含正确页码元数据的结果。
迁移到IronBarcode的主要优势
扩展平台覆盖范围:迁移后,该应用程序除了支持 iOS 和 Android 目标平台外,还支持 Windows 和macOS MAUI目标平台——所有这些都来自同一个软件包和相同的扫描模式。 以前需要特定平台存根或将 Windows 排除在条形码功能之外的项目,现在无需额外代码即可获得全面覆盖。
当前代硬件可靠性:通过BarcodeReader.Read()的图像捕获方法不受在iPhone 15 Pro和Pro Max硬件上失效的CameraBarcodeReaderView自动对焦模型的影响。 系统摄像头独立处理对焦,用户确认拍摄后, IronBarcode会处理拍摄的图像。
消除相机资源管理:移除CameraBarcodeReaderView消除了整类相机资源泄漏bug。 无需跟踪OnDisappearing样板代码,也不会跨导航周期积累相机资源。 无状态 API 使得扫描页面在资源生命周期方面与其他任何页面没有区别。
无需配置即可覆盖格式:自动检测现场遇到的任何条形码格式。 由于BarcodeFormats列表中缺少条目而导致的扫描故障被消除。 由于供应商更改了标签格式,导致用户条形码被默默忽略,此类支持请求不再发生。
文件和文档处理:此次迁移使得无需额外库即可从 PDF 文档、图像文件和字节流中读取条形码。 以前不在ZXing.Net.MAUI范围内的工作流——读取上传发票中的条码、处理数字票据、批量扫描图像目录——现在可以通过用于相机捕获的相同BarcodeReader.Read()调用实现。
生产级稳定性: IronBarcode作为稳定的商业版本发布,拥有积极的开发节奏、商业支持和定期更新,以跟踪.NET版本发布。 依赖性审计、软件组成分析和Enterprise审批流程遇到的是一个有文档记录的维护承诺的支持库,而不是一个社区预发布包。
常见问题解答
我为什么要从 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;”。
与 ZXing.Net.MAUI 相比,IronBarcode 如何简化 Docker 部署?
IronBarcode 是一个 NuGet 包,不包含任何外部 SDK 文件或已挂载的许可证配置。在 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是否支持生成自定义样式的二维码?
是的。QRCodeWriter.CreateQrCode() 支持通过 ChangeBarCodeColor() 自定义颜色、通过 AddBrandLogo() 嵌入徽标、可配置纠错级别以及多种输出格式,包括 PNG、JPG、PDF 和流媒体。

