簡 述: 講解元對象系統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】