Qt Quick 技術的引入,使得你可以快速構建 UI ,具備動畫、各類絢麗效果的 UI 都不在話下。但它不是萬能的,也有不少侷限性,原來 Qt 的一些技術,好比低階的網絡編程如 QTcpSocket ,多線程,又如 XML 文檔處理類庫 QXmlStreamReader / QXmlStreamWriter 等等,在 QML 中要麼不可用,要麼用起來不方便,因此呢,不少時候咱們是會基於這樣的原則來混合使用 QML 和 C++: QML 構建界面, C++ 實現非界面的業務邏輯和複雜運算。react
QML 的不少基本類型本來就是在 C++ 中實現的,好比 Item 對應 QQuickItem , Image 對應 QQuickImage , Text 對應 QQuickText ,……這樣看來,在 QML 中訪問 C++ 對象必然不成問題。然也!反過來,在 C++ 中其實也可使用 QML 對象。編程
在 QML 中使用 C++ 類和對象
咱們知道, QML 實際上是對 JavaScript 的擴展,融合了 Qt Object 系統,它是一種新的解釋型的語言, QML 引擎雖然由 Qt C++ 實現,但 QML 對象的運行環境,說到底和 C++ 對象的上下文環境是不一樣的,是平行的兩個世界。若是你想在 QML 中訪問 C++ 對象,那麼必然要找到一種途徑來在兩個運行環境之間創建溝通橋樑。網絡
Qt 提供了兩種在 QML 環境中使用 C++ 對象的方式:多線程
- 在 C++ 中實現一個類,註冊到 QML 環境中, QML 環境中使用該類型建立對象
- 在 C++ 中構造一個對象,將這個對象設置爲 QML 的上下文屬性,在 QML 環境中直接使用改屬性
無論哪一種方式,對要導出的 C++ 類都有要求,不是一個類的全部方法、變量均可以被 QML 使用,所以咱們先來看看怎樣讓一個方法或屬性能夠被 QML 使用。app
實現能夠導出的 C++ 類
前提條件
- 從 QObject 或 QObject 的派生類繼承
- 使用 Q_OBJECT 宏
信號,槽
咱們首先來看一個完整類的實現。函數
LogicMaker.hpost
#include <QObject> class LogicMaker:public QObject { Q_OBJECT Q_ENUMS(kGameType) //直接調用函數,非槽函數 Q_INVOKABLE void qmlCallCfunction(); Q_PROPERTY(int width READ width WRITE setWidth NOTIFY widthChanged) public: LogicMaker(QObject *p); LogicMaker(){ } enum kGameType{ TYPE_DOTA=2, TYPE_WAR3, TYPE_RPG, }; int width(); void setWidth(int); signals: void widthChanged(int newwidth); public slots: void qmlCallCSlotfunction(kGameType type); private: int _width; QObject* pparent; };
LogicMaker.cpp動畫
#include "logicmaker.h" #include<QDebug> #include <QQuickView> #include <QQuickItem> LogicMaker::LogicMaker(QObject *obj) { _width=0; pparent=obj; qDebug()<<"parent"<<pparent; } void LogicMaker::qmlCallCSlotfunction(kGameType type){ qDebug()<<"qml call C++ slots function"<<type; setWidth(5); qDebug()<<"=======parent"<< this->parent(); QObject *quitButton = this->parent()->findChild<QObject*>("qmlbtn2");//要在qml中設置其對應的objname if(quitButton!=NULL) { QObject::connect(quitButton, SIGNAL(clicked()), this, SLOT(qmlCallCfunction())); //setText這個必定會調用失敗,由於並無setText這個屬性 bool bRet = QMetaObject::invokeMethod(quitButton, "setText", Q_ARG(QString, "world hello")); qDebug() << "call setText return - " << bRet; quitButton->setProperty("width", 200); quitButton->setProperty("text", QString(tr("hello,world"))); } else { qDebug()<<"get button failed"; } } void LogicMaker::qmlCallCfunction(){ qDebug()<<"qml call C++ function"; } int LogicMaker::width(){ return _width; } void LogicMaker::setWidth(int w){ _width=w; emit widthChanged(w); }
main.cppui
#include <QGuiApplication> #include <QQuickView> #include <QQuickItem> #include <QQmlContext> include "logicmaker.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); // QQmlApplicationEngine engine; //engine.load(QUrl(QStringLiteral("qrc:///main.qml"))); //在qml環境準備好以前,註冊好qml類型 qmlRegisterType<LogicMaker>("seanyxie.qt.logicMaker", 1, 0,"LogicMaker"); QQuickView viwer; viwer.setSource(QUrl(QStringLiteral("qrc:///main.qml"))); // QObject *rootItem=NULL; QQuickItem *rootItem = viwer.rootObject(); qDebug()<<rootItem; viwer.rootContext()->setContextProperty("cpplogicMaker",new LogicMaker(rootItem)); viwer.show(); return app.exec(); }
main.qml
import QtQuick 2.2 import QtQuick.Window 2.1 import QtQuick.Controls 1.1 import seanyxie.qt.logicMaker 1.0 Rectangle { visible: true width: 360 height: 360 id:rect MouseArea { anchors.fill: parent onClicked: { Qt.quit(); } } Text { text: qsTr("Hello World") anchors.centerIn: parent } LogicMaker{ id:qml2Cmaker; } Row{ Button{ id:btn1 onClicked: { qml2Cmaker.qmlCallCSlotfunction(LogicMaker.TYPE_RPG); // cpplogicMaker.qmlCallCSlotfunction(LogicMaker.TYPE_RPG); } } Button{ id:btn2 objectName:"qmlbtn2" onClicked: { // qml2Cmaker.qmlCallCfunction(); } } } //這個對象是用來綁定一個從C++來的信號,對應的曹函數 Connections{ target:qml2Cmaker onWidthChanged: { btn1.width = newwidth // console.log("width change slot %d",newwidth); } } }
第一步,在main.cpp裏面咱們註冊了一個類,能夠在qml中直接被使用,就是這段代碼
qmlRegisterType<LogicMaker>("seanyxie.qt.logicMaker", 1, 0,"LogicMaker");
這個過程大概分四個步驟:
- 實現 C++ 類
- 註冊 QML 類型
- 在 QML 中導入類型
- 在 QML 建立由 C++ 導出的類型的實例並使用
要註冊一個 QML 類型,有多種方法可用,如 qmlRegisterSingletonType() 用來註冊一個單例類型, qmlRegisterType() 註冊一個非單例的類型, qmlRegisterTypeNotAvailable() 註冊一個類型用來佔位, qmlRegisterUncreatableType() 一般用來註冊一個具備附加屬性的附加類型
qmlRegisterType()是一個模板函數
template<typename T> int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName); template<typename T, int metaObjectRevision> int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName);
因此這裏註冊的類,在qml中使用的話,就要先
import seanyxie.qt.logicMaker 1.0
在 QML 中建立 C++ 導入類型的實例
LogicMaker{
id:qml2Cmaker;
}
咱們看到,LogicMaker和Rectangle等用法沒有什麼不一樣,指定一個id,就能夠在qml中直接使用這個對象。
咱們在LogicMaker中定義了槽函數qmlCallCSlotfunction(),能夠直接在qml中使用qml2Cmaker對象來調用這個槽函數,可是還有一個參數,這個參數是C++類裏的枚舉,這時候須要要QENUM宏來導出這組枚舉。
Q_ENUMS
一旦你使用 Q_ENUMS 宏註冊了你的枚舉類型,在 QML 中就能夠用 ${CLASS_NAME}.${ENUM_VALUE} 的形式來訪問,好比 LogicMaker.TYPE_DOTA,上節展現的 QML 代碼片斷已經使用了導出的枚舉類型。
Q_INVOKABLE 宏
Q_PROPERTY
Q_PROPERTY(type name (READ getFunction [WRITE setFunction] | MEMBER memberName [(READ getFunction | WRITE setFunction)]) [RESET resetFunction] [NOTIFY notifySignal] [REVISION int] [DESIGNABLE bool] [SCRIPTABLE bool] [STORED bool] [USER bool] [CONSTANT] [FINAL])
是否是很複雜?你能夠爲一個屬性命名,能夠設定的選項數超過10個……我是以爲有點兒頭疼。不過,不是全部的選項都必須設定,看一個最簡短的屬性聲明:
Q_PROPERTY(int width READ width WRITE setWidth NOTIFY widthChanged)
- READ 標記,若是你沒有爲屬性指定 MEMBER 標記,則 READ 標記必不可少;聲明一個讀取屬性的函數,該函數通常沒有參數,返回定義的屬性。
- WRITE 標記,可選配置。聲明一個設定屬性的函數。它指定的函數,只能有一個與屬性類型匹配的參數,必須返回 void 。
- NOTIFY 標記,可選配置。給屬性關聯一個信號(該信號必須是已經在類中聲明過的),當屬性的值發生變化時就會觸發該信號。信號的參數,通常就是你定義的屬性。
因此上述定義的width屬性,能夠用width,setWidth來讀寫,而且在width發生變化時候,關聯一個信號widthChanged的信號。
因此這段代碼的表現效果是:
點擊了btn1按鈕後,會經過LogicMaker的對象qml2Cmaker調用C++裏LogicMaker的槽函數qmlCallCSlotFunction,而且帶一個枚舉類型的參數。而後在qmlCallCSlotFunction方法裏,調用setWidth來設置width屬性,而且發射出信號widthChanged的信號。
這個信號將會被qml捕獲處理,在qml中有下面一段代碼:
Connections{ target:qml2Cmaker onWidthChanged: { btn1.width = newwidth // console.log("width change slot %d",newwidth); } }
Connections的解釋:
A Connections object creates a connection to a QML signal.
When connecting to signals in QML, the usual way is to create an 「on<Signal>」 handler that reacts when a signal is received, like this:
MouseArea { onClicked: { foo(parameters) } }
就是用來綁定一個QML信號的處理對象,它的槽函數使用on+信號名的格式,因此qml中onWidthChanged,在設定了target屬性後,就會綁定qml2Cmaker對象的信號WidthChanged,這個信號在C++中發出,並在qml中處理。從而修改了btn1按鈕的寬度。
導出一個 C++ 對象爲 QML 的屬性
咱們看main.cpp裏代碼
QQuickItem *rootItem = viwer.rootObject();
qDebug()<<rootItem;
viwer.rootContext()->setContextProperty("cpplogicMaker",new LogicMaker(rootItem));
還有一點要說明,由於咱們去掉了 qmlRegisterType() 調用,因此在 main.qml 中不能再訪問LogicMaker 類了,好比你不能經過類名來引用它定義的枚舉類,也不能定義LogicMaker對象了。
而後咱們就能夠在qml中使用這個全局可見對象cpplogicMaker了。
在 QML 中使用關聯到 C++ 對象的屬性
cpplogicMaker.qmlCallCSlotfunction(LogicMaker.TYPE_RPG);
固然這裏由於處處了LogicMaker類,因此才能訪問LogicMaker.TYPE_RPG枚舉。
在 C++ 中使用 QML 對象
查找一個對象的孩子
T QObject::findChild(const QString & name = QString(),\ Qt::FindChildOptions options = \ Qt::FindChildrenRecursively) const; QList<T> QObject::findChildren(const QString & name = \ QString(), Qt::FindChildOptions options = \ Qt::FindChildrenRecursively) const; QList<T> QObject::findChildren(const QRegExp & regExp, \ Qt::FindChildOptions options = \ Qt::FindChildrenRecursively) const; QList<T> QObject::findChildren(const QRegularExpression & re,\ Qt::FindChildOptions options = \ Qt::FindChildrenRecursively) const;
- QPushButton *button = parentWidget->findChild<QPushButton *>(「button1」);
查找 parentWidget 的名爲 「button1」 的類型爲 QPushButton 的孩子。
- QList<QWidget *> widgets = parentWidget.findChildren<QWidget *>(「widgetname」);
返回 parentWidget 全部名爲 「widgetname」 的 QWidget 類型的孩子列表。
使用元對象調用一個對象的方法
bool QMetaObject::invokeMethod(QObject * obj, const char * member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0 = QGenericArgument( 0 ), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument()) [static]
QGenericArgument Q_ARG( Type, const Type & value)
返回類型是相似的,使用 QGenericReturnArgument 表示,你可使用 Q_RETURN_ARG 宏來構造一個接收返回指的參數,它的定義是:
QGenericReturnArgument Q_RETURN_ARG( Type, Type & value)
假設一個對象有這麼一個槽 compute(QString, int, double) ,返回一個 QString 對象,那麼你能夠這麼調用(同步方式):
QString retVal; QMetaObject::invokeMethod(obj, "compute", Qt::DirectConnection, Q_RETURN_ARG(QString, retVal), Q_ARG(QString, "sqrt"), Q_ARG(int, 42), Q_ARG(double, 9.7));
若是你要讓一個線程對象退出,能夠這麼調用(隊列鏈接方式):
QMetaObject::invokeMethod(thread, "quit", Qt::QueuedConnection);
因此在LogicMaker類的函數中能夠這樣來調用qml中的對象
void LogicMaker::qmlCallCSlotfunction(kGameType type){ qDebug()<<"qml call C++ slots function"<<type; setWidth(5); qDebug()<<"=======parent"<< this->parent(); QObject *quitButton = this->parent()->findChild<QObject*>("qmlbtn2");//要在qml中設置其對應的objname if(quitButton!=NULL) { QObject::connect(quitButton, SIGNAL(clicked()), this, SLOT(qmlCallCfunction())); //setText這個必定會調用失敗,由於並無setText這個屬性 bool bRet = QMetaObject::invokeMethod(quitButton, "setText", Q_ARG(QString, "world hello")); qDebug() << "call setText return - " << bRet; quitButton->setProperty("width", 200); quitButton->setProperty("text", QString(tr("hello,world"))); } else { qDebug()<<"get button failed"; } }