看到標題,你大概能猜到這篇我想講述的是什麼了。對的,將要同大家分享的是一種目前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
本篇先暫時到這兒,有疑問或者有好的建議的請留言不吝指教。謝謝。後面接着講項目開發。