29.qt quick-在QML中調用C++類

  • 初學者小建議: 不妨先點贊後再學習, 進群免費拿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對象,像「640×480」這樣的字符串可以自動轉換為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中註冊單例類