29.qt quick-在QML中呼叫C++類

諾謙發表於2021-06-18
  • 初學者小建議: 不妨先點贊後再學習, 進群免費拿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,還是signalslots修飾,最終都會變成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中註冊單例類

 

相關文章