使用IRONBARCODE

如何创建一个Blazor二维码扫描器

乔尔迪·巴尔迪亚
乔尔迪·巴尔迪亚
2024年二月18日
分享:

介绍

本文探讨了在使用IronQR(一个.NET库)的Blazor应用程序中集成快速响应码扫描器(QR码扫描器)。 QR 码是一种二维条形码,与普通的一维条形码相比,它能存储更多的数据。

Blazor是一个微软框架,允许开发者创建单页面应用程序(使用Blazor WebAssembly应用)或者使用C#构建交互式Web界面(Blazor Server,这也是我们将在本指南中关注的重点)。

IronQR 与 Blazor Server 在二维码扫描方面的集成是一种战略性组合,充分利用了两种技术的优势。 通过将 IronQR 与 Blazor 应用程序集成,您可以高效地处理二维码的生成和扫描。 二维码阅读器的这一功能在库存管理、票务系统和非接触式信息共享等各种商业环境中的需求日益增长。

了解基础知识

什么是 Blazor 服务器?

Blazor Server 是 ASP.NET Core 平台的一部分的 Web 应用框架。 它使开发人员能够使用 C# 而不是 JavaScript 构建交互式 Web UI。 这种服务器端模型通过 SignalR 连接(一种实时网络功能)处理用户交互。 这有助于开发人员创建有效的交互式网络应用程序。

IronQR 简介

IronQR 是一个 .NET 库,以其读取、解释和生成二维码的高准确性而著称。 它提供了一系列功能,包括处理不同类型二维码内容的能力。 IronQR 的优势在于简单易用,易于集成到 .NET 应用程序中,是希望集成和创建 QR 代码功能的开发人员的首选。

如何创建一个Blazor二维码扫描器

  1. 在 Visual Studio 代码中创建 Blazor 服务器应用程序

  2. 使用 NuGet 软件包管理器安装 QR 代码类库

  3. 在 index.razor 中使用 HTML 和 CSS 创建用户界面

  4. 编写上传文件处理逻辑

  5. 使用 QR 库编写 QR 扫描逻辑

  6. 在文本框中显示结果

设置环境

创建新的 Blazor 服务器应用程序

启动 Visual Studio 并选择 "创建新项目"。在项目模板选择界面,找到并选择 "Blazor Server App "模板。 单击下一步。

如何创建 Blazor 二维码扫描器:图 1 - 查找正确的模板进行实现

选择模板后,输入项目名称和位置(保留其他所有内容为默认值),然后点击“下一步”按钮。

如何创建一个Blazor二维码扫描器:图2 - 配置项目详细信息

现在选择所需的 .NET Framework 并点击创建按钮。 它将创建一个 Blazor 服务器应用程序。

如何创建 Blazor QR Code Scanner:图3 - 选择 .NET Framework 并创建项目

安装 IronQR 库

单击菜单栏中的 "工具"。 从下拉菜单中选择 NuGet 包管理器。 从上下文菜单中选择 "管理解决方案的 NuGet 包"。 这将打开 NuGet 包管理器选项卡。

如何创建一个 Blazor QR Code 扫描器:图 4 - 访问 NuGet 包管理器

在 NuGet 包管理器中,在 "浏览 "选项卡中搜索 "IronQR"。 然后在列表中找到 "IronQR "软件包。点击 "安装 "按钮。

如何创建一个Blazor QR码扫描器:图5 - 通过浏览选项卡安装IronQR软件包

现在,您已经安装好了所有工具,我们可以了解一下项目结构以及如何在您的项目中实施所有工具。

实施 QR 码扫描

构建用户界面

QR Code Scanner 的用户界面主要构建在 Index.razor 文件中。这个文件是 Blazor 服务器项目的一部分,使用 HTML 和 Razor 语法的组合来创建一个动态和互动的网页。 结构包括

@page "/"
@using System.IO
@using Microsoft.AspNetCore.Components.Forms
@using IronQr
@using IronSoftware.Drawing
@inject IJSRuntime JSRuntime
<PageTitle>QR Code Scanner</PageTitle>
<div>
    <h1>QR Code Scanner</h1> 
    <InputFile OnChange="HandleSelectedFile" accept="image/*" class="file-input" />
    @if (!string.IsNullOrEmpty(qrImageSrc))
    {
        <img src="@qrImageSrcForDisplay" alt="QR Code Image" class="qr-image" />
    }
    <button @onclick="ScanQRCode" disabled="@(!fileSelected)" class="button scan-button">Scan QR Code</button>
    @if (!string.IsNullOrEmpty(scannedText))
    {
        <div class="result-section">
            <button @onclick="CopyToClipboard" class="button copy-button">Copy</button>
        </div>
    }
</div>
@page "/"
@using System.IO
@using Microsoft.AspNetCore.Components.Forms
@using IronQr
@using IronSoftware.Drawing
@inject IJSRuntime JSRuntime
<PageTitle>QR Code Scanner</PageTitle>
<div>
    <h1>QR Code Scanner</h1> 
    <InputFile OnChange="HandleSelectedFile" accept="image/*" class="file-input" />
    @if (!string.IsNullOrEmpty(qrImageSrc))
    {
        <img src="@qrImageSrcForDisplay" alt="QR Code Image" class="qr-image" />
    }
    <button @onclick="ScanQRCode" disabled="@(!fileSelected)" class="button scan-button">Scan QR Code</button>
    @if (!string.IsNullOrEmpty(scannedText))
    {
        <div class="result-section">
            <button @onclick="CopyToClipboard" class="button copy-button">Copy</button>
        </div>
    }
</div>
'INSTANT VB WARNING: An assignment within expression was extracted from the following statement:
'ORIGINAL LINE: @page "/" using System.IO using Microsoft.AspNetCore.Components.Forms using IronQr using IronSoftware.Drawing inject IJSRuntime JSRuntime <PageTitle> QR Code Scanner</PageTitle> <div> <h1> QR Code Scanner</h1> <InputFile OnChange="HandleSelectedFile" accept="image/*" class="file-input" /> if(!string.IsNullOrEmpty(qrImageSrc))
"image/*" Class="file-input" /> [if](Not String.IsNullOrEmpty(qrImageSrc))
'INSTANT VB WARNING: An assignment within expression was extracted from the following statement:
'ORIGINAL LINE: Friend @page "/" using System.IO using Microsoft.AspNetCore.Components.Forms using IronQr using IronSoftware.Drawing inject IJSRuntime JSRuntime <PageTitle> QR Code Scanner</PageTitle> <div> <h1> QR Code Scanner</h1> <InputFile OnChange="HandleSelectedFile" accept="image/*" Class
"HandleSelectedFile" accept="image/*" Class
Friend page "/" [using] System.IO [using] Microsoft.AspNetCore.Components.Forms [using] IronQr [using] IronSoftware.Drawing inject IJSRuntime JSRuntime (Of PageTitle) QR Code Scanner</PageTitle> (Of div) (Of h1) QR Code Scanner</h1> <InputFile OnChange="HandleSelectedFile" accept
'INSTANT VB WARNING: An assignment within expression was extracted from the following statement:
'ORIGINAL LINE: <img src="@qrImageSrcForDisplay" alt="QR Code Image" class="qr-image" />
		"QR Code Image" class="qr-image" />
'INSTANT VB WARNING: An assignment within expression was extracted from the following statement:
'ORIGINAL LINE: <img src="@qrImageSrcForDisplay" alt="QR Code Image" class
		"@qrImageSrcForDisplay" alt="QR Code Image" class
		<img src="@qrImageSrcForDisplay" alt
End Class
'INSTANT VB WARNING: An assignment within expression was extracted from the following statement:
'ORIGINAL LINE: <button onclick="ScanQRCode" disabled="@(!fileSelected)" class="button scan-button"> Scan QR Code</button> if(!string.IsNullOrEmpty(scannedText))
	"@(!fileSelected)" class="button scan-button"> Scan QR Code</button> [if](Not String.IsNullOrEmpty(scannedText))
	If True Then
'INSTANT VB WARNING: An assignment within expression was extracted from the following statement:
'ORIGINAL LINE: <button onclick="ScanQRCode" disabled="@(!fileSelected)" class
	"ScanQRCode" disabled="@(!fileSelected)" class
	<button onclick="ScanQRCode" disabled
'INSTANT VB WARNING: An assignment within expression was extracted from the following statement:
'ORIGINAL LINE: <div class="result-section"> <button onclick="CopyToClipboard" class="button copy-button"> Copy</button> </div>
		"CopyToClipboard" class="button copy-button"> Copy</button> </div>
'INSTANT VB WARNING: An assignment within expression was extracted from the following statement:
'ORIGINAL LINE: <div class="result-section"> <button onclick="CopyToClipboard" class
		"result-section"> <button onclick="CopyToClipboard" class
		<div class="result-section"> <button onclick
	End If
'INSTANT VB TODO TASK: The following line uses invalid syntax:
'</div>
$vbLabelText   $csharpLabel

标题和标题<PageTitle><h1> 标签分别定义页面的标题和主要标题,为用户设置上下文。

图片上传控件:使用<InputFile>组件上传二维码图片。 该元素只接受图像文件,通过过滤掉不相关的文件类型来增强用户体验。

图像显示:一旦上传图像,将使用<img>标签显示。 这种视觉反馈对于确保用户上传了正确的文件至关重要。

扫描按钮:一个标记为@onclick="ScanQRCode的按钮会触发扫描过程。 其可用性取决于文件是否被选中,从而增强界面的直观性。

结果显示:扫描的 QR 码文本显示在一个文本输入框中,便于查看。 用户可以通过一个单独的按钮将这些文本复制到剪贴板。

site.css 中的 CSS 样式

QR Code Scanner 的视觉美学和布局在 site.css 文件中定义。

.content {
    padding: 20px;
    margin: 10px auto; /* Centers the content */
    max-width: 500px; /* Sets a max width for the content */
    border-radius: 10px;
    box-shadow: 0 2px 4px rgba(0,0,0,0.2);
    text-align: center;
}
.file-input, .result-input {
    margin: 10px 0;
    padding: 10px;
    border-radius: 5px;
    border: 1px solid #ddd;
    width: 100%;
}
.button {
    background-color: #4CAF50;
    color: white;
    border: none;
    cursor: pointer;
    padding: 10px;
    margin: 10px 0;
    border-radius: 5px;
    transition: background-color 0.3s, box-shadow 0.3s;
    width: auto; /* Adjusts button width */
    display: inline-block; /* Allows the width to adjust to content */
}
.button:hover {
    background-color: #45a049;
    box-shadow: 0 4px 8px rgba(0,0,0,0.2);
} 
.qr-image {
    max-width: 300px;
    max-height: 300px;
    display: block;
    margin: 10px auto;
    border-radius: 10px;
}
.result-section {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 100%;
}
.result-input {
    width: 100%;
    box-sizing: border-box;
}
.copy-button {
    margin-top: 10px;
    white-space: nowrap;
}

.content:此类用于样式化主要内容区域,赋予其定义的宽度、居中对齐和细微阴影以增加深度。

.file-input, .result-input:这些类设置了文件输入和结果显示元素的样式,确保它们在视觉上一致并完全占据其容器的宽度。

.button:按钮采用独特的绿色背景、圆角和悬停效果,以改善用户互动。

.qr-image:应用于二维码图像的样式包括尺寸限制和用于居中的自动边距,使图像突出而不显得过于醒目。

.result-section:这个类确保结果部分内的元素居中对齐并间距适当。

处理文件上传

HandleSelectedFile 方法是二维码扫描过程中的关键部分,负责处理用户的文件上传并准备进行扫描。 当用户通过<InputFile>组件选择文件时,将触发此方法。 这一点在以下代码中有所体现:

private async Task HandleSelectedFile(InputFileChangeEventArgs e)
{
    selectedFile = e.File;
    fileSelected = true;
    var imagesDirectory = Path.Combine(Directory.GetCurrentDirectory(), "UploadedImages");
    Directory.CreateDirectory(imagesDirectory); // Ensure the directory exists
    // Use a GUID as the unique file name
    var uniqueFileName = Guid.NewGuid().ToString() + Path.GetExtension(selectedFile.Name);
    var fullPath = Path.Combine(imagesDirectory, uniqueFileName);
    await using (var fileStream = new FileStream(fullPath, FileMode.Create))
    {
        await selectedFile.OpenReadStream().CopyToAsync(fileStream);
    }
    // Store the full path in qrImageSrc for scanning
    qrImageSrc = fullPath;
    // Optionally, create a base64 string for displaying the image (if needed)
    byte [] imageBytes = await File.ReadAllBytesAsync(fullPath);
    var base64String = Convert.ToBase64String(imageBytes);
    qrImageSrcForDisplay = $"data:image/{Path.GetExtension(selectedFile.Name).TrimStart('.')};base64,{base64String}";
}
private async Task HandleSelectedFile(InputFileChangeEventArgs e)
{
    selectedFile = e.File;
    fileSelected = true;
    var imagesDirectory = Path.Combine(Directory.GetCurrentDirectory(), "UploadedImages");
    Directory.CreateDirectory(imagesDirectory); // Ensure the directory exists
    // Use a GUID as the unique file name
    var uniqueFileName = Guid.NewGuid().ToString() + Path.GetExtension(selectedFile.Name);
    var fullPath = Path.Combine(imagesDirectory, uniqueFileName);
    await using (var fileStream = new FileStream(fullPath, FileMode.Create))
    {
        await selectedFile.OpenReadStream().CopyToAsync(fileStream);
    }
    // Store the full path in qrImageSrc for scanning
    qrImageSrc = fullPath;
    // Optionally, create a base64 string for displaying the image (if needed)
    byte [] imageBytes = await File.ReadAllBytesAsync(fullPath);
    var base64String = Convert.ToBase64String(imageBytes);
    qrImageSrcForDisplay = $"data:image/{Path.GetExtension(selectedFile.Name).TrimStart('.')};base64,{base64String}";
}
Private Async Function HandleSelectedFile(ByVal e As InputFileChangeEventArgs) As Task
	selectedFile = e.File
	fileSelected = True
	Dim imagesDirectory = Path.Combine(Directory.GetCurrentDirectory(), "UploadedImages")
	Directory.CreateDirectory(imagesDirectory) ' Ensure the directory exists
	' Use a GUID as the unique file name
	Dim uniqueFileName = Guid.NewGuid().ToString() & Path.GetExtension(selectedFile.Name)
	Dim fullPath = Path.Combine(imagesDirectory, uniqueFileName)
'INSTANT VB TODO TASK: Local functions are not converted by Instant VB:
'	await using(var fileStream = New FileStream(fullPath, FileMode.Create))
'	{
'		await selectedFile.OpenReadStream().CopyToAsync(fileStream);
'	}
	' Store the full path in qrImageSrc for scanning
	qrImageSrc = fullPath
	' Optionally, create a base64 string for displaying the image (if needed)
	Dim imageBytes() As Byte = Await File.ReadAllBytesAsync(fullPath)
	Dim base64String = Convert.ToBase64String(imageBytes)
	qrImageSrcForDisplay = $"data:image/{Path.GetExtension(selectedFile.Name).TrimStart("."c)};base64,{base64String}"
End Function
$vbLabelText   $csharpLabel

以下是其功能的详细分类:

文件选择和验证:当用户上传文件时,该方法使用InputFileChangeEventArgs e捕获文件的详细信息。 selectedFile 变量然后被分配给这个文件,并且布尔值 fileSelected 被设置为 true,指示输入数据/文件已准备好进行处理。

创建文件路径:该方法准备一个目录以存储上传的图像。 它使用Path.Combine来创建指向'UploadedImages'目录的路径,并使用Directory.CreateDirectory确保该目录存在。 这一步对于系统整理上传的文件至关重要。

生成唯一文件名:为了避免与现有文件发生冲突,使用 GUID(全局唯一标识符)生成一个唯一文件名,并附加原文件的扩展名。 这确保每个上传的文件都被uniquePathdentified

保存文件: 然后将文件保存到服务器。 该方法创建一个指向新生成文件路径的文件流,并使用await selectedFile.OpenReadStream().CopyToAsync(fileStream)将上传文件的内容复制到此流中。 这一步完成了上传过程。

准备图像以供显示:文件保存后,有必要将图像再次显示给用户以进行确认。 该方法将文件读取到字节数组中,并将其转换为 base64 字符串,适合直接嵌入到 <img> 标签的 src 属性中。 这种转换可以显示图像,而不需要向服务器单独请求图像文件。

扫描二维码

ScanQRCode 方法是 Blazor Server 应用程序中二维码扫描功能的核心。 该方法获取上传的图片,并使用 IronQR 提取二维码数据。

private async Task ScanQRCode()
{   
    // Check if there is a valid image to work with
    if (string.IsNullOrEmpty(qrImageSrc)) return;
    try
    {
        var inputBmp = AnyBitmap.FromFile(qrImageSrc);
        QrImageInput imageInput = new QrImageInput(inputBmp);
        QrReader reader = new QrReader();
        IEnumerable<QrResult> results = reader.Read(imageInput);
        // Check if there are any results and if the first result contains text
        var firstResult = results.FirstOrDefault();
        if (firstResult != null && !string.IsNullOrWhiteSpace(firstResult.Value.ToString()))
        {
            scannedText = firstResult.Value.ToString();
        }
        else
        {
            scannedText = "QR value not found!";
        }
    }
    catch (Exception ex)
    {
        scannedText = "Error scanning QR code: " + ex.Message;
    }
}
private async Task ScanQRCode()
{   
    // Check if there is a valid image to work with
    if (string.IsNullOrEmpty(qrImageSrc)) return;
    try
    {
        var inputBmp = AnyBitmap.FromFile(qrImageSrc);
        QrImageInput imageInput = new QrImageInput(inputBmp);
        QrReader reader = new QrReader();
        IEnumerable<QrResult> results = reader.Read(imageInput);
        // Check if there are any results and if the first result contains text
        var firstResult = results.FirstOrDefault();
        if (firstResult != null && !string.IsNullOrWhiteSpace(firstResult.Value.ToString()))
        {
            scannedText = firstResult.Value.ToString();
        }
        else
        {
            scannedText = "QR value not found!";
        }
    }
    catch (Exception ex)
    {
        scannedText = "Error scanning QR code: " + ex.Message;
    }
}
Private Async Function ScanQRCode() As Task
	' Check if there is a valid image to work with
	If String.IsNullOrEmpty(qrImageSrc) Then
		Return
	End If
	Try
		Dim inputBmp = AnyBitmap.FromFile(qrImageSrc)
		Dim imageInput As New QrImageInput(inputBmp)
		Dim reader As New QrReader()
		Dim results As IEnumerable(Of QrResult) = reader.Read(imageInput)
		' Check if there are any results and if the first result contains text
		Dim firstResult = results.FirstOrDefault()
		If firstResult IsNot Nothing AndAlso Not String.IsNullOrWhiteSpace(firstResult.Value.ToString()) Then
			scannedText = firstResult.Value.ToString()
		Else
			scannedText = "QR value not found!"
		End If
	Catch ex As Exception
		scannedText = "Error scanning QR code: " & ex.Message
	End Try
End Function
$vbLabelText   $csharpLabel

最初,该方法检查用于存储上传图片路径的qrImageSrc变量是否不为空。 该检查可确保在继续翻译之前有一个有效的图像可供使用。

一旦确认图像可以处理,该方法就会进入二维码读取的核心功能。 这涉及几个关键步骤,首先是将图像从其存储位置加载到适合 QR 代码分析的格式。 这种转换由 AnyBitmap.FromFile(qrImageSrc) 方法实现,它将图像准备好进行扫描过程。

下一步涉及创建一个QrReader对象。 该对象是 IronQR 库不可或缺的一部分,是从图像中解码二维码的主要工具。 当 QrReader 实例准备就绪时,应用程序随后继续扫描上传的图像。 reader.Read(imageInput) 函数负责此操作,系统地搜索图像中的二维码并提取其数据。

扫描结果存储在IEnumerable<QrResult>集合中。 然后,我们会仔细检查这组译文,以找到第一个二维码结果。 如果检测到二维码,并且其中包含可读文本,则该文本会被捕获并存储在scannedText变量中。 不过,在未找到二维码或二维码不包含文本的情况下,应用程序会设置一条默认消息,告知用户未检测到二维码值。

一旦成功扫描二维码,文本字符串就会显示在文本输入框中,这要归功于 Blazor 的双向数据绑定功能。 这通过将scannedText变量绑定到一个文本输入元素来实现。 输入字段设置为禁用,只读。 这种设计选择将用户交互的重点放在查看结果和复制结果上,而不是编辑内容。

整个扫描过程都包含在一个 try-catch 块中,以防止在扫描操作过程中出现意外错误。 这可能包括与图像文件格式相关的问题或阅读过程中的意外错误。 如果出现异常,就会捕捉到异常,并编写错误信息显示给用户。 这种方法不仅有助于发现问题,还能保持对用户的透明度,提高应用程序的可靠性。

复制结果

要启用复制到剪贴板功能,需要在_Host.cshtml文件中定义一个名为copyTextToClipboard的JavaScript函数。该脚本是一种简单而有效的方式来与剪贴板进行交互:

<script>
    function copyTextToClipboard(text) {
        navigator.clipboard.writeText(text).then(function () {
            console.log('Copying to clipboard was successful!');
        }, function (err) {
            console.error('Could not copy text: ', err);
        });
    }
</script>
JAVASCRIPT

该函数接受一个文本参数,即要复制的文本。 它使用navigator.clipboard.writeText方法,这是一种与剪贴板交互的现代方法。 这种方法因其简单性和符合网络标准而受到青睐。 设计的目的是在复制成功后在控制台中记录一条成功信息,以帮助调试并确保功能顺畅。 如果出现错误,错误信息将记录到控制台,以便深入了解操作过程中遇到的任何问题。

CopyToClipboard 方法位于 index.razor 文件的 @code 部分,作为 Blazor 应用程序与 JavaScript 函数之间的桥梁。 用户界面中的按钮可触发该方法的点击。 激活时,它会使用Blazor的JavaScript InterOp功能调用copyTextToClipboard JavaScript函数。 scannedText被作为参数传递给该函数,从而有效地将文本复制到用户的剪贴板。

private async Task CopyToClipboard()
{
    await JSRuntime.InvokeVoidAsync("copyTextToClipboard", scannedText);
}
private async Task CopyToClipboard()
{
    await JSRuntime.InvokeVoidAsync("copyTextToClipboard", scannedText);
}
Private Async Function CopyToClipboard() As Task
	Await JSRuntime.InvokeVoidAsync("copyTextToClipboard", scannedText)
End Function
$vbLabelText   $csharpLabel

执行应用程序

运行该项目后,用户将看到以下简洁的界面。 初始屏幕突出展示了 QR 码扫描器模块。 此模块包括一个上传二维码图像文件的按钮(“选择文件”),以及另一个用于启动扫描过程的按钮(“扫描二维码”)。 最初,没有选择任何文件,扫描区域为空白,等待用户输入。

如何创建一个Blazor QR码扫描器:图6 - 项目初步执行结果

用户使用“选择文件”按钮选择并上传QR码图像,现在显示所选文件的名称(例如,“qrvalue.png”)。 上传的 QR 代码在界面上的指定区域可见,向用户确认图像已准备好扫描。

如何创建Blazor QR Code扫描器:图 7 - 用户输入QR Code的结果

用户点击 "扫描二维码 "按钮后,应用程序将处理图像。 如果扫描成功,二维码内的编码文本就会显示在图片下方。 在这种情况下,扫描结果 ('<https://ironsoftware.com/csharp/qr/>') 是一个URL,指示扫描二维码时用户会被引导到的位置。 结果旁边会出现 "复制 "按钮,用户可以轻松地将扫描文本复制到剪贴板,以便进一步使用。

如何创建一个Blazor QR码扫描器:图8 - 显示来自QR码的文本和复制按钮

结论

如何创建 Blazor QR 码扫描器:图 9

总之,将 IronQR 集成到 Blazor 服务器应用程序中的过程是顺利而有效的,从而形成了一个二维码扫描解决方案。 由于混合使用了 IronQR 的强大处理功能和 Blazor 的动态用户界面渲染功能,从开始设置这个项目到实现扫描功能,都具有响应性和易用性。 从设置环境到部署的整个过程都要强调这种集成在实际应用中的实用性和有效性。 虽然IronQR擅长处理QR码,但对于需要扫描条形码功能的项目,IronBarcode是一个理想的选择,提供了类似的易用性和集成水平。

IronQR 为开发人员提供免费试用,以便在购买之前探索其功能。 若要在生产中扩展使用并访问其所有专业功能,IronQR 许可证的起始价格为$749。

乔尔迪·巴尔迪亚
乔尔迪·巴尔迪亚
软件工程师
Jordi 最擅长 Python、C# 和 C++,当他不在 Iron Software 运用技能时,他会进行游戏编程。作为产品测试、产品开发和研究的负责人之一,Jordi 为持续的产品改进增添了极大的价值。多样化的经验让他充满挑战和参与感,他说这是他在 Iron Software 工作中最喜欢的方面之一。Jordi 在佛罗里达州迈阿密长大,并在佛罗里达大学学习计算机科学和统计学。
< 前一页
如何在 C# 中打印条形码
下一步 >
如何在VB .NET中打印条形码标签