QCefView + QWebChannel + Vue 項目開發

       看到標題,你大概能猜到這篇我想講述的是什麼了。對的,將要同大家分享的是一種目前PC軟件常見的開發方案。

前言

1、桌面應用爲什麼要使用Vue等框架開發,而不再是Qt UI開發?       

     界面開發我們使用java語言,通過Vue框架快速開發,好處不言而喻,開發速度快,界面美觀,同時兼具跨平臺特性。其次,網頁端與PC應用可同步開發和維護,同步更新,上線快,後期維護成本低。

    有朋友會問,Qt的QML和QSS也完全可以做出極其精美和出彩的界面來呀,不可否認,但是這樣做還有一個不可比擬的優勢:可將項目打包發佈到雲服務器,後期界面更新、維護更加方便,不必因爲界面佈局的修改而被動升級更新桌面應用程序。就拿前4.6的全國哀悼日來說,我們的桌面應用也可以變灰,也可以即時做出樣式風格的調整。

2、網頁加載爲什麼用QCefView(或者CEF),而不是Qt自帶的 QWebEngineView或者QWebKit組件?

     不再使用QWebEngineVIew 和QWebKit,實在是因爲筆者在實際的項目開發中遇到了太多的坑。

     QWebKit : 如果是出學Qt,需要用到Qt加載和渲染網頁的朋友,Qt5以下的版本,你可以嘗試QWebKit套件,真心講,如果不做Qt與JS之間的頻繁通訊,webkit組件夠用,而且系統和平臺的兼容性也比較好,使用簡單,開發速度也很快。前期我在使用Qt4.8.3的版本開發過的一個項目中,亦使用webkit加載百度地圖,實現錨點、動態信息展示等,效果還可以,但是不得不否認他存在嚴重的內存泄露,這也是爲什麼Qt5以後,QWebKit組件被放棄的原因。但是細心的朋友可以看到WPS實際上也是使用了Qt4+webkit的方案,但是內存上泄漏並不嚴重,WPS做了某些處理就不得而知了。

     QWebEngineView:不使用QWebEngineView的原因很簡單,一個是隻支持MSVC編譯工具鏈,接口不豐富,無法和QFrame進行交互;其次就是系統兼容性真的很不友好。如果系統自帶的.netframe版本過低,QWebengineView 編譯的程序在windows7無法運行;

    筆者曾在windows7下部署 運行調試環境,在QWebEngineView實例化對象時總是崩潰,後來發現直接和根本原因是跟計算機顯卡驅動有關係。

(1)QWebEngineView在運行之前需要檢查本地硬件環境,硬件必須要支持OpenGL2.0以上的版本,否則無法運行。

(2)機器的顯卡和系統所帶的顯卡驅動不匹配,導致QtWebEngine在渲染時出現了崩潰。用戶需要手動更新顯卡驅動來解決這個問題。

   QCefView : 基於CEF的封裝,對硬件要求低,性能好(XP、windowNT和其他Unix、MacOS都可以支持)。CEF提供了很多複雜精細的功能,相對比較難理解,但是QCefView做了封裝之後,難度減少很多 。愛打Dota1的朋友可以看看 09電競平臺使用的也是Qt+libcef的方案。

3、爲什麼使用QWebChannel來實現Qt與JS的通訊,而不是我們在上一篇(QCefView(2)—— 實現Qt與JS之間的通訊)中向大家介紹的方法?

     首先說明,上一篇講訴的方法還是很不錯的,參照demo,如果是加載本地的HTML頁面,值得推薦:但是在我們使用Vue框架時,會報出QCefClient未定義的問題,原因是Vue中並沒有加載此模塊,自然無法識得;不需要使用QCefClient的方法也有,如果你有足夠的能力,可以在CEF的基礎上自己進行封裝,看到簡書上有大神的教程過,可以去做參考下。所以QWebChannel才成爲了備選方案,一個是因爲Qt已經爲QWebChannel做了足夠的完善,在Vue中我們也可以很方便的通過 

import { QWebChannel } from "@calvinscofield/qwebchannel"

進行導入,其次就是其通訊基於本地的websocket進行,實時性也很高,操作起來簡單明瞭,故而成了明智之選。還是說到 09電競平臺,你可以看到其通訊部分實際上也是採用了QWebChannel組件。

 

項目開發

1、在Vue中進行通訊配置

僞代碼如下:
import { QWebChannel } from "@calvinscofield/qwebchannel"

...

 /// 譬如在某個按鈕點擊時調用add()發送消息給Qt
 add() {
    this.websocketsend()   
 },

    // websocket連接
 initWebpack() {
      //初始化websocket
     if ("WebSocket" in window) {
        const baseUrl = "ws://localhost:32145";
        this.websock = new WebSocket(baseUrl); //這裏面的this都指向vue
        this.websock.onopen = this.websocketopen;
        this.websock.onmessage = this.websocketonmessage;
        this.websock.onclose = this.websocketclose;
        this.websock.onerror = this.websocketerror;
     } else {
        // 瀏覽器不支持 WebSocket
        alert("您的瀏覽器不支持 WebSocket!");
     }
  },

 websocketopen() {
      //打開
      console.log("WebSocket連接成功");
          new QWebChannel(this.websock, function(channel) {
            window.bound = channel.objects.CPPJSInterface //連接qt信號
             window.bound.CPPToJSFunc.connect(function(returnValue, val) {
          //通過信號接收返回值
          alert("returnValue=" + returnValue + val)
      })
      })
    },
    
    websocketsend() { // 數據發送

      var str1 = 80
      var str2 = "world"
      window.bound.CPPJSInterfaceFunc(str1, str2)
    },
    websocketonmessage(e) {
      //數據接收
     
    },
    websocketclose(e) {
      //關閉
      console.log("connection closed (" + e.code + ")");
      console.log("WebSocket關閉");
    },
    websocketerror() {
      //失敗
      console.log("WebSocket連接失敗");
    },

2、在Qt中進行頁面加載和通訊模塊編寫

2.1  通過QCefView加載網頁

        QHBoxLayout* layout = new QHBoxLayout();
	layout->setContentsMargins(2, 2, 2, 2);
	layout->setSpacing(3);

#if defined(LOCAL_TEST)  // test local html
	QDir dir = QCoreApplication::applicationDirPath();
	QString uri = QDir::toNativeSeparators(dir.filePath("QCefViewTestPage.html"));
	cefview = new CustomCefView(uri, this);
#else  // load webpage
	cefview = new CustomCefView(CUR_IOT_URL, this); // www.unimatiot.com
#endif //LOCAL_TEST
 	
	layout->addWidget(cefview);
	centralWidget()->setLayout(layout)

2.2 通過QWebChannel實現通訊

(1)在Qt上,創建一個本地websocket服務器端,網頁端則是對應的websocket客戶端,注意端口號一致。

(2)注意啓動順序,應先啓動websocket服務器,然後再實例化窗體對象(窗體中包含了QCefView的子對象,會在窗體對象實例化過程中加載網頁,即啓動websocket客戶端程序),否則會導致網頁上websocket客戶端無法連接到本地websocket服務器。

(3)註冊對象:

channel.registerObject(QStringLiteral("CPPJSInterface"), &core);

那麼,在JS中便可以通過”CPPJSInterface“ 對象來調用 Qt中 core class中的槽函數實現 JS發送消息給Qt了。

int main(){
    QApplication a(argc, argv);
    QFileInfo jsFileInfo(QDir::currentPath() + "/qwebchannel.js");

	if (!jsFileInfo.exists()) QFile::copy(":/qtwebchannel/qwebchannel.js", jsFileInfo.absoluteFilePath());

	// setup the QWebSocketServer
	QWebSocketServer server(QStringLiteral("QWebChannel Standalone Example Server"), QWebSocketServer::NonSecureMode);
	if (!server.listen(QHostAddress::LocalHost, 32145)) {
		qFatal("Failed to open web socket server.");
		return 1;
	}

	// wrap WebSocket clients in QWebChannelAbstractTransport objects
	WebSocketClientWrapper clientWrapper(&server);

	// setup the channel
	QWebChannel channel;
	QObject::connect(&clientWrapper, &WebSocketClientWrapper::clientConnected,
		&channel, &QWebChannel::connectTo);

	UManager w;
	
	// setup the core and publish it to the QWebChannel
	Core core(&w);
    // 註冊通訊對象==》QWebChannle進行通訊很重要的一點
	channel.registerObject(QStringLiteral("CPPJSInterface"), &core);

    w.show();
    return a.exec();
}

  在core.h中我們做消息的收發處理:

#ifndef CORE_H
#define CORE_H

#include "umanager.h"
#include <QObject>

/*
    An instance of this class gets published over the WebChannel and is then accessible to HTML clients.
*/
class Core : public QObject
{
    Q_OBJECT

public:
    Core(UManager *dialog, QObject *parent = nullptr)
        : QObject(parent), m_dialog(dialog)
    {
        connect(dialog, &UManager::sendText, this, &Core::sendText);// Qt send msg to js ,connect signal A with signal B
		connect(dialog, &UManager::CPPToJSFunc, this, &Core::CPPToJSFunc);

		connect(this, &Core::textRecived, dialog, &UManager::CPPJSInterfaceFunc); // 接收到的消息將轉發到UManager進行分發處理
    }

signals:
    /*
        This signal is emitted from the C++ side and the text displayed on the HTML client side.
    */
    void sendText(const QString &text);

    /****************************************************************************/
	void CPPToJSFunc(int index, QString paramters); // send message from cpp to js.
    /****************************************************************************/

	void textRecived(int index, QString paramters); // core對象接收到JS端發送的消息會觸發此信號

public slots:

    /*
        This slot is invoked from the HTML client side and the text displayed on the server side.
    */
    void receiveText(const QString &text)
    {
        m_dialog->displayMessage(UManager::tr("Received message: %1").arg(text));
		sendText(text.toUpper());

    }

    /*******************************************************/
	void CPPJSInterfaceFunc(int index, QString paramters) {
		emit textRecived(index, paramters);
	}
    /*******************************************************/

private:
	UManager *m_dialog;
};

#endif // CORE_H

本篇先暫時到這兒,有疑問或者有好的建議的請留言不吝指教。謝謝。後面接着講項目開發。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章