QT開發(六十九)——QML與C++混合編程

QT開發(六十九)——QMLC++混合編程

1、QMLC++混合編程簡介

    QML與C++混合編程就是使用QML高效便捷地構建UI,而C++則用來實現業務邏輯和複雜算法。算法

2、QML訪問C++

    Qt集成了QML引擎和Qt元對象系統,使得QML很容易從C++中獲得擴展,在必定的條件下,QML就能夠訪問QObject派生類的成員,例如信號、槽函數、枚舉類型、屬性、成員函數等。編程

    QML訪問C++有兩個方法:一是在Qt元對象系統中註冊C++類,在QML中實例化、訪問二是在C++中實例化並設置爲QML上下文屬性,在QML中直接使用。第一種方法可使C++類在QML中做爲一個數據類型,例如函數參數類型或屬性類型,也可使用其枚舉類型、單例等,功能更強大。app

3、C++類的實現

    C++類要想被QML訪問,首先必須知足兩個條件:一是派生自QObject類或QObject類的子類,二是使用Q_OBJECT宏。QObject類是全部Qt對象的基類,做爲Qt對象模型的核心,提供了信號與槽機制等不少重要特性。Q_OBJECT宏必須在private區(C++默認爲private)聲明,用來聲明信號與槽,使用Qt元對象系統提供的內容,位置通常在語句塊首行。Projects選擇Qt Quick Application,工程名爲Hello。ide

一、信號與槽實現

A、C++類實現函數

#ifndef HELLO_H
#define HELLO_H
#include <QObject>
#include <QDebug>
 
class Hello: public QObject
{
Q_OBJECT
public slots:
  void doSomething()
  {
    qDebug() << "Hello::dosomething() is called.";
  }
signals:
  void begin();
};
 
#endif // HELLO_H


    Hello類中的信號begin()和槽doSomething()均可以被QML訪問。槽必須聲明爲public或protected,信號在C++中使用時要用到emit關鍵字,但在QML中就是個普通的函數,用法同函數同樣,信號處理器形式爲on,Signal首字母大寫。信號不支持重載,多個信號的名字相同而參數不一樣時,可以被識別的只是最後一個信號,與信號的參數無關。ui

B、註冊C++類型spa

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "hello.h"
 
int main(int argc, char *argv[])
{
  QGuiApplication app(argc, argv);
  //註冊C++類型Hello
  qmlRegisterType<Hello>("Hello.module",1,0,"Hello");
 
  QQmlApplicationEngine engine;
  engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
 
  return app.exec();
}


C++類註冊到Qt元對象系統。指針

C、在QML文件中導入C++類並使用component

import QtQuick 2.5
import QtQuick.Window 2.2
//導入註冊的C++類
import Hello.module 1.0
 
Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello QML")
    MouseArea {
        anchors.fill: parent
        onClicked: {
            hello.begin();//單擊鼠標調用begin信號函數
        }
    }
    Hello{
        id:hello   //Hello類的實例
        onBegin:doSomething()
    }
}


    在QML文件中導入註冊的C++import),關鍵字Hello就能夠在當前QML文件中看成一種QML類型來用。MouseArea鋪滿界面,單擊鼠標時會發送begin()信號,進而調用doSomething()槽函數。對象

二、枚舉類型實現

A、C++類中枚舉的定義

#ifndef HELLO_H
#define HELLO_H
#include <QObject>
#include <QDebug>
 
class Hello: public QObject
{
Q_OBJECT
Q_ENUMS(Color)
public:
  Hello():m_color(RED)
  {
    qDebug() << "Hello() is called.";
  }
  //枚舉
  enum Color
  {
    RED,
    BLUE,
    BLACK
  };
public slots:
  void doSomething(Color color)
  {
    qDebug() << "Hello::dosomething() is called " << color;
  }
 
signals:
  void begin();
private:
  Color m_color;
};
 
#endif // HELLO_H


    C++類中添加了public的Color枚舉類型,枚舉類型要想在QML中使用,須要使用Q_ENUMS()宏。

B、QML文件中使用C++枚舉類型

import QtQuick 2.5
import QtQuick.Window 2.2
//導入註冊的C++類
import Hello.module 1.0
 
Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello QML")
    MouseArea {
        anchors.fill: parent
        onClicked: {
            hello.begin();//單擊鼠標調用begin信號函數
        }
    }
    Hello{
        id:hello   //Hello類的實例
        onBegin:doSomething(Hello.RED)
    }
}

    QML中使用枚舉類型的方式是經過C++類型名使用.」操做符直接訪問枚舉成員Hello.RED。

三、成員函數實現

A、成員函數定義

#ifndef HELLO_H
#define HELLO_H
#include <QObject>
#include <QDebug>
 
class Hello: public QObject
{
Q_OBJECT
Q_ENUMS(Color)
public:
  Hello():m_color(RED)
  {
    qDebug() << "Hello() is called.";
  }
  //枚舉
  enum Color
  {
    RED,
    BLUE,
    BLACK
  };
  Q_INVOKABLE void show()
  {
    qDebug() << "show() is called.";
  }
public slots:
  void doSomething(Color color)
  {
    qDebug() << "Hello::dosomething() is called " << color;
  }
 
signals:
  void begin();
private:
  Color m_color;
};
 
#endif // HELLO_H


    若是QML中訪問C++成員函數,則C++成員函數必須是public或protected成員函數,且使用Q_INVOKABLE宏,位置在函數返回類型的前面。

B、QML中調用C++類成員函數

import QtQuick 2.5
import QtQuick.Window 2.2
//導入註冊的C++類
import Hello.module 1.0
 
Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello QML")
    MouseArea {
        anchors.fill: parent
        onClicked: {
            hello.begin();//單擊鼠標調用begin信號函數
            hello.show();
        }
    }
    Hello{
        id:hello   //Hello類的實例
        onBegin:doSomething(Hello.RED)
    }
}


    在QML中訪問C++的成員函數的形式是.,如hello.show()支持函數重載。

四、C++類的屬性

A、C++類中屬性的定義

#ifndef HELLO_H
#define HELLO_H
#include <QObject>
#include <QDebug>
 
class Hello: public QObject
{
Q_OBJECT
Q_ENUMS(Color)
//屬性聲明
Q_PROPERTY(Color color READ color WRITE setColor NOTIFY colorChanged)
public:
  Hello():m_color(RED)
  {
    qDebug() << "Hello() is called.";
  }
  //枚舉
  enum Color
  {
    RED,
    BLUE,
    BLACK
  };
  Q_INVOKABLE void show()
  {
    qDebug() << "show() is called.";
  }
  Color color() const
  {
    return m_color;
  }
  void setColor(const Color& color)
  {
    if(color != m_color)
    {
        m_color = color;
        emit colorChanged();
    }
  }
public slots:
  void doSomething(Color color)
  {
    qDebug() << "Hello::dosomething() is called " << color;
  }
 
signals:
  void begin();
  void colorChanged();
private:
  Color m_color;//屬性
};
 
#endif // HELLO_H


    C++類中添加了Q_PROPERTY()宏,用來在QObject派生類中聲明屬性,屬性同類的數據成員同樣,但又有一些額外的特性可經過Qt元對象系統來訪問。

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])

    屬性的type、name是必需的,其它是可選項,經常使用的有READ、WRITE、NOTIFY。屬性的type能夠是QVariant支持的任何類型,也能夠是自定義類型,包括自定義類、列表類型、組屬性等。另外,屬性的READ、WRITE、RESET是能夠被繼承的,也能夠是虛函數,不經常使用。

    READ:讀取屬性值,若是沒有設置MEMBER的話,是必需的。通常狀況下,函數是個const函數,返回值類型必須是屬性自己的類型或這個類型的const引用,沒有參數。

    WRITE:設置屬性值,可選項。函數必須返回void,有且僅有一個參數,參數類型必須是屬性自己的類型或類型的指針或引用。

    NOTIFY:與屬性關聯的可選信號信號必須在類中聲明過,當屬性值改變時,就可觸發信號,能夠沒有參數,有參數的話只能是一個類型同屬性自己類型的參數,用來記錄屬性改變後的值。

B、QML中修改屬性

import QtQuick 2.5
import QtQuick.Window 2.2
//導入註冊的C++類
import Hello.module 1.0
 
Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello QML")
    MouseArea {
        anchors.fill: parent
        onClicked: {
            hello.begin()//單擊鼠標調用begin信號函數
            hello.show()
            hello.color = 2  //修改屬性
        }
    }
    Hello{
        id:hello   //Hello類的實例
        onBegin:doSomething(Hello.RED)
        onColorChanged:console.log("color changed.")
    }
}

    C++類中的m_color屬性能夠在QML中訪問、修改,訪問時調用了color()函數,修改時調用setColor()函數,同時還發送了一個信號來自動更新color屬性值

4、註冊C++類爲QML類型

    QObject派生類能夠註冊到Qt元對象系統,使得類在QML中同其它內建類型同樣,能夠做爲一個數據類型來使用。QML引擎容許註冊可實例化的類型,也能夠是不可實例化的類型,常見的註冊函數有:

qmlRegisterInterface()

qmlRegisterRevision()

qmlRegisterSingletonType()

qmlRegisterType()

qmlRegisterTypeNotAvailable()

qmlRegisterUncreatableType()

 

template<typename T>

int qmlRegisterType(const char *uri,int versionMajor,

       int versionMinor, const char *qmlName);

    模板函數註冊C++類到Qt元對象系統中,uri是須要導入到QML中的庫名,versionMajor和versionMinor是其版本數字,qmlName是在QML中可使用的類型名。

    qmlRegisterType<Hello>("Hello.module",1,0,"Hello");

    main.cpp中將Hello類註冊爲在QML中可使用的GHello類型,主版本爲1,次版本爲0,庫的名字是Hello.modulemain.qml中導入了C++庫,使用Hello構造了一個對象,id爲hello,能夠藉助id來訪問C++。

    註冊動做必須在QML上下文建立前,不然無效。

    QQuickView爲QtQuickUI提供了一個窗口,能夠方便地加載QML文件並顯示其界面。QApplication派生自QGuiApplication,而QGuiApplication又派生自QCoreApplication,這三個類是常見的管理Qt應用程序的類。QQmlApplicationEngine能夠方便地從一個單一的QML文件中加載應用程序,派生自QQmlEngine,QQmlEngine則提供了加載QML組件的環境,能夠與QQmlComponent、QQmlContext等一塊兒使用。

5、QML上下文屬性設置

    在C++應用程序加載QML對象時,能夠直接嵌入一些C++數據來給QML使用,須要用到QQmlContext::setContextProperty()設置QML上下屬性,上下文屬性能夠是一個簡單的類型,也能夠是任何自定義的類對象。

A、C++設置上下文屬性

#include <QGuiApplication>
#include <QQuickView>
#include <QQmlContext>
#include "hello.h"
 
int main(int argc, char *argv[])
{
  QGuiApplication app(argc, argv);
  
  QQuickView view;
  Hello hello;
  view.rootContext()->setContextProperty("hello", &hello);
  view.setSource(QUrl(QStringLiteral("qrc:///main.qml")));
  view.show();
 
  return app.exec();
}


    Hello類先實例化爲hello對象,而後註冊爲QML上下文屬性。

B、QML使用

import QtQuick 2.5
 
Item {
    width: 640
    height: 480
    MouseArea {
        anchors.fill: parent
        onClicked: {
            hello.begin()//單擊鼠標調用begin信號函數
            hello.show()
        }
    }
    Connections{
       target:hello
       onBegin:console.log("hello")
    }
}


    在main.qml中不能使用Hello類型來實例化,也不能調用doSomething()槽函數,由於doSomething()函數中的枚舉類型在QML中是訪問不到的,正確的用法是經過已經設置的QML上下文屬性「hello」來訪問C++,能夠訪問信號begin()和成員函數show(),此時的信號處理器須要用Connections來處理

6、C++訪問QML

    在C++中也能夠訪問QML中的屬性、函數和信號。

    在C++中加載QML文件能夠用QQmlComponent或QQuickView,而後就能夠在C++中訪問QML對象。QQuickView提供了一個顯示用戶界面的窗口,而QQmlComponent沒有。

一、C++使用QQmlComponent

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "hello.h"
 
int main(int argc, char *argv[])
{
  QGuiApplication app(argc, argv);
  //註冊C++類型Hello
  qmlRegisterType<Hello>("Hello.module",1,0,"Hello");
 
  QQmlApplicationEngine engine;
  QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml")));
  component.create();
 
  return app.exec();
}


二、C++使用QML的屬性

    在C++中加載了QML文件並進行組件實例化後,就能夠在C++中訪問、修改實例的屬性值,能夠是QML內建屬性,也能夠是自定義屬性。

A、QML中定義部分元素的屬性

import QtQuick 2.5
import QtQuick.Window 2.0
import Hello.module 1.0
 
Window {
    visible: true
    width: 640
    height: 480
    title: "Hello QML"
    color: "white"
    MouseArea {
        anchors.fill: parent
        onClicked: {
            hello.begin()//單擊鼠標調用begin信號函數
            hello.doSomething(2)
        }
    }
    Rectangle{
        objectName: "rect"
        anchors.fill: parent
        color:"red"
        }
    Hello{
       id:hello
       onBegin:console.log("hello")
       onColorChanged:hello.show()
    }
}

    QML中添加了一個Rectangle,設置objectName屬性值爲「rect」,objectName是爲了在C++中可以找到Rectangle。

B、C++中使用QML元素的屬性

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "hello.h"
 
int main(int argc, char *argv[])
{
  QGuiApplication app(argc, argv);
  //註冊C++類型Hello
  qmlRegisterType<Hello>("Hello.module",1,0,"Hello");
 
  QQmlApplicationEngine engine;
  QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml")));
  QObject* object = component.create();
  qDebug() << "width value is" << object->property("width").toInt();
  object->setProperty("width", 500);//設置window的寬
  qDebug() << "width value is" << object->property("width").toInt();
  qDebug() << "height value is" << QQmlProperty::read(object, "height").toInt();
  QQmlProperty::write(object, "height", 300);//設置window的高
  qDebug() << "height value is" << QQmlProperty::read(object, "height").toInt();
  QObject* rect = object->findChild<QObject*>("rect");//查找名稱爲「rect」的元素
  if(rect)
  {
      rect->setProperty("color", "blue");//設置元素的color屬性值
      qDebug() << "color is " << object->property("color").toString();
  }
 
  return app.exec();
}


    QObject::property()/setProperty()來讀取、修改width屬性值。

    QQmlProperty::read()/write()來讀取、修改height屬性值。

    若是某個對象的類型是QQuickItem,例如QQuickView::rootObject()的返回值,可使用QQuickItem::width/setWidth()來訪問、修改width屬性值。

    QML組件是一個複雜的樹型結構,包含兄弟組件和孩子組件,可使用QObject::findchild()/findchildren()來查找

三、C++使用QML中信號與函數

    在C++中,使用QMetaObject::invokeMethod()能夠調用QML中的函數,從QML傳遞過來的函數參數和返回值會被轉換爲C++中的QVariant類型,成功返回true,參數不正確或被調用函數名錯誤返回false,invokeMethod()共有四個重載函數。必須使用Q_ARG()宏來聲明函數參數,用Q_RETURN_ARG()宏來聲明函數返回值,其原型以下:

QGenericArgument           Q_ARG(Type, const Type & value)

QGenericReturnArgument     Q_RETURN_ARG(Type, Type & value)

    使用QObject::connect()能夠鏈接QML中的信號,connect()共有四個重載函數,都是靜態函數。必須使用SIGNAL()宏來聲明信號,SLOT()宏聲明槽函數。

    使用QObject::disconnect()能夠解除信號與槽函數的鏈接。

A、QML中定義信號與函數

import QtQuick 2.5
import QtQuick.Window 2.0
import Hello.module 1.0
 
Window {
    visible: true
    width: 640
    height: 480
    title: "Hello QML"
    color: "white"
    //定義信號
    signal qmlSignal(string message)
    //定義函數
    function qmlFunction(parameter) {
        console.log("qml function parameter is", parameter)
        return "function from qml"
    }
    MouseArea {
        anchors.fill: parent
        onClicked: {
            hello.begin()//單擊鼠標調用begin信號函數
            hello.doSomething(2)
            qmlSignal("This is an qml signal.")//發送信號
        }
    }
    Rectangle{
        objectName: "rect"
        anchors.fill: parent
        color:"red"
        }
    Hello{
       id:hello
       onBegin:console.log("hello")
       onColorChanged:hello.show()
    }
}


    QML中添加了qmlSignal()信號和qmlFunction()函數,信號在QML中發送,函數在C++中調用。

B、C++中調用

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "hello.h"
 
int main(int argc, char *argv[])
{
  QGuiApplication app(argc, argv);
  //註冊C++類型Hello
  qmlRegisterType<Hello>("Hello.module",1,0,"Hello");
 
  QQmlApplicationEngine engine;
  QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml")));
  QObject* object = component.create();
  qDebug() << "width value is" << object->property("width").toInt();
  object->setProperty("width", 500);//設置window的寬
  qDebug() << "width value is" << object->property("width").toInt();
  qDebug() << "height value is" << QQmlProperty::read(object, "height").toInt();
  QQmlProperty::write(object, "height", 300);//設置window的高
  qDebug() << "height value is" << QQmlProperty::read(object, "height").toInt();
  QObject* rect = object->findChild<QObject*>("rect");//查找名稱爲「rect」的元素
  if(rect)
  {
      rect->setProperty("color", "blue");//設置元素的color屬性值
      qDebug() << "color is " << object->property("color").toString();
  }
  //調用QML中函數
  QVariant returnedValue;
  QVariant message = "Hello from C++";
  QMetaObject::invokeMethod(object, "qmlFunction",
                            Q_RETURN_ARG(QVariant, returnedValue),
                            Q_ARG(QVariant, message));
  qDebug() << "returnedValue is" << returnedValue.toString(); // function from qml
  Hello hello;
  //鏈接QML元素中的信號到C++槽函數
  QObject::connect(object, SIGNAL(qmlSignal(QString)),
                   &hello, SLOT(qmlSlot(QString)));
 
  return app.exec();
}


C++類中的槽函數:

public slots:
  void doSomething(Color color)
  {
    qDebug() << "Hello::dosomething() is called " << color;
  }
  void qmlSlot(const QString& message)
  {
    qDebug() << "C++ called: " << message;
  }


7、QMLC++混合編程注意事項

    QML與混合編程注意事項:

    A、自定義C++類必定要派生自QObject類或其子類,並使用Q_OBJECT宏。

    B、註冊自定義C++類到Qt元對象系統或設置自定義類對象實例爲QML上下文屬性是必須的。

    C、二者交互進行數據傳遞時,要符合QML與C++間數據類型的轉換規則。

相關文章
相關標籤/搜索