首頁 > web前端 > js教程 > 從 .NET MAUI Blazor 切換到 WebView 控制項進行文件掃描

從 .NET MAUI Blazor 切換到 WebView 控制項進行文件掃描

PHPz
發布: 2024-07-29 18:37:22
原創
874 人瀏覽過

在.NET MAUI開發中,BlazorWebView和WebView都用於顯示網頁內容,但它們的用途不同,針對不同的場景而設計。 BlazorWebView 專門設計用於在 .NET MAUI 應用程式中託管 Blazor 元件,可讓您重複使用 Blazor 元件並在 Web 和本機應用程式之間共用程式碼。 WebView 是用於顯示 Web 內容的通用控制項,包括網頁、HTML 字串和本機 HTML 檔案。在本文中,我們將探討如何使用WebView 控制項將.NET MAUI Blazor 文件掃描器應用程式轉換為.NET MAUI 應用程序,以JavaScript 和HTML 實作文件掃描邏輯,並實作C# 和JavaScript 之間的互通以掃描文件和保存圖像。

先決條件

  1. 安裝 Dynamsoft 服務:此服務對於與 Windows 和 macOS 上的 TWAIN、SANE、ICA、ESCL 和 WIA 掃描器進行通訊是必需的。
    • Windows:Dynamsoft-Service-Setup.msi
    • macOS:Dynamsoft-Service-Setup.pkg
  2. 申請免費試用許可證:取得 Dynamic Web TWAIN 的 30 天免費試用許可證以開始使用。

步驟 1:使用 WebView 控制項建立新的 .NET MAUI 項目

  1. 在 Visual Studio 或 Visual Studio Code 中,建立一個新的 .NET MAUI 專案。
  2. 開啟 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>
    
    登入後複製
  3. 開啟 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
                }
            }
        }
    
    }
    
    
    登入後複製

    說明

    • LoadHtmlFile方法設定WebView控制項的Source屬性來載入index.html檔。
    • 當 WebView 導覽到新 URL 時,會觸發 OnWebViewNaviged 方法。它檢查 URL 是否以 invoke://callcsharpfunction 開頭,如果是,則允許 C# 和 JavaScript 互通。

步驟 2:將靜態 HTML、JavaScript 和 CSS 檔案載入到 WebView 控制項中

在 .NET MAUI 專案中,您可以將位於 Resources/Raw 資料夾中的靜態 HTML、JavaScript 和 CSS 檔案載入到 WebView 中。確保 MauiAsset 建置操作包含在 .csproj 檔案中:

<ItemGroup>
    <MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
登入後複製

Switching from .NET MAUI Blazor to WebView Control for Document Scanning

我們在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 &amp; 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>
登入後複製

第 3 步:用 JavaScript 實作文件掃描

環境準備好了,下一步就是用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 [];
}
登入後複製

說明

  • getDevices 函數向 RESTful API 端點 /DWTAPI/Scanners 發送 GET 請求以取得可用的掃描器。掃描器類型由scannerType參數指定。

取得影像

透過指定像素類型、解析度和其他設定從選定的掃描器掃描文件。

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;
}
登入後複製

說明

  • scanDocument 函數向 RESTful API 端點 /DWTAPI/ScanJobs 傳送 POST 請求以啟動掃描作業。參數包括許可證密鑰、設備名稱和掃描設定。
  • getImages 函數向 RESTful API 端點 /DWTAPI/ScanJobs/{jobId}/NextDocument 發送 GET 請求以取得掃描影像。影像儲存在 blob 物件中並顯示在影像顯示區域中。

旋轉影像

將掃描影像旋轉-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 = '';
}
登入後複製

步驟 4:C# 和 JavaScript 之間的互通性以保存映像

出於安全考慮,直接在 JavaScript 中保存映像受到限制。因此,我們需要在 C# 和 JavaScript 之間進行互通性來完成此任務。

  1. 建立一個 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;
    }
    
    登入後複製
  2. 點選儲存按鈕時,設定window.location.href以觸發WebView控制項的OnWebViewNaviged事件處理程序。

    let saveButton = document.getElementById("save-button");
    saveButton.onclick = async () => {
        window.location.href = 'invoke://CallCSharpFunction';    
    }
    
    登入後複製
  3. 在 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.

Step 5: Run the .NET MAUI Document Scanner Application

Press F5 in Visual Studio or Visual Studio Code to run the .NET document scanner application on Windows or macOS.

Switching from .NET MAUI Blazor to WebView Control for Document Scanning

Source Code

https://github.com/yushulx/dotnet-twain-wia-sane-scanner/tree/main/examples/MauiWebView

以上是從 .NET MAUI Blazor 切換到 WebView 控制項進行文件掃描的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板