一、元對象
元對象(meta object)意思是描述另一個對象結構的對象,比如獲得一個對象有多少成員函數,有哪些屬性。在Qt中,我們將要用到的是QMetaObject這個類。
元對象系統基於以下3點:
- 以QObject作為基類
- 類聲明的私有區域中,Q_Object宏指令使我們能夠使用元對象的特性,比如動態屬性、信號、槽等
- 元對象編譯器(Meta-Object Compiler moc)為QObject子類生成具有元對象特性的代碼
我們可以通過QObject類的一個成員函數獲得該類的元對象:
QMetaObject *QObject::metaObject() const
通過這個元對象,進而可以獲取一個QObject對象的更多信息:
QMetaObject::className() 返回運行時類的名稱(不需要C++中的運行時類型識別機制RTTI)
QMetaObject::methodCount() 返回類中方法的個數
以上只是元對象的簡單介紹,記住元對象系統的3點特性。之所以要介紹元對象,因為Qt中很多用法是基於元對象的,如果不支持元對象,比如沒有繼承自QObject,那么很多東西將無法使用,下面對此作進一步介紹。
二、類型識別
眾所周知,C++中使用dynamic_cast和typeid這兩個運算符進行運行時類型識別(RTII),但是Qt提供另外兩種運行時類型識別方法:
qobject_cast 和 QObject::inherits()
看名字就可以知道,這兩個方法都是基於QObject的,也就是元對象系統。
if (QLabel *label = qobject_cast<QLabel *>(obj)) { label->setText(tr("Ping")); } else if (QPushButton *button = qobject_cast<QPushButton *>(obj)) { button->setText(tr("Pong!")); }
據說,qobject_cast的速度比dynamic_cast的速度快很多。
QObject::inherits(const char *className)的速度相對慢一些,所以盡可能使用qobject_cast。
三、Qt中的屬性
1.自定義屬性
我們可能已經接觸到很多Qt中的屬性了,比如qreal類型的opacity屬性表示“透明度”,QRect類型的geometry表示“幾何位置和大小”,QPoint類型的pos屬性代表“位置”。
所謂屬性,也就是類中的一個數據成員,我們可以獲取(get)和設置(set)。
除了Qt中一些類已經具備的屬性,我們還可以自定義屬性,也就是定義一種訪問數據成員的方式。
在一個繼承自QObject的類中使用 Q_PROPERTY 宏指令,比如:
Q_PROPERTY(bool focus READ hasFocus) Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled) Q_PROPERTY(QColor color MEMBER m_color NOTIFY colorChanged)
不要被這個用法搞暈了,其實很簡單,剛開始指定屬性類型和名稱,然后READ表示獲取屬性值的方法,一般這兩點是必須的。其他都是可選的,比如WRITE表示設置屬性值得方法,MEMBER表示這個屬性在類中數據成員的名稱,NOTIFY表示屬性改變發出的信號。
2.屬性的類型
由上可知,屬性的類型可以是bool、QString、QRect等等,我們可以通過 QVariant::Type 的枚舉值獲得所有可用於屬性的類型。
可以查到,它不支持枚舉類型,但可以通過 Q_ENUM 來設置:
enum Priority { High, Low, VeryHigh, VeryLow }; Q_ENUM(Priority) Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged)
當然,自定義的類型也是不支持的,需要通過 Q_DECLARE_METATYPE 注冊元類型:
struct MyStruct { int i; ... }; Q_DECLARE_METATYPE(MyStruct)
3.屬性的讀與寫
我們可以直接使用get和set方法來讀寫屬性,也可以通過QObject與QMetaObject來間接地讀寫屬性。
首先是設置屬性值
比如類QAbstractButton有一個“down”的屬性,表示按鈕是否被按下,它有一個成員函數 QAbstractButton::setDown() 來改變屬性值,同時,我們也可以通過 QObject::setProperty() 對其進行設置:
QPushButton *button = new QPushButton; QObject *object = button; button->setDown(true); object->setProperty("down", true);
值得注意的是,setProperty()這個函數不但可以改變屬性值,也可以在運行時動態地為對象添加屬性。
接下來是讀取屬性值
如果有get函數,可以直接調用它,當然也可以通過 QObject::property() 來獲取屬性,它的返回值是 QVariant 類型的,通過 canConvert() 進行判斷,然后將其轉換為所需的類型。
QObject *object = ... const QMetaObject *metaobject = object->metaObject(); int count = metaobject->propertyCount(); for (int i=0; i<count; ++i) { QMetaProperty metaproperty = metaobject->property(i); const char *name = metaproperty.name(); QVariant value = object->property(name); ... }
四、自定義屬性有什么用
也許你要問,說了這么多廢話,不就是讀寫成員變量嗎?好吧,平時確實不會用太多,但是如果做插件開發、qml等,就會經常用到了。不過,在通常的界面開發中,屬性也是有用處的,下面舉兩個例子。
1.改變樣式
樣式表設置中有一個屬性選擇器,比如 QPushButton[flat="false"] 意思是當按鈕屬性flat為false時的樣式。
舉個栗子,我們有個QWidget類,名字叫PropertyTest,界面中有一個按鈕叫pushButton
#pushButton{border:4px solid blue;} PropertyTest[borderColor="red"] #pushButton{border:4px solid red;} PropertyTest[borderColor="green"] #pushButton{border:4px solid green;} PropertyTest[borderColor="blue"] #pushButton{border:4px solid blue;}
按鈕默認樣式是blue藍色,通過改變類PropertyTest的屬性borderColor值改變按鈕的顏色。
在代碼中,首先定義屬性
Q_PROPERTY(QString borderColor READ getBorderColor WRITE setBorderColor)
使用一個成員變量保存屬性的值,並通過set和get函數分別設置和獲得該值。
private: QString m_strBorderColor; private: void setBorderColor(const QString &strBorderColor){ m_strBorderColor = strBorderColor; } QString getBorderColor(){ return m_strBorderColor; }
單擊按鈕pushButton改變屬性值,從而改變按鈕pushButton的樣式。
void PropertyTest::changeBorderColor() { if (m_iTest % 3 == 0) { setBorderColor("red"); } else if (m_iTest % 3 == 1) { setBorderColor("green"); } else { setBorderColor("blue"); } style()->unpolish(ui.pushButton_3); style()->polish(ui.pushButton_3); update(); m_iTest++; }
最后要注意的是,上面代碼中的unpolish和polish部分。
在Qt文檔中有個提醒,在使用屬性選擇器時,如果之前控件有其它樣式,那么需要重寫設置一下,“舊的不去,新的不來”,通過unpolish和polish抹去舊的樣式,塗上新的樣式。
2.動畫中使用自定義屬性
如果我們想通過動畫使一個按鈕逐漸變透明,思路會是這樣:按鈕QPushButton繼承自QWidget,在QWidget中有個函數setWindowOpacity,所以只需使用動畫類QPropertyAnimation,屬性那個參數設置為windowOpacity。
然而,實際中,按鈕透明度不會有任何改變,繼續查看文檔才知道——只有調用setWindowFlags函數,將窗口屬性設置為Qt::Window,windowOpacity這個屬性才能生效。但是這樣做pushbutton就不是正常的widget了。
因此,有必要尋求其它方法,在QWidget中有一個函數setGraphicsEffect(QGraphicsEffect *),其中QGraphicsEffect有一個派生類QGraphicsOpacityEffect,可以通過它來設置QWidget的透明度。
m_pOpacityEffect = new QGraphicsOpacityEffect(this); m_pOpacityEffect->setOpacity(1); this->setGraphicsEffect(m_pOpacityEffect);
Q_PROPERTY(qreal buttonOpacity READ buttonOpacity WRITE setBtnOpacity)
上面的寫法可能不太好,因為qreal精度與機器有關,最好用double或float。
定義屬性時,在函數setBtnOpacity中改變QGraphicsOpacityEffect對象,來調整透明度。
好了,現在我們將動畫屬性名稱設置為buttonOpacity—— QPropertyAnimation::setPropertyName("buttonOpacity") ,就能通過動畫改變按鈕的透明度了。
五、invokeMethod()
Qt中的信號槽機制是以元對象為基礎的,通過名稱以類型安全的方式來間接調用槽函數。
當調用槽函數時,實際是由invokeMethod()完成的。
比如顯示一個窗口,一般是通過show()函數來完成,不過我們還能這樣做:
MyWidget w; QMetaObject::invokeMethod(&w, "show");
上面講了如何將成員變量注冊進元對象系統,那么對於成員函數,該怎么做呢?
在聲明一個類的成員函數時,通過使用 Q_INVOKABLE 宏進行注冊,可以使它們能夠被元對象系統調用。
class Window : public QWidget { Q_OBJECT public: Window(); void normalMethod(); Q_INVOKABLE void invokableMethod(); };
that'a all.
有錯誤的地方還請指正!