放棄 Electron,擁抱 WebView2!JavaScript 快速開發獨立 EXE 程式

来源:https://www.cnblogs.com/aardio/archive/2022/09/13/16688602.html
-Advertisement-
Play Games

Electron 不錯,但也不是完美的。 Electron 帶來了很多優秀的桌面軟體,但並不一定總是適合我們的需求。 多個選擇總是好事! ▶ 我使用 Electron 遇到的一些麻煩 1、Electron 太大了! 2、每一個 Electron 寫的軟體都要重覆地帶一個 Electron …… 升級 ...


Electron 不錯,但也不是完美的。

Electron 帶來了很多優秀的桌面軟體,但並不一定總是適合我們的需求。

多個選擇總是好事!

我使用 Electron 遇到的一些麻煩

1、Electron 太大了!

2、每一個 Electron 寫的軟體都要重覆地帶一個 Electron …… 升級與分發都不方便。

3、Electron 不方便嵌入其他視窗界面,與其他語言、技術融合不易。

4、並不是所有桌面軟體都需要 Electron 的跨平臺特性。macOS , Linux 的桌面系統市場份額小於被遺忘的 Windows 8 ,如果軟體只是在 Windows 平臺運行,並且需要大量與專用系統 API 交互,跨平臺反而是不必要的負擔。

5、我曾經在 aardio 中封裝了一個 electron 擴展庫,然後我在寫這個擴展庫的時候,當時看到的還是 remote 真香 …… 然後我為這個擴展庫寫了個很大的 JS 文件就用到了 remote。可是等我寫完沒多久, 就看到 remote 被 Electron 拋棄了,remote 會慢一萬倍 ,各種缺陷 ……

▶ WebView2 的優勢

1、WebView2 基於性能強悍的 Edge(Chromium) 內核。

2、調用 WebView2 生成的軟體體積很小。所有基於 WebView2 的軟體可以共用同一個 WebView2 組件。Win11 已經內置 WebView2 組件,其他操作系統也可以快速地自動安裝 WebView2 。

3、WebView2 介面非常簡潔,嵌入其他視窗界面也非常方便。

總結一句話就是:WebView2 簡單、好用、生成軟體體積小。

aardio 標準庫中的 web.view 就是基於 WebView2。WebView2 的介面是如此簡潔,所以我寫的這個庫也只有很少的代碼。aardio 支持在低版本操作系統自動檢測與安裝 WebView2 ( 速度極快,不用加任何代碼。隨著 WebView2 普及,舊系統減少,需要安裝的系統也會越來越少 ),又因為 aardio 可以將網頁自動內嵌到獨立 EXE 文件,就可以非常方便地生成獨立 EXE 程式。

▶ 一個最簡單的程式演示

下麵我們用 aardio 調用 web.view (WebView2)寫一個最簡單的程式:

import win.ui;
/*DSG{{*/
mainForm = win.form(text="WebView2")
mainForm.add(
btnCallJs={cls="button";text="調用 JS 函數";left=461;top=395;right=726;bottom=449;note="點這裡調用 JavaScript  函數";z=1};
custom={cls="custom";left=17;top=21;right=730;bottom=356;z=2}
)
/*}}*/

//創建瀏覽器組件
import web.view;
var wb = web.view(mainForm.custom);

//導出本地函數給網頁 JavaScript
wb.external = {
    getComputerName = function(){
        return sys.getComputerName();
    }
}
import sys;

//寫入網頁 HTML
wb.html = /**
<html>
<head>
    <script> 
    (async ()=>{ 
        var n = await aardio.getComputerName();
        alert(n);
    })()
    </script>
</head>
<body>
**/

//響應按鈕事件
mainForm.btnCallJs.oncommand = function(id,event){
    //調用 JS 函數
    wb.xcall("document.write","測試")
}

mainForm.show();
win.loopMessage();

 

對,這就是一個完整程式的源代碼,可以一鍵生成獨立 EXE 文件。

▶ 入門

首先點選 「aardio 主菜單 > 新建工程 > 視窗程式 > 空白工程」,然後點擊「創建工程」。

 

如果熟悉網頁前端開發,也可以點擊 「 新建工程 > Web 界面 > WebView2 」創建工程。

雙擊工程入口代碼 main.aardio 打開主視窗,自「界面控制項」中拖一個 「調用 JS 函數」的按鈕上去,再拖一個 custom 控制項到窗體上 —— 用來嵌入網頁:

 

然後切換到代碼視圖,添加以下代碼創建網頁瀏覽器:

import web.view;
var wb = web.view(mainForm.custom);

web.view 的第 1 個參數指定要嵌入 WebView2 的視窗對象,該參數可以是 mainForm.custom 這樣的控制項視窗,也可以是 mainForm 這樣的窗體對象。

 

下麵使用

wb.html = "<html></html>"

就可以寫網頁 HTML 代碼了。

或者使用

wb.go("網址")

可以打開指定的網頁。

使用

import wsock.tcp.simpleHttpServer;
wb.go("\res\index.html");

可以打開資源目錄的網頁,支持SPA 單頁應用。資源目錄可以嵌入 EXE 生成 獨立 EXE 文件,放心不用多寫其他代碼。

 

添加下麵的代碼導出 external 對象給網頁 JavaScript :

//導出本地函數給網頁 JavaScript
wb.external = {
    getComputerName = function(){
        return sys.getComputerName();
    }
}

import sys;

在網頁 JavaScript 里可以調用上面導出的 external 對象,不過在 JavaScript 里要用 aardio 這個名字表示 external 對象,網頁代碼如下:

wb.html = /**
<html>
<head>
    <script> 
    (async ()=>{ 
        var n = await aardio.getComputerName();
        alert(n);
    })()
    </script>
</head>
<body>
**/

註意在 aardio 中 /* 註釋 */ 可以作為字元串賦值給其他變數,請參考:aardio 編程語言快速入門——語法速覽

要註意所有 aardio 對象在 JavaScript 中都是非同步 Promise 對象。如上在 async 函數體內可以愉快地使用 await 調用 aardio 函數 —— 這非常方便。

我們在窗體設計視圖雙擊「調用 JS 函數」按鈕,這會切換到代碼視圖,並自動添加以下回調函數:

mainForm.btnCallJs.oncommand = function(id,event){
    
}

用戶點擊按鈕時就會調用上面的函數。

小改一下添加 aardio 代碼調用 JavaScript 函數:

//響應按鈕事件
mainForm.btnCallJs.oncommand = function(id,event){
    //調用 JS 函數
    wb.xcall("document.write","測試")
}

很簡單,一個程式就寫好了。可以在 aardio 中點擊「運行」按鈕直接運行代碼,也可以點擊「發佈」按鈕直接生成 EXE 文件。

 

▶ 如何將網頁顯示在窗體的指定位置?並且支持自動縮放?

web.view() 構造函數的第 1 個嵌入視窗參數可以是 win.form 對象(獨立視窗),也可以是 custom, static 這樣的普通控制項對象。例如前面的例子就是將 WebView2 嵌入 custom 控制項:

import web.view;
var wb = web.view(mainForm.custom);

aardio 中的所有控制項都可以非常方便的支持自動縮放。只要簡單地在窗體設計器中選定控制項,然後在「屬性」面板設置「固定邊距」、「自適應大小」這些屬性就可以。

 一個更簡單的方法是在窗體設計器上點右鍵,然後在彈出菜單中點擊「九宮格縮放佈局」—— aardio 將會自動設置所有控制項的自適應縮放屬性。

 至於網頁內容自適應排版很簡單,不需要在 aardio 中編寫代碼。

▶ 使用 wb.export 導出 aardio 函數到 Javascript

前面我們介紹過使用 external 導出 aardio 函數到網頁 JavaScript 。我們還可以用 wb.export 導出 aardio 函數,先看例子:

import web.view;
var wb = web.view(mainForm.custom);

wb.export({
    alert = function(msg){
        winform.msgbox(msg) 
    };
    nativeAdd = function(a,b){ 
        return a + b; 
    }
})

註意:

1、wb.export() 導出的是 JavaScript 全局函數。

2、wb.export() 導出的函數在 JavaScript 中同樣是非同步 Promise 對象。

3、wb.export() 導出的 Javascript 全局函數, 使用 JSON 自動轉換調用參數和返回值,可以更好的相容只能支持純 aardio 對象 / 純 JavaScript 對象的代碼。

4、wb.export() 導出的函數內部禁止調用 wb.doScript 或 wb.eval 執行Javascript 。

wb.external 內部是調用 wb.exportHostObject() 導出 aardio 對象,中間不需要經過 JSON 自動轉換。

 示例:網頁 JavaScript 調用本地 Ping 命令

我經常被問到幾個類似的問題:

1、JavaScript 的非同步函數太麻煩了,怎樣把他搞成同步的,不用 await ,不用 async 。

2、JavaScript 的非同步函數太好用了,怎樣在 aardio 中也這樣搞,如何在 aardio 里 await 。

其實同步有同步的優勢,非同步有非同步的好處,揚長避短是智慧,倒行逆施最累人。下麵我們一起來寫一個在 WebView2 中調用本地 Ping 命令的小程式體驗一下。


第一步:創建視窗。

import win.ui;
var winform = win.form(text="Ping")

第二步:基於視窗創建 WebView2 瀏覽器組件。

import web.view;
var wb = web.view(winform);

第三步:使用 external 對象導出 JavaScript 可以調用的本地函數。

import process.popen;
wb.external = {
  ping = function(domain){
    
    var prcs = process.popen("ping "+ domain);
    for( all,out,err in prcs.each() ){
        wb.invoke("document.body.insertAdjacentText",'beforeend',all); 
    }
    
    return "恭喜,事做好了!"
  } 
}

在 JavaScript 里用 aardio.ping() 就可以直接調用上面的 external.ping() 函數了。

第四步:下麵在網頁里寫 JavaScript 來調用 aardio 函數。

wb.html = /**
<body style="white-space: pre;"><script>
doSomething = async() => {
    var result = await aardio.ping('www.baidu.com');
    document.body.insertAdjacentText('beforeend',result);
};
</script>
<button  onclick="doSomething()">開始幹活了</ping>
**/

就這麼短短幾句,一個簡單的程式就完成了,請看運行效果:

 

上面程式的完整 aardio 源代碼如下:

//創建視窗
import win.ui;
var winform = win.form(text="Ping")

//嵌入瀏覽器組件
import web.view;
var wb = web.view(winform);

//導出 aardio 函數到 JavaScript
wb.external = {
    ping = function(domain){
        
        //同步有同步的優勢,揚長避短是智慧,倒行逆施最累人。 
        var prcs = process.popen("ping "+ domain);
        for( all,out,err in prcs.each() ){
            wb.invoke("document.body.insertAdjacentText",'beforeend',all); 
        }
        
        return "恭喜,事做好了!"
    } 
}
import process.popen;

//寫入網頁 HTML
wb.html = /**
<body style="white-space: pre;"><script>
doSomething = async() => {
    
    //非同步有非同步的好處,揚長避短是智慧,倒行逆施最累人。
      var result = await aardio.ping('www.baidu.com');
      document.body.insertAdjacentText('beforeend',result);
};
</script>
<button  onclick="doSomething()">開始幹活了</ping>
**/

//顯示視窗
winform.show();

//啟動界面消息迴圈
win.loopMessage();

▶ aardio 調用 JS 函數

在 aardio 中可以使用 wb.doScript() , wb.eval() , wb.xcall() 等函數調用網頁 JavaScript ,下麵看一個在 aardio 中調用 xterm.js 的簡單例子:

import win.ui;
var winform = win.form(text="xterm")

import web.view;
var wb = web.view(winform);

wb.html = /**
<!DOCTYPE html> 
<head>
  <meta charset="UTF-8">
  <title></title>
  <link rel="stylesheet" href="https://unpkg.com/[email protected]/css/xterm.css">
  <script src="https://unpkg.com/[email protected]/lib/xterm.js"></script>
</head>
<body style="height:100vh;"> 
  <script>
    let term  = new Terminal();
    term.open(document.body);
    term.write('\x1b[31m紅色字體\x1b[37m測試')
  </script>
</body>
</html>
**/

wb.xcall("term.write",'\e[32m綠色字體');

winform.show();
win.loopMessage();

▶ 無邊框視窗:用網頁實現視窗標題欄

「無邊框視窗」指的是去掉獨立窗體預設的邊框與標題欄,然後由程式自行定製邊框與標題欄。

aardio 做這事還是很容易的,首頁在窗體屬性中指定「邊框」屬性為 none。

 

這樣直接運行後顯示的窗體就沒有邊框和標題欄了( 按 Alt + F4 關閉視窗 )。

然後添加下麵的代碼就可以為窗體添加標題欄、標題欄按鈕、陰影邊框、並支持拖動邊框縮放:

import win.ui.simpleWindow;
win.ui.simpleWindow(winform);

win.ui.simpleWindow 的源碼很簡單,參考其源碼也可以自己編寫新的庫定製邊框與標題欄。

這裡我們不用上面的方法,而是用網頁實現標題欄。

我們知道網頁繪製一個標題欄與標題欄按鈕很簡單,難點在於怎麼在網頁里控制視窗。我們先學習幾個專用於無邊框視窗的 aardio 函數:

winform.hitMax() //模擬點擊最大化按鈕
winform.hitMin() //模擬點擊最小化按鈕
winform.hitClose() //模擬點擊關閉按鈕
winform.hitCaption() //拖動標題欄

下麵寫個簡單的例子,先看下運行效果:

 

WebView2 無邊框視窗示例完整源碼如下:

import win.ui;
/*DSG{{*/
var winform = win.form(text="無邊框視窗";right=759;bottom=469;bgcolor=16777215;border="none")
winform.add()
/*}}*/

import web.view;
var wb = web.view(winform);
 
//導出為 Javascript 中的 aardio 對象
wb.external = { 
    close = function(){
        winform.close();
    };
    hitCaption = function(){
        winform.hitCaption();
    };
    hitMin = function(){
        winform.hitMin();
    };
    hitMax = function(){
        return winform.hitMax();
    };
}

wb.html = /**
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <style type="text/css">
    html {
        margin: 0px;
        padding: 0px; 
        background-color: #202020; 
    }
    
    #title-bar {
        height: 32px;    
        padding: 0px;
        margin: 0px;
    }
    
    #title-bar .caption {
        position: fixed;
        top: 0px;
        left: 0px;    
        width: 100%;
        padding-left: 10px;
        color: #ADADAD;
        line-height: 32px;
        font-size: 14px;
        cursor: default;
        user-select:none;
    }
    
    #title-bar .buttons {
        position: fixed;
        top: 1px;
        right: 1px;    
    }
    
    #title-bar button {
        font: 14px Marlett ;
        color: #F5F5F5;
        background-color: transparent;
        border: none;
        height: 28px;
        width: 28px;  
    }
     
    #title-bar button:hover {
        background-color: #FF4500;
    }
    
    #title-bar button:active {
        background-color: #B0451E;
        color: #C5C5C5;
    }
    
    #main {
        padding: 12px;    
        color: #C0C0C0;
    }
     
    </style>
    <script type="text/javascript">  
    
    </script>
</head>
  <body>
    <div id="title-bar" >
      <div class="caption" onmousedown="aardio.hitCaption()">按住這裡調用 aardio.hitCaption() 拖動視窗 </div>
      <div class="buttons">
        <button id="min-btn" onclick="aardio.hitMin()">0</button>
        <button id="max-btn"  onclick="aardio.hitMax()">1</button>
        <button id="close-btn" onclick="aardio.close()">r</button>
      </div>
    </div>
    <div id="main">
        1、請指定窗體「邊框」屬性為 none ,創建無邊框視窗。<br />
        2、調用 win.ui.shadow(winform) 創建陰影邊框<br />
    </div>
    <script src="default.js"></script>
  </body>
</html>
**/ 

//添加陰影邊框
import win.ui.shadow;
win.ui.shadow(winform);

//設置視窗縮放範圍
import win.ui.minmax;
win.ui.minmax(winform);

//切換最大化、還原按鈕
winform.adjust = function( cx,cy,wParam ) {
    if( wParam == 0x2/*_SIZE_MAXIMIZED*/ ){ 
        wb.doScript(`document.getElementById("max-btn").innerText="2";`)
    }
    elseif( wParam == 0x0/*_SIZE_RESTORED*/ ){
        wb.doScript(`document.getElementById("max-btn").innerText="1";`)
    } 
};
            
winform.show();
win.loopMessage();

以上源碼來自 aardio 自帶範例 > Web 界面 > web.view :

▶ WebView2 + 前端工程

如果熟悉網頁前端開發,也可以點擊 「 新建工程 > Web 界面 > WebView2 」創建工程。

 

運行創建的範例工程會顯示幫助:

 

這些熟悉前端的一看就懂,就不多說了。

註意 WebView2 預設工程的「網頁源碼」這個目錄的「內嵌資源」屬性為 false —— 也就是說發佈後的 EXE 文件不會包含這個目錄。

 

而工程中的「網頁」目錄「內嵌資源」屬性為 true —— 也就是說發佈後的 EXE 文件會包含這個目錄。

 

「網頁」目錄「本地構建」屬性為 true —— 這指的是該目錄下的文件會無條件添加到發佈 EXE 文件中(不必添加到工程 )。

▶ 其他瀏覽器組件

aardio 中的瀏覽器組件非常多,用法與 web.view 基本都類似。aardio 甚至可以調用操作系統已安裝的 Chrome,Edge 等瀏覽器寫軟體界面。

請參考「 aardio 自帶範例 > Web 界面」:


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在項目中出現多重嵌套情況時,會出現無法滑動的場景,比如經常碰到的場景 ViewPager -> Fragment -> RecyclerView -> RecyclerView | ViewPager 最外層是一個可滑動的 tabLayout+ViewPager,ViewPager 中是多個 Fra ...
  • AR作為一項增強現實技術,帶來了虛擬數字世界與現實世界的深度融合,這種虛實融合,不僅能應用於虛擬汽車展示、虛擬室內設計等視覺交互場景,更可通過動作交互控制虛擬世界場景,實現無邊界的人機互動。 比如人們在拍攝短視頻時,可以不接觸屏幕,僅通過做出特定手勢來控制特效切換;也可以在拍照時通過手勢識別控制快門 ...
  • Web 水印技術在信息安全和版權保護等領域有著廣泛的應用,對防止信息泄露或知識產品被侵犯有重要意義。水印根據可見性可分為可見水印和不可見水印(盲水印),本文將分別予以介紹,帶你探秘 web 水印技術。 ...
  • 前言 自成都九月份以來疫情原因被封了一兩周,居家著實無聊,每天都是盯著微信公眾號發佈的疫情數據看,那種頁面,就我一個前端仔來說,看著是真的醜啊!(⊙_⊙)?既然醜,那就自己動手開整!項目是2022.9.5開始的,截止2022.9.12我完成了大概有八成。主要是想讓數據更加直觀,而且可離線下載(當然還 ...
  • 跨域是什麼 簡單的講就是你在一個地方使用另一個地方的資源,被瀏覽器給擋下來了,不讓不用!當然,它擋下來是有自己理由的:為了安全(╬▔皿▔)╯。 解決跨域 我是用vue開發的,就vue代理模式解決跨域說明一下。 1、在vue.config.js中這樣寫: let devProxy = { //獲取ip ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 說明 基於uni-app開發,調用官方藍牙相關api實現連接藍牙與向藍牙熱敏印表機發送位元組流,可列印文字,二維碼,圖片,調整字體大小等,本文提供大概思路 結構 bluetooth.js 藍牙連接相關模塊封裝 commands.js 列印十 ...
  • 由於 vite 出現的時間不是很久,基於 vite 創建的項目沒有 vue-cli 那麼完整,如果要使用 vue 全家桶、ESLint 等,還需要開發人員手動添加和配置,步驟稍多,略繁瑣。雖然在創建項目時可以選擇 *Customize with create-vue*,但我由於網路問題,一直沒有成功... ...
  • 每日3題 34 以下代碼執行後,控制臺中的輸出內容為? const num = { a: 10, add() { return this.a + 2; }, reduce: () => this.a - 2, }; console.log(num.add()); console.log(num.re ...
一周排行
    -Advertisement-
    Play Games
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...