元對象系統moc(Meat-Object System)的對象MetaObject和(含動態)屬性Propert的用法



簡 述: 講解元對象系統moc(Meat-Object System)的對象MetaObject和(含動態)屬性Propert的用法。沒想到這一篇會延期如此之久之久。 (此篇有大部分是借鑑書籍和或互聯網),因爲作者寫的很棒,故大篇幅的直接借鑑過來了 。其中文中有些少部分是自己照着修改了一點,稍加改寫而成的。


編程環境:

💻: uos20 amd64 📎 Qt 5.11.3

💻: MacOS 10.14.6 📎 Qt 5.12.6

💻: win10 x64 📎 Qt 5.12.7


元對象系統:

​ Qt 的元對象系統 (Meta-Object System) 提供了對象之間通信的信號與槽機制、運行時類型信息和動態屬性系統。

元對象系統由以下三個基礎組成:

  • QObject類是所有使用元對象系統的類的基類。

  • 在一個類的private部分聲明 Q OBJECT 宏,使得類可以使用元對象的特性,如動態屬性、信號與槽。

  • MOC (元對象編譯器)爲每個 QObjeet 的子類提供必要的代碼來實現元對象系統的特性。構建項目時,MOC工具讀取 C++ 源文件,當它發現類的定義裏有 Q_ OBJECT 宏時,它就會爲這個類生成另外一個包含有元對象支持代碼的 C++ 源文件,這個生成的源文件連同類的實現文件一起被編譯和連接。

  • 除了信號與槽機制外,元對象還提供如下一些功能。

  • Qbjet::metaOject()函數返回類關聯的元對象,元對象類 QMetaObject 包含了訪問元對象的一些接口口函數,例如 QMetabjet:classNamec() 函數可在運行時返回類的名稱字符串。

    QObject *obj = new QPushButton;
    obj->metaObject ()->classNane();  //返回"QPushButton
    
  • QMetaOjct::newInstance()函數創建類的一個新的實例。

  • Q0bjct:inherits(const char *className)函數判斷一個對象實例是否是名稱爲 className 的類或 QObject 的子類的實例。例如:

    QTimer *timer = new QTimer;  // OTimer是oobject的子類
    timer->inherits ("QTimer");  //返回true
    timer->inherits ("QObject");  //返回true
    timer->inherits ("QAbstractButton");//返回false. 不是QAbatractButton的子類
    
  • QObject::tr()Qbjet::trUtf8() 函數可翻譯字符串,用於多語言界面設計。

  • QObjct:setProperty()Q0bjct:property() 函數用於通過屬性名稱動態設置和獲取屬性值。

    對於 QObject 及其子類,還可以使用 qobject_cast() 函數進行動態投射(dynamic cast)。例如,假設 QMyWidget 是 QWidget 的子類並且在類定義中聲明瞭Q_OBJECT宏。創建實例使用下面的語句:

    Q0bject *obj = new QMyWidget;
    

    變量 obj 定義爲 QObject 指針,但它實際指向 QMyWidget 類,所以可以正確投射爲 QWidget,即:

    Qwidget *widget = qobject_cast<Qwidget *>(obj);
    

    從 QObject 到 QWidget 的投射是成功的,因爲 obj 實際是 QMyWidget 類,是 QWidge 的子類。也可以將其成功投射爲 QMyWidget,即:

    QMyWidget *myWidget = qobject_cast<QMyWidget *>(obj);
    

    投射爲 QMyWidget是成功的,因爲 qoiect_cast() 並不區分 Qt 內建的類型和用戶自定義類型。但是,若要將 obj 投射爲 QLabel 則是失敗的,因爲 QMyWidget 不是 Qlabel 的子類。即:

    QLabe1 *labol = qobject_caot<QLabe1 *>(obj);  
    

屬性系統:

屬性定義:

Qt提供一個Q PROPERTY0宏可以定義屬性,它也是基於元對象系統實現的。Qt 的屬性系統與C++編譯器無管,可以用任何柝準的C++編譯器定義屬性的Qt C++程序。

  • 在QObijct的子奬中,用宏Q PROPERTYO定文屬性,其使用格式如下:

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

    Q_PROPERTY宏定義屬性的一些主要關鍵字的意義如下。

    ● READ 指定一個讀取屬性值的函數,沒有 MEMBER 關鍵字時必須設置READ.

    ● WRITE指定一個設定屬性值的函數, 只讀屬性沒有WRITE設置。

    ● MEMBER指定一個成員變量與屬性關聯,成爲可讀可寫的屬性,無需再設置READ和WRITE.

    ● RESET是可選的,用於指定一個設置屬性缺省值的函數。

    ● NOTIFY是可選的,用於設置一個信號, 當屬性值變化時發射此信號.

    ● CONSTANT表示屬性値是一常數,対於一個対象實例,READ 指定的函數返回値是常數,但是每個實例的返回值可以不一-樣。具有CONSTANT關鍵字的屬性不能有WRITE和NOTIFY關鍵字。

    FINAL表示所定文的屬性不能被子美重栽。QWidget類定義屬性的一-些例子如下:

    Q_ PROPERTY (bool  focus READ hasFocus)
    Q_ PROPERTY(b0ol enabled READ isEnabled WRITE setEnabled)
    Q_ PROPERTY (QCursor cursor READ cursor WRITE setCursor RESET unsetCursor()
    

屬性的使用:

不管是否用READ和WRITE定義了接口函數,只要知道屬性名稱就可以通過QObjct:property()讀取屬性值,並通過QObject:setProperty0設置屬性值。例如:

QPushButton *button = new QPushButton;Q0bject *object = button;
object->setProperty("flat", true);
bool isFlat- object->property("flat")

動態屬性:

QObject:setPropert()函數可以在運行時爲類定義一個新的屬性,稱之爲動態屬性。動態屬性是針對類的實例定義的。動態屬性可以使用Qbjct:property()查詢,就如在類定義裏用 Q_PROPERTY 宏定義的屬性一樣。

例如,在數據表編輯界面上,一些字段是必填字段,就可以在初始化界面時爲這些字段的關聯顯示組件定義一個新的required屬性,並設置值爲"true",如:

editName->setProperty("required""true");
comboSex-> setProperty("required". "true"); 
checkAgree-> setProperty("required", "true");

然後,可以應用下面的樣式定義將這種必填字段的背景顏色設置爲亮綠色

*[required="true"] (background-color: lime)


類的附加信息:

屬性系統還有一個宏Q CLASSINFO0.可以爲類的元對象定義“名稱-值” 信息,如:

class QMyC1ass : public QObject
{ 
  Q_OBJECT
  Q_CLASSINFO("author", "Wang" )
  Q_CLASSINFO("company", "UPC" )
  Q_CLASSINFO("version ""3.0.1")
  public:
  ...
}

用Q CLASSINFOQ宏定義附加類信息後,可以通過元對象的一些函數獲取類的附加信息,如classInfo(int )獲取某個附加信息,函數原型定義如下: .

  QMetaClassInfo QMetaObject: :classInfo(int index) const

返回值是 QMetaClassInfo 類型,有name()和value()兩個函數,可獲得類附加信息的名稱和值。


核心源碼:

寫了一個例子:

ExPerson.h

#ifndef EXPERSON_H
#define EXPERSON_H

#include <QObject>

class ExPerson : public QObject
{
    Q_OBJECT

    //類的附加信息:名稱————值
    Q_CLASSINFO("author", "touwoyimuli")
    Q_CLASSINFO("version", "1.0.0")
    Q_CLASSINFO("info", "Qt5 Meta Object and Property Example")

    //屬性定義
    Q_PROPERTY(int age READ getAge WRITE setAge NOTIFY ageChanged)    //屬性age; 方法getAge()和setAge()對其讀寫; 設置信號ageChanged()
    Q_PROPERTY(QString name MEMBER m_name)     //屬性name 與類成員變量m_name關聯
    Q_PROPERTY(int score MEMBER m_score)       //屬性score與類成員變量m_score關聯

public:
    explicit ExPerson(QString name, QObject *parent = nullptr);

public:
    int getAge();                //屬性 READ 函數
    void setAge(int value);      //屬性 WRITE 函數

    void incAge();               //單獨寫一個接口,與屬性無關

signals:
    void ageChanged(int value);  //屬性age發生改變的信號函數

private:
    int m_age = 5;
    QString m_name;
    int m_score = 50;

};

#endif // EXPERSON_H

ExWidget.h

#ifndef EXWIDGET_H
#define EXWIDGET_H

#include <QWidget>
#include "ExPerson.h"

namespace Ui {
class ExWidget;
}

class ExWidget : public QWidget
{
    Q_OBJECT

public:
    explicit ExWidget(QWidget *parent = nullptr);
    ~ExWidget();

private slots:
    void onAgeChange(int val);       //自定義的槽函數
    void onSpinValChange(int val);

    void onBtnClear();               //UI界面的槽函數
    void onBtnBoyInc();
    void onBtnGrilInc();
    void onClassInfo();

private:
    Ui::ExWidget *ui;

    ExPerson* m_boy;
    ExPerson* m_girl;
};

#endif // EXWIDGET_H

ExPerson.cpp

/*
  * Copyright 2019 [email protected]
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
*/
#include "ExPerson.h"

//加一個參後的構造函數
ExPerson::ExPerson(QString name, QObject *parent) : QObject(parent)
{
    m_name = name;
}

int ExPerson::getAge()
{
    return m_age;
}

void ExPerson::setAge(int value)
{
    m_age = value;
    emit ageChanged(m_age);  //發射信號
}

void ExPerson::incAge()
{
    m_age++;
    emit ageChanged(m_age);  //發射信號
}

ExWidget.cpp

#include "ExWidget.h"
#include "ui_ExWidget.h"
#include <QMetaProperty>
#include <QDebug>

ExWidget::ExWidget(QWidget *parent) : QWidget(parent), ui(new Ui::ExWidget)
{
    ui->setupUi(this);

    m_boy = new ExPerson("張三");
    m_boy->setProperty("score", 90);
    m_boy->setProperty("age", 20);
    m_boy->setProperty("sex", "Boy");           //動態屬性
    connect(m_boy, &ExPerson::ageChanged, this, &ExWidget::onAgeChange);

    m_girl = new ExPerson("張麗");
    m_girl->setProperty("score", 80);
    m_girl->setProperty("age", 10);
    m_girl->setProperty("sex", "Gril");         //動態屬性
    connect(m_girl, &ExPerson::ageChanged, this, &ExWidget::onAgeChange);

    ui->spinBoy->setProperty("isBoy", true);    //動態屬性
    ui->spinGril->setProperty("isBoy", false);

    connect(ui->spinGril, SIGNAL(valueChanged(int)), this, SLOT(onSpinValChange(int)));
    connect(ui->spinBoy,  SIGNAL(valueChanged(int)), this, SLOT(onSpinValChange(int)));

    connect(ui->btnBoyAdd, SIGNAL(clicked()), this, SLOT(onBtnBoyInc()));
    connect(ui->btnGrilAdd, SIGNAL(clicked()), this, SLOT(onBtnGrilInc()));
    connect(ui->btnMetaObject, SIGNAL(clicked()), this, SLOT(onClassInfo()));
    connect(ui->btnClean, SIGNAL(clicked()), this, SLOT(onBtnClear()));

    setWindowTitle(QObject::tr("元對象MetaObject和(含動態)屬性Propert的用法"));
}

ExWidget::~ExWidget()
{
    delete ui;
}

void ExWidget::onAgeChange(int val)
{
    Q_UNUSED(val)   //參數val沒使用,避免警告

    ExPerson* person = qobject_cast<ExPerson *>(sender());    //類型投射
    QString name = person->property("name").toString();
    QString sex = person->property("sex").toString();
    int age  = person->getAge();                             //通過接口函數,獲得年齡
                                                             //或使用 int age  = person->property("age").toInt();
    ui->textEdit->appendPlainText(name+","+sex + QString::asprintf(",年齡=%d",age));
}

void ExWidget::onSpinValChange(int val)
{
    Q_UNUSED(val)

    QSpinBox* spin = qobject_cast<QSpinBox *>(sender());     //類型投射
    if (spin->property("isBoy").toBool())
        m_boy->setAge(ui->spinBoy->value());
    else
        m_girl->setAge(ui->spinGril->value());
}

void ExWidget::onBtnClear()
{
    ui->textEdit->clear();
}

void ExWidget::onBtnBoyInc()
{
    m_boy->incAge();
}

void ExWidget::onBtnGrilInc()
{
    m_girl->incAge();
}

void ExWidget::onClassInfo()
{
    const QMetaObject* meta = m_boy->metaObject();

    ui->textEdit->clear();
    ui->textEdit->appendPlainText("==元對象信息(Meta Object)===");
    ui->textEdit->appendPlainText(QString("類名稱: %1\n").arg(meta->className()));
    ui->textEdit->appendPlainText("屬性(property)");

    for (int i = meta->propertyOffset(); i < meta->propertyCount(); i++)
    {
        QMetaProperty prop = meta->property(i);
        const char* propName = prop.name();
        QString propValue = m_boy->property(propName).toString();
        ui->textEdit->appendPlainText(QString("屬性名稱=%1, 屬性值= %2").arg(propName).arg(propValue));
    }

    ui->textEdit->appendPlainText("");
    ui->textEdit->appendPlainText("classInfo:");
    for (int i = meta->classInfoOffset(); i < meta->classInfoCount(); i++)
    {
        QMetaClassInfo classInfo = meta->classInfo(i);
        ui->textEdit->appendPlainText(QString("Name=%1, Value= %2").arg(classInfo.name()).arg(classInfo.value()));
    }

}

運行效果:

附上的最後的運行效果圖一覽:


下載地址:

https://github.com/xmuli/QtExamples 【QtMeatObjectEx】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章