.NET MAUI 開発では、BlazorWebView と WebView の両方が Web コンテンツの表示に使用されますが、それらは異なる目的を果たし、異なるシナリオ向けに設計されています。 BlazorWebView は、.NET MAUI アプリケーションで Blazor コンポーネントをホストするように特別に設計されており、Blazor コンポーネントを再利用し、Web アプリケーションとネイティブ アプリケーション間でコードを共有できます。 WebView は、Web ページ、HTML 文字列、ローカル HTML ファイルなどの Web コンテンツを表示するための汎用コントロールです。この記事では、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>
以前の Blazor ドキュメント スキャナー アプリケーションと同様の UI レイアウトを、index.html ファイルに作成します。
<!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 の間で相互運用する必要があります。
スキャンされた画像を Base64 文字列に変換する JavaScript 関数を作成します。
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; }
保存ボタンをクリックしたときに、WebView コントロールの OnWebViewNavigated イベント ハンドラーをトリガーするように window.location.href を設定します。
let saveButton = document.getElementById("save-button"); saveButton.onclick = async () => { window.location.href = 'invoke://CallCSharpFunction'; }
OnWebViewNavigated イベント ハンドラーで 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 中国語 Web サイトの他の関連記事を参照してください。