- 初學者小建議: 不妨先點贊後再學習, 進群免費拿demo,參考本文章一起學習, 效果更佳o~~~
1.Qml呼叫C++類
Qt QML模組提供了一組API,用來將C++類擴充套件QML中。您可以編寫擴充套件來新增自己的QML型別,擴充套件現有的Qt型別,或呼叫無法從普通QML程式碼訪問的C/C++函式
本章將學習如何使用C++類編寫QML擴充套件,其中包括屬性、QML function和屬性繫結等
為了方便大家理解,本章示例的函式實現能寫在標頭檔案,就寫在標頭檔案.
2.建立QML
將C++類擴充套件QML時,一般用來實現QML目前無法實現的功能,比如訪問系統資訊,檔案資訊等。
本章demo是顯示一個簡單的餅圖,建立一個C++類提供給QML使用
這裡匯入一個"import Charts 1.0"模組,然後建立一個名為"PieChart"的QML型別,該型別具有兩個屬性:name和color。
import QtQuick.Window 2.12 import Charts 1.0 Window { visible: true width: 640 height: 480 PieChart { width: 100; height: 100 name: "A simple pie chart" color: "red" } }
要做到這一點,我們需要一個C++類,它封裝了這個PieChart型別及其name和color兩個屬性。
3.建立C++類
由於QML大量使用了Qt的元物件系統,因此該類必須是:
- 繼承於QObject的派生類
- 並且有Q_OBJECT巨集
以下是我們的餅圖PieChart類,在piechart.h中定義:
#include <QtQuick/QQuickPaintedItem> #include <QColor> #include <QPen> #include <QPainter> #include <QDebug> #include <QTimer> class PieChart : public QQuickPaintedItem { Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName) Q_PROPERTY(QColor color READ color WRITE setColor) public: PieChart(QQuickItem *parent = 0); QString name() const { return m_name; } void setName(const QString &name) { m_name = name; } QColor color() const { return m_color; } void setColor(const QColor &color) { m_color = color; } void paint(QPainter *painter); private: QString m_name; QColor m_color; public slots: void onTimeout(); };
請注意,儘管color在QML中指定為字串.比如"#00FF00",但它會自動轉換為QColor物件,像“640x480”這樣的字串可以自動轉換為QSize值。
- 該類繼承自QQuickPaintedItem,因為我們希望在使用QPainter API執行繪圖操作時重寫QQuickPaintedItem::paint()。
- 如果類只是表示了某些資料型別,而不是實際需要顯示的內容,它可以簡單地從QObject繼承。
- 如果我們想建立一個不需要使用QPainter API執行繪圖操作的視覺化項,我們可以只對QQuickItem子類進行子類。
Ps:
- QQuickItem: Qt Quick中的所有可視項都繼承自QQuickItem。雖然QQuickItem沒有視覺外觀,但它定義了視覺專案中常見的所有屬性,如x和y位置、寬度和高度、錨定和Key處理支援。
- QQuickPaintedItem:繼承自QQuickItem,並擴充套件了Qt中的QPainter API函式,使得QPainter將能夠直接繪製到QML場景的紋理上。呼叫update()時可以重新繪製。在paint()中使用setAntaliasing()時可以設定抗鋸齒渲染
PieChart類使用Q_PROPERTY巨集定義了兩個屬性name和color,並重寫QQuickPaintedItem::paint()。
3.1 Q_PROPERTY介紹
Q_PROPERTY 巨集定義屬性的一些主要關鍵字的意義如下:
- READ 指定一個讀取屬性值的函式,沒有 MEMBER 關鍵字時必須設定 READ。
- WRITE 指定一個設定屬性值的函式,只讀屬性沒有 WRITE 設定。
- MEMBER 指定一個成員變數與屬性關聯,成為可讀可寫的屬性,無需再設定 READ 和 WRITE。
- RESET 是可選的,用於指定一個設定屬性預設值的函式。
- NOTIFY 是可選的,用於設定一個訊號,當屬性值變化時發射此訊號(在QML中經常用到,比如onXChanged)。
- DESIGNABLE 表示屬性是否在 Qt Designer 裡可見,預設為 true。
- CONSTANT 表示屬性值是一個常數,對於一個物件例項,READ 指定的函式返回值是常數,但是每個例項的返回值可以不一樣。具有 CONSTANT 關鍵字的屬性不能有 WRITE 和 NOTIFY 關鍵字。
- FINAL 表示所定義的屬性不能被子類過載。
在C++中屬性的使用
不管是否用 READ 和 WRITE 定義了介面函式,只要知道屬性名稱,就可以通過 QObject::property() 讀取屬性值,並通過 QObject::setProperty() 設定屬性值。
比如定義一個類:
class MyObj : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName) public: MyObj(QQuickItem *parent = 0) { } QString name() const { return m_name; } void setName(const QString &name) { qDebug()<<name; m_name = name; } // 新增了一個列印 private: QString m_name; // 用來儲存name屬性的值 };
然後我們呼叫setProperty時:
MyObj ct; ct.setProperty("name","1234"); // 將會呼叫setName()介面函式,並且列印"1234"
在QML中屬性的使用(在"5.屬性繫結"會講解)
在QML中,屬性就更加常見了,比如Rectangle的color屬性,其實本質就是:
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) public: QColor color() const { return m_color; } void setColor(const QColor &color) { if (color == m_color) return; m_color = color; emit colorChanged(); } signals: void xChanged(const QString &name); private: QColor m_color;
假如在c++類中自己更改屬性時,並且該屬性設定了NOTIFY關鍵字,那麼必須更改後,主動emit來觸發屬性更改訊號,比如:
m_color = QColor::QColor(255, 0, 0, 255); emit colorChanged();
3.2 piechart.cpp最終如下所示:
#include "piechart.h" PieChart::PieChart(QQuickItem *parent) : QQuickPaintedItem(parent) { } void PieChart::paint(QPainter *painter) { QPen pen(m_color, 2); painter->setPen(pen); painter->setRenderHints(QPainter::Antialiasing, true); painter->drawPie(boundingRect().adjusted(1, 1, -1, -1), 90 * 16, 290 * 16); }
4.在main.cpp中通過qmlRegisterXXX註冊C++類到QML中
我們已經建立好了C++類,剩下的就是註冊到QML中即可大功告成了.註冊函式是qmlRegisterType(),當然也可以通過qmlRegisterSingletonType()註冊單例類(後面章節介紹).
qmlRegisterType函式模版宣告如下:
template<typename T> int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName); // uri: 類似於java包名,比如"import QtQuick 2.12","QtQuick"就是包名,而2.12是versionMajor和versionMinor拼接的版本號 // qmlName: 包名中的型別名稱,比如Rectangle就是QtQuick包名中的其中一個型別名稱
main函式如下所示:
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "piechart.h" int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); qmlRegisterType<PieChart>("Charts", 1, 0, "PieChart"); // 註冊C++類 QQmlApplicationEngine engine; const QUrl url(QStringLiteral("qrc:/main.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); }
執行效果如下所示:
5.屬性繫結
屬性繫結是QML的一個強大功能,它允許自動同步不同型別的值。當屬性值更改時,它使用訊號通知和更新其他型別的值。
我們來建立兩個PieChart圖,名稱分別為chartA和chartB,然後我們在chartB裡進行color屬性繫結"color: chartA.color".
修改main.qml:
import QtQuick 2.14 import QtQuick.Window 2.12 import Charts 1.0 Window { visible: true width: 640 height: 480 Row { PieChart { id: chartA width: 100; height: 100 name: "A simple pie chart" color: "red" } PieChart { id: chartB width: 100; height: 100 name: "A simple pie chart" color: chartA.color } } MouseArea { anchors.fill: parent onClicked: { chartA.color = "blue" } } }
修改piechart.h:
#include <QtQuick/QQuickPaintedItem> #include <QColor> #include <QPen> #include <QPainter> #include <QDebug> #include <QTimer> class PieChart : public QQuickPaintedItem { Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) public: PieChart(QQuickItem *parent = 0); QString name() const { return m_name; } void setName(const QString &name) { if (name != m_name) { m_name = name; emit nameChanged(name); } } QColor color() const { return m_color; } void setColor(const QColor &color) { if (color != m_color) { m_color = color; emit colorChanged(color); update(); } } void paint(QPainter *painter); private: QString m_name; QColor m_color; signals: void nameChanged(const QString &name); void colorChanged(const QColor &color); public slots: void onTimeout(); };
這裡我們在Q_PROPERTY()中新增了一個NOTIFY功能:
- 屬性繫結主要是靠的是屬性中的NOTIFY功能,每當color值更改時,就會發出colorChanged訊號。從而使得繫結的目標屬性自動更新值.
- 呼叫WRITE功能的函式時(比如setColor()),我們必須判斷要設定的值是否與當前屬性值相等,這樣確保訊號不會必要地發出,從而導致可能死迴圈的事情發生.
當我們點選應用後,由於立即設定chartA.color = "blue",然後由於屬性繫結,所以chartB也跟著改變了.最終兩個chart都變成了藍色:
6.使用Q_INVOKABLE修飾函式提供給QML使用
比如在QML中,我們想使用C++類的clearChart()函式來清除繪圖時,那我們需要在C++類的clearChart()函式前面使用Q_INVOKABLE修飾來註冊到元物件中(本質就是signal和slots).這樣QML就可以像使用function那樣呼叫該函式了.
修改類標頭檔案,新增clearChart():
class PieChart : public QQuickPaintedItem { ... public: ... Q_INVOKABLE void clearChart(); };
在cpp檔案中實現函式:
void PieChart::clearChart() { setColor(QColor(Qt::transparent)); update(); }
修改main.qml:
Window { visible: true width: 640 height: 480 Row { PieChart { id: chartA width: 100; height: 100 name: "A simple pie chart" color: "red" } PieChart { id: chartB width: 100; height: 100 name: "A simple pie chart" color: "blue" } } MouseArea { anchors.fill: parent onClicked: { chartA.clearChart();} } }
當我們點選應用後,就會呼叫chartA.clearChart()方法,從而清除chartA:
7.訊號、槽函式提供給QML使用
上一節我們講過使用Q_INVOKABLE修飾來註冊到元物件中其實本質就是signal和slots.這是因為:
所以不管用Q_INVOKABLE,還是signal和slots修飾,最終都會變成QT_ANNOTATE_FUNCTION(...)
修改piechart.h,新增一個clearChart2槽函式:
class PieChart : public QQuickPaintedItem { ... public slots: void clearChart2() { setColor(QColor(Qt::transparent)); update(); } ... };
修改main.qml:
MouseArea {
anchors.fill: parent
onClicked: { chartA.clearChart2();}
}
發現,最終效果和呼叫chartA.clearChart()的效果一樣.
8.使用Q_ENUM()將列舉提供給QML使用
在C++類中,我們可以通過Q_ENUM()將C++類的列舉型別註冊到元物件中,修改標頭檔案如下所示:
class PieChart : public QQuickPaintedItem { ... public : enum Priority { High, Low, VeryHigh, VeryLow }; Q_ENUM(Priority) Q_INVOKABLE Priority setPriority(Priority value) { qDebug()<<value; } ... };
在main.qml中新增:
Component.onCompleted: {
chartA.setPriority(chartA.VeryHigh);
}
執行效果如下所示:
需要注意的是: 在qml中,我們只能使用這些列舉,假如是列印這些列舉變數,值將會是0.
未完待續,下章學習如何向QML中註冊單例類