您好,登錄后才能下訂單哦!
基于Qt4.8.6版本
Qt 的信號槽和屬性系統(tǒng)基于在運(yùn)行時(shí)進(jìn)行內(nèi)省的能力,所謂內(nèi)省是指面向?qū)ο笳Z言的一種在運(yùn)行期間查詢對象信息的能力, 比如如果語言具有運(yùn)行期間檢查對象型別的能力,那么是型別內(nèi)?。╰ype intropection)的,型別內(nèi)省可以用來實(shí)施多態(tài)。
C++的內(nèi)省比較有限,僅支持型別內(nèi)省, C++的型別內(nèi)省是通過運(yùn)行時(shí)類型識別(RTTI)(Run-Time Type Information)中的typeid 以及 dynamic_cast關(guān)鍵字來實(shí)現(xiàn)的。
Qt拓展了C++的內(nèi)省機(jī)制,但并沒有采用C++的RTTI,而是提供了更為強(qiáng)大的元對象(meta object)機(jī)制,來實(shí)現(xiàn)內(nèi)省機(jī)制?;趦?nèi)省機(jī)制,可以列出對象的方法和屬性列表,并且能夠獲取有關(guān)對象的所有信息,如參數(shù)類型。如果沒有內(nèi)省機(jī)制,QtScript和 QML是難以實(shí)現(xiàn)的。
Qt中的元對象系統(tǒng)全稱Meta Object System,是一個(gè)基于標(biāo)準(zhǔn)C++的擴(kuò)展,為Qt提供了信號與槽機(jī)制、實(shí)時(shí)類型信息、動(dòng)態(tài)屬性系統(tǒng)。元對象系統(tǒng)基于QObject類、Q_OBJECT宏、元對象編譯器MOC實(shí)現(xiàn)。
A、QObject 類
作為每一個(gè)需要利用元對象系統(tǒng)的類的基類。
B、Q_OBJECT宏
定義在每一個(gè)類的私有數(shù)據(jù)段,用來啟用元對象功能,比如動(dòng)態(tài)屬性、信號和槽。
在一個(gè)QObject類或者其派生類中,如果沒有聲明Q_OBJECT宏,那么類的metaobject對象不會(huì)被生成,類實(shí)例調(diào)用metaObject()返回的就是其父類的metaobject對象,導(dǎo)致的后果是從類的實(shí)例獲得的元數(shù)據(jù)其實(shí)都是父類的數(shù)據(jù)。因此類所定義和聲明的信號和槽都不能使用,所以,任何從QObject繼承出來的類,無論是否定義聲明了信號、槽和屬性,都應(yīng)該聲明Q_OBJECT 宏。
C、元對象編譯器MOC (Meta Object Complier),
MOC分析C++源文件,如果發(fā)現(xiàn)在一個(gè)頭文件(header file)中包含Q_OBJECT 宏定義,會(huì)動(dòng)態(tài)的生成一個(gè)moc_xxxx命名的C++源文件,源文件包含Q_OBJECT的實(shí)現(xiàn)代碼,會(huì)被編譯、鏈接到類的二進(jìn)制代碼中,作為類的完整的一部分。
元對象系統(tǒng)除了提供信號槽機(jī)制在對象間進(jìn)行通訊的功能,還提供了如下功能:
QObject::metaObject() 方法
獲得與一個(gè)類相關(guān)聯(lián)的 meta-object
QMetaObject::className() 方法
在運(yùn)行期間返回一個(gè)對象的類名,不需要本地C++編譯器的RTTI(run-time type information)支持
QObject::inherits() 方法
用來判斷生成一個(gè)對象類是不是從一個(gè)特定的類繼承出來,必須是在QObject類的直接或者間接派生類當(dāng)中。
QObject::tr() and QObject::trUtf8()
為軟件的國際化翻譯字符串
QObject::setProperty() and QObject::property()
根據(jù)屬性名動(dòng)態(tài)的設(shè)置和獲取屬性值
??使用qobject_cast()方法在QObject類之間提供動(dòng)態(tài)轉(zhuǎn)換,qobject_cast()方法的功能類似于標(biāo)準(zhǔn)C++的dynamic_cast(),但qobject_cast()不需要RTTI的支持。
#define Q_PROPERTY(text)
Q_PROPERTY定義在/src/corelib/kernel/Qobjectdefs.h文件中,用于被MOC處理。
Q_PROPERTY(type name
READ getFunction
[WRITE setFunction]
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL])
Type:屬性的類型
Name:屬性的名稱
READ getFunction:屬性的訪問函數(shù)
WRITE setFunction:屬性的設(shè)置函數(shù)
RESET resetFunction:屬性的復(fù)位函數(shù)
NOTIFY notifySignal:屬性發(fā)生變化的地方發(fā)射的notifySignal信號
REVISION int:屬性的版本,屬性暴露到QML中
DESIGNABLE bool:屬性在GUI設(shè)計(jì)器中是否可見,默認(rèn)為true
SCRIPTABLE bool:屬性是否可以被腳本引擎訪問,默認(rèn)為true
STORED bool:
USER bool:
CONSTANT:標(biāo)識屬性的值是常量,值為常量的屬性沒有WRITE、NOTIFY
FINAL:標(biāo)識屬性不會(huì)被派生類覆寫
注意:NOTIFY notifySignal聲明了屬性發(fā)生變化時(shí)發(fā)射notifySignal信號,但并沒有實(shí)現(xiàn),因此程序員需要在屬性發(fā)生變化的地方發(fā)射notifySignal信號。
Object.h:
#ifndef OBJECT_H
#define OBJECT_H
#include <QObject>
#include <QString>
#include <QDebug>
class Object : public QObject
{
Q_OBJECT
Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged)
Q_PROPERTY(int score READ score WRITE setScore NOTIFY scoreChanged)
Q_CLASSINFO("Author", "Scorpio")
Q_CLASSINFO("Version", "1.0")
Q_ENUMS(Level)
protected:
QString m_name;
QString m_level;
int m_age;
int m_score;
public:
enum Level
{
Basic,
Middle,
Advanced
};
public:
explicit Object(QString name, QObject *parent = 0):QObject(parent)
{
m_name = name;
setObjectName(m_name);
connect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int)));
connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int)));
}
int age()const
{
return m_age;
}
void setAge(const int& age)
{
m_age = age;
emit ageChanged(m_age);
}
int score()const
{
return m_score;
}
void setScore(const int& score)
{
m_score = score;
emit scoreChanged(m_score);
}
signals:
void ageChanged(int age);
void scoreChanged(int score);
public slots:
void onAgeChanged(int age)
{
qDebug() << "age changed:" << age;
}
void onScoreChanged(int score)
{
qDebug() << "score changed:" << score;
}
};
#endif // OBJECT_H
Main.cpp:
#include <QCoreApplication>
#include "Object.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Object ob("object");
//設(shè)置屬性age
ob.setProperty("age", QVariant(30));
qDebug() << "age: " << ob.age();
qDebug() << "property age: " << ob.property("age").toInt();
//設(shè)置屬性score
ob.setProperty("score", QVariant(90));
qDebug() << "score: " << ob.score();
qDebug() << "property score: " << ob.property("score").toInt();
//內(nèi)省intropection,運(yùn)行時(shí)查詢對象信息
qDebug() << "object name: " << ob.objectName();
qDebug() << "class name: " << ob.metaObject()->className();
qDebug() << "isWidgetType: " << ob.isWidgetType();
qDebug() << "inherit: " << ob.inherits("QObject");
return a.exec();
}
#define Q_INVOKABLE
Q_INVOKABLE定義在/src/corelib/kernel/Qobjectdefs.h文件中,用于被MOC識別。
Q_INVOKABLE宏用于定義一個(gè)成員函數(shù)可以被元對象系統(tǒng)調(diào)用,Q_INVOKABLE宏必須寫在函數(shù)的返回類型之前。如下:
Q_INVOKABLE void invokableMethod();
invokableMethod()函數(shù)使用了Q_INVOKABLE宏聲明,invokableMethod()函數(shù)會(huì)被注冊到元對象系統(tǒng)中,可以使用 QMetaObject::invokeMethod()調(diào)用。
Q_INVOKABLE與QMetaObject::invokeMethod均由元對象系統(tǒng)喚起,在Qt C++/QML混合編程、跨線程編程、Qt Service Framework以及?Qt/ HTML5混合編程以及里廣泛使用。
A、在跨線程編程中的使用
如何調(diào)用駐足在其他線程里的QObject方法呢?Qt提供了一種非常友好而且干凈的解決方案:向事件隊(duì)列post一個(gè)事件,事件的處理將以調(diào)用所感興趣的方法為主(需要線程有一個(gè)正在運(yùn)行的事件循環(huán))。而觸發(fā)機(jī)制的實(shí)現(xiàn)是由MOC提供的內(nèi)省方法實(shí)現(xiàn)的。因此,只有信號、槽以及被標(biāo)記成Q_INVOKABLE的方法才能夠被其它線程所觸發(fā)調(diào)用。如果不想通過跨線程的信號、槽這一方法來實(shí)現(xiàn)調(diào)用駐足在其他線程里的QObject方法。另一選擇就是將方法聲明為Q_INVOKABLE,并且在另一線程中用invokeMethod喚起。
B、Qt Service Framework
Qt服務(wù)框架是Qt Mobility 1.0.2版本推出的,一個(gè)服務(wù)(service)是一個(gè)獨(dú)立的組件提供給客戶端(client)定義好的操作??蛻舳丝梢酝ㄟ^服務(wù)的名稱,版本號和服務(wù)的對象提供的接口來查×××。 查找到服務(wù)后,框架啟動(dòng)服務(wù)并返回一個(gè)指針。
服務(wù)通過插件(plug-ins)來實(shí)現(xiàn)。為了避免客戶端依賴某個(gè)具體的庫,服務(wù)必須繼承自QObject,保證QMetaObject?系統(tǒng)可以用來提供動(dòng)態(tài)發(fā)現(xiàn)和喚醒服務(wù)的能力。要使QmetaObject機(jī)制充分的工作,服務(wù)必須滿足,其所有的方法都是通過 signal、slot、property或invokable method和Q_INVOKEBLE來實(shí)現(xiàn)。
QServiceManager manager;
QObject *storage ;
storage = manager.loadInterface("com.nokia.qt.examples.FileStorage");
if(storage)
QMetaObject::invokeMethod(storage, "deleteFile", Q_ARG(QString, "/tmp/readme.txt"));
上述代碼通過service的元對象提供的invokeMethod方法,調(diào)用文件存儲對象的deleteFile() 方法??蛻舳瞬恍枰缹ο蟮念愋停虼艘矝]有鏈接到具體的service庫。?當(dāng)然在服務(wù)端的deleteFile方法,一定要被標(biāo)記為Q_INVOKEBLE,才能夠被元對象系統(tǒng)識別。
Qt服務(wù)框架的一個(gè)亮點(diǎn)是它支持跨進(jìn)程通信,服務(wù)可以接受遠(yuǎn)程進(jìn)程。在服務(wù)管理器上注冊后,進(jìn)程通過signal、slot、invokable method和property來通信,就像本地對象一樣。服務(wù)可以設(shè)定為在客戶端間共享,或針對一個(gè)客戶端。?在Qt服務(wù)框架推出之前,信號、槽以及invokable method僅支持跨線程。 下圖是跨進(jìn)程的服務(wù)/客戶段通信示意圖。invokable method和Q_INVOKEBLE?是跨進(jìn)城、跨線程對象之間通信的重要利器。
任何從QObject派生的類都包含自己的元數(shù)據(jù)模型,一般通過宏Q_OBJECT定義。
Q_OBJECT定義在/src/corelib/kernel/Qobjectdefs.h文件中。
#define Q_OBJECT \
public: \
Q_OBJECT_CHECK \
static const QMetaObject staticMetaObject; \
Q_OBJECT_GETSTATICMETAOBJECT \
virtual const QMetaObject *metaObject() const; \
virtual void *qt_metacast(const char *); \
QT_TR_FUNCTIONS \
virtual int qt_metacall(QMetaObject::Call, int, void **); \
private: \
Q_DECL_HIDDEN static const QMetaObjectExtraData staticMetaObjectExtraData; \
Q_DECL_HIDDEN static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);
QMetaObject類型的靜態(tài)成員變量staticMetaObject是元數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)。metaObject,qt_metacast,qt_metacall、qt_static_metacall四個(gè)虛函數(shù)由MOC在生成的moc_xxx.cpp文件中實(shí)現(xiàn)。metaObject的作用是得到元數(shù)據(jù)表指針;qt_metacast的作用是根據(jù)簽名得到相關(guān)結(jié)構(gòu)的指針,返回void*指針;qt_metacall的作用是查表然后調(diào)用調(diào)用相關(guān)的函數(shù);qt_static_metacall的作用是調(diào)用元方法(信號和槽)。#define Q_DECL_HIDDEN __attribute__((visibility("hidden")))
QMetaObject類定義在/src/corelib/kernel/Qobjectdefs.h文件。
struct Q_CORE_EXPORT QMetaObject
{
...
enum Call {
InvokeMetaMethod,
ReadProperty,
WriteProperty,
ResetProperty,
QueryPropertyDesignable,
QueryPropertyScriptable,
QueryPropertyStored,
QueryPropertyEditable,
QueryPropertyUser,
CreateInstance
};
int static_metacall(Call, int, void **) const;
static int metacall(QObject *, Call, int, void **);
struct { // private data
const QMetaObject *superdata;
const char *stringdata;
const uint *data;
const void *extradata;
} d;
};
QMetaObject中有一個(gè)嵌套結(jié)構(gòu)封裝了所有的數(shù)據(jù):
const QMetaObject superdata;//元數(shù)據(jù)代表的類的基類的元數(shù)據(jù)
const char stringdata;//元數(shù)據(jù)的簽名標(biāo)記
const uint *data;//元數(shù)據(jù)的索引數(shù)組的指針
const QMetaObject **extradata;//擴(kuò)展元數(shù)據(jù)表的指針,指向QMetaObjectExtraData數(shù)據(jù)結(jié)構(gòu)。
struct QMetaObjectExtraData
{
#ifdef Q_NO_DATA_RELOCATION
const QMetaObjectAccessor *objects;
#else
const QMetaObject **objects;
#endif
typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **); //from revision 6
//typedef int (*StaticMetaCall)(QMetaObject::Call, int, void **); //used from revison 2 until revison 5
StaticMetacallFunction static_metacall;
};
static_metacall是一個(gè)指向Object::qt_static_metacall 的函數(shù)指針。
宏QT_TR_FUNCTIONS是和翻譯相關(guān)的。
#define QT_TR_FUNCTIONS \
static inline QString tr(const char *s, const char *c = 0) \
{ return staticMetaObject.tr(s, c); } \
#endif
Qt在/src/corelib/kernel/Qobjectdefs.h文件中定義了大量的宏。
#ifndef Q_MOC_RUN
# if defined(QT_NO_KEYWORDS)
# define QT_NO_EMIT
# else
# define slots
# define signals protected
# endif
# define Q_SLOTS
# define Q_SIGNALS protected
# define Q_PRIVATE_SLOT(d, signature)
# define Q_EMIT
#ifndef QT_NO_EMIT
# define emit
#endif
#define Q_CLASSINFO(name, value)
#define Q_INTERFACES(x)
#define Q_PROPERTY(text)
#define Q_PRIVATE_PROPERTY(d, text)
#define Q_REVISION(v)
#define Q_OVERRIDE(text)
#define Q_ENUMS(x)
#define Q_FLAGS(x)
#define Q_SCRIPTABLE
#define Q_INVOKABLE
#define Q_SIGNAL
#define Q_SLOT
Qt中的大部分宏都無實(shí)際的定義,都是提供給MOC識別處理的,MOC工具通過對類中宏的解析處理生成moc_xxx.cpp文件。
在 Qt4 及之前的版本中,signals被展開成protected。Qt5則變成public,用以支持新的語法。
A、處理Q_OBJECT宏和signals/slots關(guān)鍵字,生成信號和槽的底層代碼
B、處理Q_PROPERTY()和Q_ENUM()生成property系統(tǒng)代碼
C、處理Q_FLAGS()和Q_CLASSINFO()生成額外的類meta信息
D、不需要MOC處理的代碼可以用預(yù)定義的宏括起來,如下:
#ifndef Q_MOC_RUN
…
#endif
A、模板類不能使用信號/槽機(jī)制
B、MOC不擴(kuò)展宏,所以信號和槽的定義不能使用宏, 包括connect的時(shí)候也不能用宏做信號和槽的名字以及參數(shù)
C、從多個(gè)類派生時(shí),QObject派生類必須放在第一個(gè)。?QObject(或其子類)作為多重繼承的父類之一時(shí),需要把它放在第一個(gè)。 如果使用多重繼承,moc在處理時(shí)假設(shè)首先繼承的類是QObject的一個(gè)子類,需要確保首先繼承的類是QObject或其子類。
D、函數(shù)指針不能作為信號或槽的參數(shù), 因?yàn)槠涓袷奖容^復(fù)雜,MOC不能處理??梢杂胻ypedef把它定義成簡單的形式再使用。
E、用枚舉類型或typedef的類型做信號和槽的參數(shù)時(shí),必須fully qualified。這個(gè)詞中文不知道怎么翻譯才合適,簡單的說就是, 如果是在類里定義的, 必須把類的路徑或者命名空間的路徑都加上, 防止出現(xiàn)混淆。如Qt::Alignment之類的,前面的Qt就是Alignment的qualifier, 必須加上,而且有幾級加幾級。
F、信號和槽不能返回引用類型
G、signals和slots關(guān)鍵字區(qū)域只能放置信號和槽的定義,不能放其它的如變量、構(gòu)造函數(shù)的定義等,友元聲明不能位于信號或者槽聲明區(qū)內(nèi)。
H、嵌套類不能含有信號和槽?
MOC無法處理嵌套類中的信號和槽,錯(cuò)誤的例子:?
class A:public QObject
{
Q_OBJECT
public:
class B
{
public slots://錯(cuò)誤用法
};
};
I、信號槽不能有缺省參數(shù)
Qt線程間傳遞自定義類型數(shù)據(jù)時(shí),自己定義的類型如果直接使用信號槽來傳遞的話會(huì)產(chǎn)生下面這種錯(cuò)誤:
????????? QObject::connect: Cannot queue arguments of type 'XXXXX' (Make sure 'XXXXX' is registed using qRegisterMetaType().)
???????? 原因:當(dāng)一個(gè)signal被放到隊(duì)列中(queued)時(shí),參數(shù)(arguments)也會(huì)被一起一起放到隊(duì)列中,參數(shù)在被傳送到slot之前需要被拷貝、存儲在隊(duì)列中;為了能夠在隊(duì)列中存儲參數(shù)(argument),Qt需要去construct、destruct、copy參數(shù)對象,而為了讓Qt知道怎樣去作這些事情,參數(shù)的類型需要使用qRegisterMetaType來注冊。
步驟:(以自定義XXXXX類型為例)
A、自定義類型時(shí)在類的頂部包含:#include <QMetaType>
B、在類型定義完成后,加入聲明:Q_DECLARE_METATYPE(XXXXX);
C、在main()函數(shù)中注冊自定義類類型:qRegisterMetaType<XXXXX>("XXXXX");
如果希望使用類型的引用,同樣要注冊:qRegisterMetaType<XXXXX>("XXXXX&");
查看工程的Makefile文件可以查找到MOC生成moc_xxx.cpp文件的命令:
moc_Object.cpp: ../moc/Object.h
/usr/local/Trolltech/Qt-4.8.6/bin/moc $(DEFINES) $(INCPATH) ../moc/Object.h -o moc_Object.cpp
因此命令行可以簡化為:
`moc Object.h -o moc_Object.cpp`
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。