别揉我奶头~嗯~啊~av_日本理伦三级斤_亚洲综合日韩第十页_亚洲va国产天堂va久久 en色www国产亚洲阿娇_一般进去了女人反抗后的表现

首頁(yè) >文化 > 正文

Qt開發(fā)-操控Web小車案例-快看

2023-04-18 06:59:43 來源:QT教程

前言

這次討論Qt與Web混合開發(fā)相關(guān)技術(shù)。

這類技術(shù)存在適用場(chǎng)景,例如:Qt項(xiàng)目使用Web大量現(xiàn)成的組件/方案做功能擴(kuò)展,

Qt項(xiàng)目中性能無關(guān)/頻繁更新迭代的頁(yè)面用html單獨(dú)實(shí)現(xiàn),Qt項(xiàng)目提供Web形式的SDK給


(資料圖)

用戶做二次開發(fā)等等,或者是Web開發(fā)人員齊全而Qt/C++人手不足,此類非技術(shù)問題,

都可以使用Qt + Web混合開發(fā)。

(不適用的請(qǐng)忽略本文)

簡(jiǎn)介

上次的文章《Qt與Web混合開發(fā)》,討論了Qt與Web混合開發(fā)相關(guān)技術(shù)。

這次通過一個(gè)web控制小車的案例,繼續(xù)討論相關(guān)技術(shù)。

本文會(huì)先介紹Qt與Web嵌套使用,再介紹Qt與Web分開使用,之后著重討論分開使用

的一些實(shí)現(xiàn)細(xì)節(jié),特別是WebChannel通信、WebChannel在Web/typescript中的使用。

Qt與Web嵌套

MiniBrowser

這里以Qt官方的例子MiniBrowser來說明吧。

打開方式如下:

運(yùn)行效果如下:

這個(gè)例子是在Qml中嵌套了WebView。

半透明測(cè)試

濤哥做了一個(gè)簡(jiǎn)單的半透明測(cè)試。

增加了兩個(gè)半透明的小方塊,藍(lán)色的在WebView上面,紅色的在WebView下面。

運(yùn)行效果也是正確的:

代碼是這樣的:

紅色框中是我增加的代碼。

為什么要做半透明測(cè)試呢?根據(jù)以往的經(jīng)驗(yàn),不同渲染方式的兩種窗口/組件嵌套在一起,總會(huì)出現(xiàn)透明失效之類的問題,例如 qml與Widget嵌套。

渲染原理

濤哥翻了一下Qt源碼,了解到渲染的實(shí)現(xiàn)方式,Windows平臺(tái)大致如下:

chromium在單獨(dú)的進(jìn)程處理html渲染,并將渲染結(jié)果存儲(chǔ)在共享內(nèi)存中;主窗口在需要重繪的時(shí)候,從共享內(nèi)存中獲取內(nèi)容并渲染。

小結(jié)

這里的WebView內(nèi)部封裝好了WebEngine,其本身也是一個(gè)Item,就和普通的Qml一樣,屬性綁定、js function都可以正常使用,暫時(shí)不深入討論了。

Qt與Web分離

Qt與Web分離,就是字面意思,Web在單獨(dú)的瀏覽器或者App中運(yùn)行,不和Qt堆在一起。兩者通過socket進(jìn)行通信。

這里用我自己做的例子來說明吧。

先看看效果:

左邊是Qt實(shí)現(xiàn)的一個(gè)簡(jiǎn)易小車,可以前進(jìn)和轉(zhuǎn)向。右邊是Html5實(shí)現(xiàn)的控制端,控制左邊的小車。

源碼在github上: https://github.com/jaredtao/QtWeb

Qt小車

原版小車

小車來自Qt的D-Bus Remote Controller 例子

原版的例子,實(shí)現(xiàn)了通過QDBus 跨進(jìn)程 控制小車。

(吐槽:這是一個(gè)古老的例子,使用了GraphicsView 和QDBus)

(知識(shí)拓展1: DBus是unix系統(tǒng)特有的一種進(jìn)程間通信機(jī)制,使用有些復(fù)雜。Qt對(duì)DBus機(jī)制進(jìn)行了封裝/簡(jiǎn)化,即QDBus模塊,

通過xml文件的配置后,把DBus的使用轉(zhuǎn)換成了信號(hào)-槽的形式。類似于現(xiàn)在的Qt Remote Objects)

(知識(shí)拓展2: Windows本身不支持DBus,網(wǎng)上有socket模擬DBus的方案。參考: https://www.freedesktop.org/wiki/Software/dbus/)

改進(jìn)小車

我做了一些修改,主要如下:

去掉了DBus 增加控制按鈕 增加WebChannel 修改Car的實(shí)現(xiàn),導(dǎo)出一些屬性和函數(shù)。 注冊(cè)Car到WebChannel

這里貼一些關(guān)鍵代碼

Car的頭文件:

其中要說明的是:

speed和angle屬性具備 讀、寫、change信號(hào)。

還有加速、減速、左轉(zhuǎn)、右轉(zhuǎn)四個(gè)公開的槽函數(shù)。

必要的知識(shí)

WebSocket和 QWebSocket

WebSocket 是 HTML5 開始提供的一種在單個(gè) TCP 連接上進(jìn)行全雙工通訊的協(xié)議。

WebSocket 使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡(jiǎn)單,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù)。在 WebSocket API 中,瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接,并進(jìn)行雙向數(shù)據(jù)傳輸。

Qt為我們封裝好了WebSocket,即QWebSocket和QWebSocketServer,簡(jiǎn)單易用。

如果你了解socket編程,就看作TCP好了;如果不了解,請(qǐng)先去補(bǔ)充一下知識(shí)吧。

WebChannel

按濤哥的理解,WebChannel是在socket上建立的一種通信協(xié)議,這個(gè)協(xié)議的作用是把QObject暴露給遠(yuǎn)端的HTML。

大致使用流程:

Qt程序中,要暴露的QObject全部注冊(cè)到WebChannel。 Qt程序中,啟動(dòng)一個(gè)WebSocketServer,等待Html的連接。 Html加載好qwebchannel.js文件, 然后去連接WebSocket。 連接建立以后,Qt程序中,由WebChannel接手這個(gè)WebSocket,按協(xié)議將QObject的各種“元數(shù)據(jù)”傳輸給遠(yuǎn)端Html。 Html端,qwebchannel.js處理WebSocket收到的各種“元數(shù)據(jù)”,用js的Object 動(dòng)態(tài)創(chuàng)建出對(duì)應(yīng)的QObject。 到這里兩邊算是做好了準(zhǔn)備,可以互相調(diào)用了。 Qt端QObject數(shù)據(jù)變化只要發(fā)出信號(hào),就會(huì)由WebChannel自動(dòng)通知Web端; Web端可以主動(dòng)調(diào)用QObject的public的 invok函數(shù)、槽函數(shù),以及讀、寫屬性。

Qt啟動(dòng)系統(tǒng)瀏覽器

在使用WebChannel的時(shí)候,Qt端建立了WebSocketServer,之后要把server的路徑(例如:ws://127.0.0.1:12345)告訴Html。

一般就是在打開Html的時(shí)候帶上Query參數(shù),例如: F:\QtWeb\index.html?webChannelBaseUrl=ws://127.0.0.1:12345

Qt的OpenUrl

Qml中有 Qt.openUrlExternally, C++ 中有 QDesktopServices::openUrl,本質(zhì)一樣, 都可以打開一個(gè)本地的html網(wǎng)頁(yè)。

其在Windows平臺(tái)的底層實(shí)現(xiàn)是Win32 API。這里有個(gè)Win32 API的缺陷,傳Query參數(shù)會(huì)被丟掉。

C# .net的 Process::Start

濤哥找到了替代的方案:

.net framework / .net core有個(gè)啟動(dòng)進(jìn)程的函數(shù): System.Diagnostics.Process::Start, 可以調(diào)用瀏覽器并傳query參數(shù)

//C# 啟動(dòng)chromeSystem.Diagnostics.Process.Start("chrome", "F:\QtWeb\index.html?webChannelBaseUrl=ws://127.0.0.1:12345");//C# 啟動(dòng)firefoxSystem.Diagnostics.Process.Start("firefox", "F:\QtWeb\index.html?webChannelBaseUrl=ws://127.0.0.1:12345");//C# 啟動(dòng)IESystem.Diagnostics.Process.Start("IExplore", "F:\QtWeb\index.html?webChannelBaseUrl=ws://127.0.0.1:12345");

Qt中直接寫C#當(dāng)然不太好,不過呢,Win7/Win10 系統(tǒng)都帶有Powershell,而powershell依賴于.net framework, 我們可以調(diào)用powershell來間接使用.net framework。

所以有了下面的代碼:

...QString psCmd = QString(\"powershell -noprofile -command \"[void][System.Diagnostics.Process]::Start("%1", "%2")\"\").arg(browser).arg(url.toString());bool ok = QProcess::startDetached(psCmd);qWarning() << psCmd;if (!ok) {    qWarning() << \"failed\";}...

結(jié)果完美運(yùn)行。

Web控制端

目錄結(jié)構(gòu)

Web端就按照Web常規(guī)流程開發(fā)。

Web部分的源碼也在前文提到的github倉(cāng)庫(kù),子路徑是QtWeb\WebChannelCar\Web

如下是Web部分的目錄結(jié)構(gòu):

腳本用typescript,包管理用npm,打包用webpack,編輯器用vs code, 都中規(guī)中矩。

內(nèi)容比較簡(jiǎn)單,暫時(shí)不需要前端框架,手(復(fù))寫(制)的html和css。

Html

html部分比較簡(jiǎn)單

//index.html

樣式和布局全靠css,這里就不貼了。

TypeScript

腳本部分需要細(xì)說了。

src文件夾為全部腳本,目錄結(jié)構(gòu)如下:

TypeScript中的QObject

從main開始, 加點(diǎn)注釋:

//main.tsimport WebChannelCore from \"./webchannelCore\";//window加載時(shí)回調(diào),入口window.onload = () =>{    //初始化WebChannel,傳參為兩個(gè)回調(diào),分別對(duì)應(yīng)WebChannel建立連接和連接斷開。    WebChannelCore.initialize(onInit, onUninit);}//WebChannel建立連接的處理function onInit() {    //換圖標(biāo)    (window as any).document.getElementById(\"img\").src = \"../img/connected.svg\";    //獲取QObject對(duì)象    let car = WebChannelCore.SDK.car;        //取dom樹上的組件    let upBtn = (window as any).document.getElementById(\"up\");    let downBtn = (window as any).document.getElementById(\"down\");    let leftBtn = (window as any).document.getElementById(\"left\");    let rightBtn = (window as any).document.getElementById(\"right\");    let speedLabel = (window as any).document.getElementById(\"speed\");    let angleLabel = (window as any).document.getElementById(\"angle\");    //綁定按鈕點(diǎn)擊事件    upBtn.onclick = () =>{        //調(diào)用QObject的接口        car.accelerate();    }    downBtn.onclick = () =>{        car.decelerate();    }    leftBtn.onclick = () =>{        car.turnLeft();    }    rightBtn.onclick = () =>{        car.turnRight();    }    //QObject的信號(hào)連接到j(luò)s 回調(diào)    car.speedChanged.connect(onSpeedChanged);    car.angleChanged.connect(onAngleChanged);}//WebChannel斷開連接的處理function onUninit() {    //換圖標(biāo)    (window as any).document.getElementById(\"img\").src = \"../img/disconnected.svg\";}//異步更新 speedasync function onSpeedChanged() {    let speedLabel = (window as any).document.getElementById(\"speed\");    let car = WebChannelCore.SDK.car;    //獲取speed,異步等待。    //注意這里改造過qwebchannel.js,才能使用await。    speedLabel.textContent = await car.getSpeed();}//異步更新 angleasync function onAngleChanged() {    let angleLabel = (window as any).document.getElementById(\"angle\");    let car = WebChannelCore.SDK.car;    //獲取angle,異步等待。    //注意這里改造過qwebchannel.js,才能使用await。    angleLabel.textContent = await car.getAngle();}

可以看到我們從WebChannelCore.SDK 中獲取了一個(gè)car對(duì)象,之后就當(dāng)作QObject來用了,包括調(diào)用它的函數(shù)、連接change信號(hào)、訪問屬性等。

這一切都得益于WebSocket/WebChannel.

TypeScript中連接websocket

接下來看一下WebChannelCore的實(shí)現(xiàn)

//WebChannelCore.tsimport { QWebChannel } from "./qwebchannel";type callback = () =>void;export default class WebChannelCore {    public static SDK: any = undefined;    private static connectedCb: callback;    private static disconnectedCb: callback;    private static socket: WebSocket;        //初始化函數(shù)    public static initialize(connectedCb: callback = () =>{ }, disconnectedCb: callback = () =>{ }) {        if (WebChannelCore.SDK != undefined) {            return;        }        //保存兩個(gè)回調(diào)        WebChannelCore.connectedCb = connectedCb;        WebChannelCore.disconnectedCb = disconnectedCb;        try {            //調(diào)用link,并傳入兩個(gè)回調(diào)參數(shù)            WebChannelCore.link(                (socket) =>{                  //socket連接成功時(shí),創(chuàng)建QWebChannel                    QWebChannel(socket, (channel: any) =>{                        WebChannelCore.SDK = channel.objects;                        WebChannelCore.connectedCb();                    });                }                , (error) =>{                  //socket出錯(cuò)                    console.log(\"socket error\", error);                    WebChannelCore.disconnectedCb();                });        } catch (error) {            console.log(\"socket exception:\", error);            WebChannelCore.disconnectedCb();            WebChannelCore.SDK = undefined;        }    }    private static link(resolve: (socket: WebSocket) =>void, reject: (error: Event | CloseEvent) =>void) {        //獲取Query參數(shù)中的websocket地址        let baseUrl = \"ws://localhost:12345\";        if (window.location.search != \"\") {            baseUrl = (/[?&]webChannelBaseUrl=([A-Za-z0-9\-:/\.]+)/.exec(window.location.search)![1]);        }        console.log(\"Connectiong to WebSocket server at: \", baseUrl);                //創(chuàng)建WebSocket        let socket = new WebSocket(baseUrl);        WebChannelCore.socket = socket;        //WebSocket的事件處理        socket.onopen = () =>{            resolve(socket);        };        socket.onerror = (error) =>{            reject(error);        };        socket.onclose = (error) =>{            reject(error);        };    }}(window as any).SDK = WebChannelCore.SDK;

這部分代碼不復(fù)雜,主要是連接WebSocket,連接好之后創(chuàng)建一個(gè)QWebChannel。

TypeScript中的QWebChannel

觀察仔細(xì)的同學(xué)會(huì)發(fā)現(xiàn),src文件夾下面,沒有叫‘qwebchannel.ts’的文件,而是‘qwebchannel.js’,和一個(gè)‘qwebchannel.d.ts’

這涉及到另一個(gè)話題:

TypeScript中使用javaScript

‘qwebchannel.js’是Qt官方提供的,在js中用足夠了。

而我們這里是用TypeScript,按照TypeScript的規(guī)則,直接引入js是不行的,需要一個(gè)聲明文件 xxx.d.ts

所以我們?cè)黾恿艘粋€(gè)qwebchannel.d.ts文件。

(熟悉C/C++的同學(xué),可以把d.ts看作typescript的頭文件)

內(nèi)容如下:

//qwebchannel.d.tsexport declare function QWebChannel(transport: any, initCallback: Function): void;

只是導(dǎo)出了一個(gè)函數(shù)。

這個(gè)函數(shù)的實(shí)現(xiàn)在‘qwebchannel.js’中:

//qwebchannel.js\"use strict\";var QWebChannelMessageTypes = {    signal: 1,    propertyUpdate: 2,    init: 3,    idle: 4,    debug: 5,    invokeMethod: 6,    connectToSignal: 7,    disconnectFromSignal: 8,    setProperty: 9,    response: 10,};var QWebChannel = function(transport, initCallback){    if (typeof transport !== \"object\" || typeof transport.send !== \"function\") {        console.error(\"The QWebChannel expects a transport object with a send function and onmessage callback property.\" +                      \" Given is: transport: \" + typeof(transport) + \", transport.send: \" + typeof(transport.send));        return;    }    ...}function QObject(name, data, webChannel){  ...}

這個(gè)代碼比較長(zhǎng),就不全部貼出來了。主要實(shí)現(xiàn)了兩個(gè)類,QWebChannel和QObject。

QWebChannel就是用來接管websocket的,而QObject是用js Object模擬的 Qt的 QObject。

這一塊不細(xì)說了,感興趣的同學(xué)可以自己去研究源碼。

改進(jìn)qwebchannel.js以支持await

Qt默認(rèn)的qwebchannel.js在實(shí)際使用過程中,有些不好的地方,就是函數(shù)的返回值不是直接返回,而是要在回調(diào)函數(shù)中獲取。

比如car.getAngle要這樣用:

let angle = 0;car.getAngle((value:number)=>{  angle = value;});

我們的實(shí)際項(xiàng)目中,有大量帶返回值的api,這樣的用法每次都嵌套一個(gè)回調(diào)函數(shù),很不友好,容易造成回調(diào)地獄。

我們同事的解決方案是,在typescript中把這些api再用Promise封裝一層,外面用await調(diào)用。

例如這樣封裝一層:

function getAngle () {    return new Promise((resolve)=>{        car.getAngle((value:number)=>{            resolve(value);        });  });}

使用和前面的代碼一樣:

//異步更新 angleasync function onAngleChanged() {    let angleLabel = (window as any).document.getElementById(\"angle\");    let car = WebChannelCore.SDK.car;    //獲取angle,異步等待。    //注意這里改造過qwebchannel.js,才能使用await。    angleLabel.textContent = await car.getAngle();}

這種解決方案規(guī)避了回調(diào)地獄,但是工作量增加了。

濤哥思考良久,稍微改造一下qwebchannel.js,自動(dòng)把Promise加進(jìn)去,也不需要再額外封裝了。

QObject to Typescript

我們?cè)赒t 程序中寫了QObject,然后暴露給了ts。

在ts這邊,一般也需要提供一個(gè)聲明文件,明確有哪些api可用。

例如我們的car聲明:

//CarObject.tsdeclare class Car {    get speed():number;    set speed(value:number);    get angle():number;    set angle(vlaue:number);    public accelerate():void;    public decelerate():void;    public turnLeft():void;    public turnRight():void;}

這里濤哥寫了一個(gè)小工具,能夠解析Qt中的QObject,并生成對(duì)應(yīng)的ts文件。

【領(lǐng) QT開發(fā)教程 學(xué)習(xí)資料, 點(diǎn)擊下方鏈接莬費(fèi)領(lǐng)取↓↓ ,先碼住不迷路~】

點(diǎn)擊這里:

標(biāo)簽:

x 廣告
x 廣告

Copyright ©   2015-2022 太平洋影視網(wǎng)版權(quán)所有  備案號(hào):豫ICP備2022016495號(hào)-17   聯(lián)系郵箱:93 96 74 66 9@qq.com