背景:
【qml-1】qml與c++交互第一次嘗試(實例方式)
【qml-2】嘗試一個有模式的qml彈窗-CSDN博客
?【qml-3】qml與c++交互第二次嘗試(類型方式)
還是qml學習筆記。
這次擱置太久了。其實不太會,還是以教程為主,但講課那哥們也是以嘗試為主,只是人家經驗多。這里吐槽一下,qml目前還是差太多。
官方手冊都啥也不是,遠不如widget完善好用。版本差異有些挺大,比如學了5再用6,有些真不一樣。我干脆從最新的6開始搞,因為5早晚淘汰。
寫代碼時有些屬性明明能用,但就是報錯說非法,你得按住ctrl轉到類型定義,還不能跳轉到頭文件,彈窗報錯但能復制那個頭文件的名,然后去文件系統qt安裝目錄里找到它再參考,發現里面有這個屬性可以用。
先吐槽這些,也許是自己太笨。
回顧:
之前寫過實例方式的qml和c++交互。
【qml-1】qml與c++交互第一次嘗試(實例方式)
亦即,在c++里直接把類型實例化,把對象指針注冊到qml上下文,然后qml直接調用它。
這種方式倒是直截了當,但是從qml里看有些晦澀,冷不丁出來個對象名,拿來就用。
個人感覺這種方式還可以,適合規模小的項目,簡單高效。
類型方式:
本次記錄的是類型方式,亦即把c++里寫好的類,不實例化,而是把類型注冊給qml,就像它自己的Button、Item一樣,用的時候使用“{}”給它實例化。這么說起來好像更合適一些。那就上demo。
quick項目說明:
這里要插入一段,我覺得按照官方態度,應該是讓咱們用Design Studio(DS)做ui,導出到c++的quick項目再混合編程。
這個該死的DS熟悉起來別具一格,等有時間再整理。這里主要說quick項目。
如上圖,兩種類型,圖上選中的是分類類型,構建后qml文件會復制到build目錄,發布時跟著exe一起,前后端分離。好處是可以隨時改qml不用編譯exe,不好就是所謂不安全。
compat那個是把qml放到qrc資源文件,它就一起編譯進exe發布,所謂更安全。
隱約記著其實web也有這種概念,以前做c#全棧,我們都是上傳aspx,改著方便。
這次demo我選的分離方式,無所謂,改一下很簡單。
demo:
如上所說我選的不帶compat的quick項目,一路下一步就行,它默認是cmake方式構建。
如上圖,我已經添加了一個MyClass類。代碼如下:
#ifndef MYCLASS_H
#define MYCLASS_H#include <QObject>
#include <QQmlEngine>
#include <QDebug>class MyClass : public QObject
{Q_OBJECTQML_ELEMENT
public:explicit MyClass(QObject *parent = nullptr);public slots:QString onFunc(){qDebug() << "slot in cpp";return "cpp value";}void onFromQml_GetValue(){qDebug() << "onFromQml_GetValue";emit sigToQml_SendValue("cpp_value");}signals:void sigToQml_SendValue(QString);
};#endif // MYCLASS_H
#include "myclass.h"MyClass::MyClass(QObject *parent): QObject{parent}
{qDebug() << "Class is created in cpp.";
}
已經盡量簡單了。主要為了驗證一些事。
上面QString onFunc()這個槽函數加了返回值。各位應該記得,qt手冊里講過,connect的隊列模式下信號是拿不到返回值的,因為是異步。只有direct和阻塞隊列方式可以,也容易理解。這里不深究qt手冊了,您可以自己看,要是我記錯了咱再討論。我這里就是為了驗證qml和c++之間是個啥情況。
onFromQml_GetValue槽函數就再正常不過了,不多解釋。就是讓它接受qml發來的信號,再把“返回值”發給qml。我就是想實現qml調用c++,比如查詢。
再看qml代碼:
import QtQuick
import QtQuick.Controls
import cpp.MyClassWindow {id: rootwidth: 640height: 480visible: truetitle: qsTr("Hello World")//qml查詢信號signal sigToCpp_GetValueonSigToCpp_GetValue: {myclass.onFromQml_GetValue();}//c++返回信號Connections {target: myclass//以下兩種方式都可以,非要共存那就下面的傳統方式優先,我試出來的。//Connections里面,function方式推薦,官方已經不推薦傳統方式了。 function onSigToQml_SendValue(s) {btn.text = s;print("-----" + s);}// onSigToQml_SendValue: (s) => { root.title = s; }}//C++實例化MyClass {id: myclass}Button {id: btnwidth: 100height: 40text: "press me"onClicked: {// text = myclass.onFunc();//這是可以的sigToCpp_GetValue();//發送qml信號}}
}
CMakeLists.txt
...
qt_add_executable(appuntitledmain.cpp# myclass.h myclass.cpp #注意這里
)qt_add_qml_module(appuntitledURI cpp.MyClass #注意這里VERSION 1.0QML_FILESMain.qmlSOURCES myclass.h myclass.cpp #注意這里
)
...
對于CMakeList.txt的內容,說明一下。在qml分離方式的項目中添加類,它會在CMakeLists.txt中把頭文件和源文件加到qt_add_qml_module函數中,在qml資源方式的項目中,它會加到qt_add_executable函數中。親測效果沒影響,但是個人感覺加在qt_add_executable中更合適。這算一個討論點。因為目前我沒試出來區別。
注意,cmake里設置的URI是qml里import時用的模塊,同時對應build目錄中那個擴展名是qmltypes的文件路徑。至于自己定義的c++類名,和這個沒有直接關系。如果有多個c++類要為qml服務,可以都加到某個模塊中。就像QtQuick.Controls一樣,里面有好多類。
我之前以為,C++里只要寫了那個QML_ELEMENT宏就可以自動實現了,但不幸還得改cmake文件,這個URI是個地址,對應build目錄里的情況。
之前默認可不是這樣的,qt會默認一個untitile,你把它改成自己想要的就行了。當然對應main函數也要改。
因為項目選擇的是分離模式,理論上qml是不參與編譯,而是單獨的文件,所以這里寫的路徑其實對應build目錄里的文件路徑,發布時一并復制給目標機即可。
(我實測結果大相徑庭!qrc模式的exe編譯出來很小,一個文件包括所有,感覺很快很好用。分離模式exe很大,編譯很慢,也并不是所有qml文件復制過去修改就生效,我試出來的也就第一個加載的qml可以,其余被調用的qml修改不能反應到界面,跟想象差距太大,個人感覺非常難用。希望用qml做過大項目的朋友指點。或者以后總結出經驗再專門寫筆記。)
效果:
沒問題的,就是點擊按鈕,由qml向c++發送請求,c++返回數據。
解析:
其實最直接簡單的方式就是直接調c++的槽函數就行了,可以帶返回值,目前感覺最方便。
之前我以為普通函數也可以,實測不行,必須是槽函數。(后面有補充Q_INVOKABLE)
非要發信號走個流程就是上面那樣,跟第一篇博客(實例方式)一樣道理。就是實例化的時機不一樣。
問題:
無論是哪種方式的項目,看教程以及我親測都會有個問題。每次做完了運行不影響,但qml文件里總有警告,以下三步操作后警告會消失,不一定第幾步有效。
1、先運行一下,讓它生成build目錄,尋址需要。
2、關閉項目,重新打開項目。
3、關閉creator,重新打開。
到這里忍不住還是要吐槽,這可不是我一個人的問題,教程視頻里也是如此。還是不完善。您說呢?
補充:
上面說直接調用c++普通函數不行,其實是如果是普通函數,聲明時需要在前面加一個宏Q_INVOKABLE。具體看手冊吧,這個宏的意義就是讓函數可以被元對象系統調用。熟悉qt信號槽的朋友到這里應該明白原因了。qt推崇的松散耦合就是信號槽,元對象系統實現的,qml這種前后端分離的東西顯然也是基于元對象的信號槽。這就是為什么直接調用普通函數不可以,而是要加上這個宏。
先記錄到這里,本文完。