在.NET MAUI开发中,BlazorWebView和WebView都用于显示网页内容,但它们的用途不同,针对不同的场景而设计。 BlazorWebView 专门设计用于在 .NET MAUI 应用程序中托管 Blazor 组件,允许您重用 Blazor 组件并在 Web 和本机应用程序之间共享代码。 WebView 是用于显示 Web 内容的通用控件,包括网页、HTML 字符串和本地 HTML 文件。在本文中,我们将探讨如何使用 WebView 控件将 .NET MAUI Blazor 文档扫描仪应用程序转换为 .NET MAUI 应用程序,以 JavaScript 和 HTML 实现文档扫描逻辑,并实现 C# 和 JavaScript 之间的互操作以扫描文档和保存图像。
打开 MainPage.xaml 文件并将现有代码替换为以下 XAML 以添加 WebView 控件:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MauiWebView.MainPage"> <ScrollView> <StackLayout VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"> <WebView x:Name="WebView" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" Navigating="OnWebViewNavigated"/> </StackLayout> </ScrollView> </ContentPage>
打开 MainPage.xaml.cs 文件并添加以下代码来设置 WebView 的来源并处理 Navigating 事件:
namespace MauiWebView { public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); LoadHtmlFile(); } private void LoadHtmlFile() { WebView.Source = "index.html"; } private async void OnWebViewNavigated(object sender, WebNavigatingEventArgs e) { if (e.Url.StartsWith("invoke://callcsharpfunction")) { // TODO: Implement interop between C# and JavaScript } } } }
说明:
在 .NET MAUI 项目中,您可以将位于 Resources/Raw 文件夹中的静态 HTML、JavaScript 和 CSS 文件加载到 WebView 中。确保 MauiAsset 构建操作包含在 .csproj 文件中:
d0fd252dd0536d593a865b1b7d180310 a6b6999ef84f0df9dca4c12a06340421 6e5ad26193226e804779ce3cd902f2c9
我们在index.html 文件中创建与之前的 Blazor 文档扫描仪应用程序类似的 UI 布局。
8b05045a5be5764f313ed5b9168a17e6 49099650ebdc5f3125501fa170048923 93f0f5c25f18dab9d176bd4f6de5d30e 1fc2df4564f5324148703df3b6ed50c1 4f2fb0231f24e8aef524fc9bf9b9874f b2386ffb911b14667cb8f0f91ea547a7Dynamsoft RESTful API Example6e916e0f7d1e588d4f442bf645aedb2f ea507197563fe9808580cd58ff5dae83 9c3bca370b5104690d9ef395f2c5f8d1 6c04bd5ca3fcae76e30b72ad730ca86d 38b885ab7897934b555c67b94cd711e2 0a6bb94971119ee328f517322c66980c16b28748ea4df4d9c2150843fecfba68 16b28748ea4df4d9c2150843fecfba68 c1a436a314ed609750bd7c7d319db4daDocument Scanner2e9b454fa8428549ca2e64dfac4625cd 39bbc163833ae4e036fa5108a4263823 dc6dce4a544fdca2df29d5ac0ea9906b 2e1cf0710519d5598b1f0f14c36ba674 Get a License key from e854f899d6c08fe1c619c9c20acd0fd7here5db79b134e9f6b82c0b36e0489ee08ed. 8c1ecd4bb896b2264e0711597d40766c 1a7f7aaa14e20835a1a1338a7058be2a df250b2156c434f3390392d09b1c9563 16b28748ea4df4d9c2150843fecfba68 16b28748ea4df4d9c2150843fecfba68 4883ec0eb33c31828b7c767c806e14c7 b91e30dda1382ac7285aae3f45407cef 684271ed9684bde649abda8831d4d355Acquire Image39528cedfa926ea0c01e69ef5b2ea9b0 b6f796d82ff1b4e710c079542e89e182Get Devices65281c5ac262bf6d81768915a4a77ac0 dc6dce4a544fdca2df29d5ac0ea9906b 6870baf322929e763a5a4dd5b95638a2Source: 8c1ecd4bb896b2264e0711597d40766c 4e4da74db0fbe784cd1a8c486f2d663a18bb6ffaf0152bbe49cd8a3620346341 16b28748ea4df4d9c2150843fecfba68 dc6dce4a544fdca2df29d5ac0ea9906b 5d63dc371f715490ca7f6d2e4600ec66Pixel Type: 8c1ecd4bb896b2264e0711597d40766c 5eeb331c51742deae3114e4956cc1be1 5a07473c87748fb1bf73f23d45547ab8B & W4afa15d3069109ac30911f04c56f3338 5a07473c87748fb1bf73f23d45547ab8Gray4afa15d3069109ac30911f04c56f3338 5a07473c87748fb1bf73f23d45547ab8Color4afa15d3069109ac30911f04c56f3338 18bb6ffaf0152bbe49cd8a3620346341 16b28748ea4df4d9c2150843fecfba68 dc6dce4a544fdca2df29d5ac0ea9906b 045b2ead8e25645fdd0c39f585f1e5bfResolution: 8c1ecd4bb896b2264e0711597d40766c c0e226d2d22b2a84209a20e9fa5db602 5a07473c87748fb1bf73f23d45547ab81004afa15d3069109ac30911f04c56f3338 5a07473c87748fb1bf73f23d45547ab81504afa15d3069109ac30911f04c56f3338 5a07473c87748fb1bf73f23d45547ab82004afa15d3069109ac30911f04c56f3338 5a07473c87748fb1bf73f23d45547ab83004afa15d3069109ac30911f04c56f3338 18bb6ffaf0152bbe49cd8a3620346341 16b28748ea4df4d9c2150843fecfba68 dc6dce4a544fdca2df29d5ac0ea9906b 1ee9335f254f79d03a6febbcf5d3d46b 2c55b6d296473c35276f4ed5dfe2cf7dShow UI8c1ecd4bb896b2264e0711597d40766c 16b28748ea4df4d9c2150843fecfba68 dc6dce4a544fdca2df29d5ac0ea9906b 5ebf17639bf6105e4664ab2e1fb1f6eb 07c2649bd6a78c967126bb66652d154bADF8c1ecd4bb896b2264e0711597d40766c 16b28748ea4df4d9c2150843fecfba68 dc6dce4a544fdca2df29d5ac0ea9906b 44173729fcd0e865b8455eb3701b40b7 9fb9d3e7b5ec672c901c914afe880bd9Duplex8c1ecd4bb896b2264e0711597d40766c 16b28748ea4df4d9c2150843fecfba68 ebf9f28c318eafce2dd4c8e1bb2c253cScan Now65281c5ac262bf6d81768915a4a77ac0 6d9fd183d333544e9d5fe06824808c81Save65281c5ac262bf6d81768915a4a77ac0 684271ed9684bde649abda8831d4d355Image Tools39528cedfa926ea0c01e69ef5b2ea9b0 2326325328cfb1510e6119c7566a1325 679019de3d0cfbcca4676917ab8cd4f0 803a9dc7ffbcccf77de190935b40089a 65281c5ac262bf6d81768915a4a77ac0 becd6db3ab50097bcb2036771f348f01 b411ae39fbc39f0429a39ad945dcd511 65281c5ac262bf6d81768915a4a77ac0 c56962c63fec7fa8fb3820e921cf15bf b02f8f3397f265cf44c9fcab016cc9c7 65281c5ac262bf6d81768915a4a77ac0 16b28748ea4df4d9c2150843fecfba68 16b28748ea4df4d9c2150843fecfba68 b3be3238529c3d77b69b7fd150fd938b 46c5d3aad1e47bee946e6355bc5143b4 ec6b4fd79f2415f29f0b734dcd571fb5 16b28748ea4df4d9c2150843fecfba68 39bbc163833ae4e036fa5108a4263823 b7c1222a61bcb1861cf5c6bfedb3b7cd a1e9e9b21d1fae6be88debb6fb09c6cc 16b28748ea4df4d9c2150843fecfba68 16b28748ea4df4d9c2150843fecfba68 16b28748ea4df4d9c2150843fecfba68 16b28748ea4df4d9c2150843fecfba68 16b28748ea4df4d9c2150843fecfba68 e803653d6fd93b3996ebf69dd59af1622cacc6d41bbb37262a98f745aa00fbf0 36cc49f0c466276486e50c850b7e4956 73a6ac4ed44ffec12cee46588e518a5e
环境准备好了,下一步就是用JavaScript实现相关功能。
枚举可用的扫描仪。
const ScannerType = { // TWAIN scanner type, represented by the value 0x10 TWAINSCANNER: 0x10, // WIA scanner type, represented by the value 0x20 WIASCANNER: 0x20, // 64-bit TWAIN scanner type, represented by the value 0x40 TWAINX64SCANNER: 0x40, // ICA scanner type, represented by the value 0x80 ICASCANNER: 0x80, // SANE scanner type, represented by the value 0x100 SANESCANNER: 0x100, // eSCL scanner type, represented by the value 0x200 ESCLSCANNER: 0x200, // WiFi Direct scanner type, represented by the value 0x400 WIFIDIRECTSCANNER: 0x400, // WIA-TWAIN scanner type, represented by the value 0x800 WIATWAINSCANNER: 0x800 }; let queryDevicesButton = document.getElementById("query-devices-button"); queryDevicesButton.onclick = async () => { let scannerType = ScannerType.TWAINSCANNER | ScannerType.TWAINX64SCANNER; let devices = await getDevices(host, scannerType); let select = document.getElementById("sources"); select.innerHTML = ''; for (let i = 0; i e5a00d500dd47a53c4e20100e3fbebf9 { let select = document.getElementById("sources"); let device = select.value; if (device == null || device.length == 0) { alert('Please select a scanner.'); return; } let inputText = document.getElementById("inputText").value; let license = inputText.trim(); if (license == null || license.length == 0) { alert('Please input a valid license key.'); } let parameters = { license: license, device: JSON.parse(device)['device'], }; let showUICheck = document.getElementById("showUICheckId"); let pixelTypeSelect = document.getElementById("pixelTypeSelectId"); let resolutionSelect = document.getElementById("resolutionSelectId"); let adfCheck = document.getElementById("adfCheckId"); let duplexCheck = document.getElementById("duplexCheckId"); parameters.config = { IfShowUI: showUICheck.checked, PixelType: pixelTypeSelect.selectedIndex, Resolution: parseInt(resolutionSelect.value), IfFeederEnabled: adfCheck.checked, IfDuplexEnabled: duplexCheck.checked, }; let jobId = await scanDocument(host, parameters); let images = await getImages(host, jobId); for (let i = 0; i 30f38ec796a9a4f3b797e10c6aa42e38 { if (e != null && e.target != null) { let target = e.target; img.src = target.src; selectedThumbnail = target; } }); } selectedThumbnail = newImage; } } async function scanDocument(host, parameters, timeout = 30) { let url = host + '/DWTAPI/ScanJobs?timeout=' + timeout; try { let response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(parameters) }); if (response.ok) { let jobId = await response.text(); return jobId; } else { return ''; } } catch (error) { alert(error); return ''; } } async function getImages(host, jobId) { let images = []; let url = host + '/DWTAPI/ScanJobs/' + jobId + '/NextDocument'; while (true) { try { let response = await fetch(url); if (response.status == 200) { const arrayBuffer = await response.arrayBuffer(); const blob = new Blob([arrayBuffer], { type: response.type }); const imageUrl = URL.createObjectURL(blob); images.push(imageUrl); } else { break; } } catch (error) { console.error('No more images.'); break; } } return images; }
说明
将扫描图像旋转-90 或 90 度。
let rotateLeftButton = document.getElementById("rotate-left-button"); rotateLeftButton.onclick = () => { let img = document.getElementById('document-image'); img.src = rotateImage('document-image', -90); selectedThumbnail.src = img.src; } let rotateRightButton = document.getElementById("rotate-right-button"); rotateRightButton.onclick = () => { let img = document.getElementById('document-image'); img.src = rotateImage('document-image', 90); selectedThumbnail.src = img.src; } function rotateImage (imageId, angle) { const image = document.getElementById(imageId); const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); const imageWidth = image.naturalWidth; const imageHeight = image.naturalHeight; // Calculate the new rotation let rotation = 0; rotation = (rotation + angle) % 360; // Adjust canvas size for rotation if (rotation === 90 || rotation === -270 || rotation === 270) { canvas.width = imageHeight; canvas.height = imageWidth; } else if (rotation === 180 || rotation === -180) { canvas.width = imageWidth; canvas.height = imageHeight; } else if (rotation === 270 || rotation === -90) { canvas.width = imageHeight; canvas.height = imageWidth; } else { canvas.width = imageWidth; canvas.height = imageHeight; } // Clear the canvas context.clearRect(0, 0, canvas.width, canvas.height); // Draw the rotated image on the canvas context.save(); if (rotation === 90 || rotation === -270) { context.translate(canvas.width, 0); context.rotate(90 * Math.PI / 180); } else if (rotation === 180 || rotation === -180) { context.translate(canvas.width, canvas.height); context.rotate(180 * Math.PI / 180); } else if (rotation === 270 || rotation === -90) { context.translate(0, canvas.height); context.rotate(270 * Math.PI / 180); } context.drawImage(image, 0, 0); context.restore(); return canvas.toDataURL(); }
删除所有扫描图像,包括主图像和缩略图,并重置数据数组。
let deleteButton = document.getElementById("delete-button"); deleteButton.onclick = async () => { let img = document.getElementById('document-image'); img.src = 'images/default.png'; data = []; let thumbnails = document.getElementById("thumb-box"); thumbnails.innerHTML = ''; }
出于安全考虑,直接在 JavaScript 中保存图像受到限制。因此,我们需要在 C# 和 JavaScript 之间进行互操作来完成此任务。
创建一个 JavaScript 函数,将扫描的图像转换为 Base64 字符串。
function getBase64Image() { var img = document.getElementById('document-image'); var canvas = document.createElement('canvas'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; var ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); var dataURL = canvas.toDataURL('image/png'); var base64 = dataURL.split(',')[1]; return base64; }
点击保存按钮时,设置window.location.href以触发WebView控件的OnWebViewNaviged事件处理程序。
let saveButton = document.getElementById("save-button"); saveButton.onclick = async () => { window.location.href = 'invoke://CallCSharpFunction'; }
在 OnWebViewNaviged 事件处理程序中,调用 EvaluateJavaScriptAsync 从 JavaScript 检索 Base64 图像数据并将其保存到文件中。
private async void OnWebViewNavigated(object sender, WebNavigatingEventArgs e) { if (e.Url.StartsWith("invoke://callcsharpfunction")) { var base64String = await WebView.EvaluateJavaScriptAsync("getBase64Image()"); CallCSharpFunction(base64String); } } private void CallCSharpFunction(string base64String) { if (!string.IsNullOrEmpty(base64String)) { try { byte[] imageBytes = Convert.FromBase64String(base64String); var filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), GenerateFilename()); File.WriteAllBytes(filePath, imageBytes); DisplayAlert("Success", "Image saved to: " + filePath, "OK"); } catch (Exception ex) { DisplayAlert("Error", ex.Message, "OK"); } } else { DisplayAlert("Failure", "No image data found", "OK"); } } private string GenerateFilename() { DateTime now = DateTime.Now; string timestamp = now.ToString("yyyyMMdd_HHmmss"); return $"image_{timestamp}.png"; }
Note: Do not pass the base64 string directly to the C# function via window.location.href, as the string may be too long and cause an error. Instead, return the base64 string when calling EvaluateJavaScriptAsync from the C# function.
Press F5 in Visual Studio or Visual Studio Code to run the .NET document scanner application on Windows or macOS.
https://github.com/yushulx/dotnet-twain-wia-sane-scanner/tree/main/examples/MauiWebView
以上是从 .NET MAUI Blazor 切换到 WebView 控件进行文档扫描的详细内容。更多信息请关注PHP中文网其他相关文章!