在.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 檔案中:
<ItemGroup> <MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" /> </ItemGroup>
我們在index.html 檔案中建立與先前的 Blazor 文件掃描器應用程式類似的 UI 佈局。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Dynamsoft RESTful API Example</title> <link rel="stylesheet" href="main.css"> </head> <body> <div id="loading-indicator" class="loading-indicator"> <div class="spinner"></div> </div> <h2>Document Scanner</h2> <div class="row"> <div> <label> Get a License key from <a href="https://www.dynamsoft.com/customer/license/trialLicense?product=dwt" target="_blank">here</a>. </label> <input type="text" placeholder="licenseKey" id="inputText" class="license-input"> <br /> </div> </div> <div class="container"> <div class="image-tool"> <h3>Acquire Image</h3> <button class="btn btn-primary" id="query-devices-button">Get Devices</button> <div> <label for="sourceSelect">Source: </label> <select id="sources" class="form-control"></select> </div> <div> <label for="pixelTypeSelect">Pixel Type: </label> <select id="pixelTypeSelectId" class="form-control"> <option>B & W</option> <option>Gray</option> <option>Color</option> </select> </div> <div> <label for="resolutionSelect">Resolution: </label> <select id="resolutionSelectId" class="form-control"> <option>100</option> <option>150</option> <option>200</option> <option>300</option> </select> </div> <div> <input class="form-check-input" type="checkbox" id="showUICheckId"> <label class="form-check-label" for="showUICheck">Show UI</label> </div> <div> <input class="form-check-input" type="checkbox" id="adfCheckId"> <label class="form-check-label" for="adfCheck">ADF</label> </div> <div> <input class="form-check-input" type="checkbox" id="duplexCheckId"> <label class="form-check-label" for="duplexCheck">Duplex</label> </div> <button class="btn btn-primary mt-3" id="scan-button">Scan Now</button> <button class="btn btn-primary mt-2" id="save-button">Save</button> <h3>Image Tools</h3> <div class="image-tools"> <button id="delete-button" style="border:none; background:none; padding:0;"> <img src="images/delete.png" alt="Click Me" style="width: 64px; height: 64px;" /> </button> <button id="rotate-left-button" style="border:none; background:none; padding:0;"> <img src="images/rotate_left.png" alt="Click Me" style="width: 64px; height: 64px;" /> </button> <button id="rotate-right-button" style="border:none; background:none; padding:0;"> <img src="images/rotate_right.png" alt="Click Me" style="width: 64px; height: 64px;" /> </button> </div> </div> <div class="image-display"> <div class="full-img"> <img id="document-image" src="images/default.png" class="scanned-image"> </div> <div class="row"> <div class="thumb-bar" id="thumb-bar"> <div class="thumb-box" id="thumb-box"> </div> </div> </div> </div> </div> <script src="main.js"></script> </body> </html>
環境準備好了,下一步就是用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 < devices.length; i++) { let device = devices[i]; let option = document.createElement("option"); option.text = device['name']; option.value = JSON.stringify(device); select.add(option); }; } async function getDevices(host, scannerType) { devices = []; let url = host + '/DWTAPI/Scanners' if (scannerType != null) { url += '?type=' + scannerType; } try { let response = await fetch(url); if (response.ok) { let devices = await response.json(); return devices; } } catch (error) { console.log(error); } return []; }
說明
透過指定像素類型、解析度和其他設定從選定的掃描器掃描文件。
let scanButton = document.getElementById("scan-button"); scanButton.onclick = async () => { 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 < images.length; i++) { let url = images[i]; let img = document.getElementById('document-image'); img.src = url; data.unshift(url); let option = document.createElement("option"); option.selected = true; option.text = url; option.value = url; let thumbnails = document.getElementById("thumb-box"); let newImage = document.createElement('img'); newImage.setAttribute('src', url); if (thumbnails != null) { thumbnails.insertBefore(newImage, thumbnails.firstChild); newImage.addEventListener('click', e => { 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中文網其他相關文章!