参考书籍:
- 王维波, 栗宝鹃, & 侯春望. (2018). Qt5.9 C++开发指南 (1st ed.). 人民邮电出版社.
元对象系统
Qt元对象系统的三个基础:
QObject
是所有使用元对象系统的类的基类。- 在一个类的
private
部分声明Q_OBJECT
宏,使得类可以使用元对象的特性,如动态属性、信号和槽。 - MOC(元对象编译器)为每个
QObject
的子类提供必要的代码来实现元对象系统的特性。
除了信号与槽,元对象还提供以下一些功能:
QObject::metaObject()
函数返回类关联的元对象,元对象QMetaObject
包含了访问元对象的一些接口函数,例如QMetaObject::className()
函数可在运行时返回类的名称字符串。QObject *obj = new QPushButton; obj->metaObject()->className();//返回"QPushButton"
QMetaObject::newInstance()
函数创建类的一个新的实例。QObject::inherits(const char *className)
函数判断一个对象实例是否是名称为className
的类或QObject
的子类的实例。例如:QTimer *timer = new QTimer; //QTimer 是QObject的子类 timer->inherits("QTimer"); //返回true timer->inherits("QObject"); //返回true timer->inherits("QAbstractButton"); //返回false,不是QAbstractButton的子类
QObject::tr()
和QObject::trUTF8()
函数可翻译字符串,用于多语言界面设计。QObject::setProperty()
和QObject::property
函数可用于通过属性名动态设置和获取属性值。
对于QObject
及其子类,可以用qobject_cast
来投射,qobject_cast
并不区分Qt内建的类型和用户自定义类型。
属性系统
1.属性定义
Q_PROPERTY
宏可用于定义属性,它是基于元对象系统实现的。Qt的属性系统与C++编译器无关,可以用任何标准的C++编译器编译定义了属性的Qt C++程序、
使用格式如下:
Q_PROPERTY(type name
READ getFunction [WRITE setFunction]
[RESER resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL] )
该宏定义一个返回值类型为type
,名称为name
的属性,用READ
、WRITE
关键字定义属性的读取、写入函数,还有其他的一些关键字定义属性的一些操作特性。属性的类型可以是QVariant
支持的任何类型,也可以用于自定义类型。
Q_PROPERTY
宏定义属性的一些主要关键字的意义如下:
- READ
- 指定一个读取属性值的函数,没有MEMBER关键字时必须设置READ、
- WRITE
- 制定一个设定属性的函数,只读属性没有WRITE设置。
- MEMBER
- 指定一个成员变量与属性关联,成为可读可写的属性,无需再设置READ和WRITE。
- RESET
- 可选的,用于指定一个设置属性缺省值的函数。
- NOTIFY
- 可选的,用于设置一个信号,当属性值发生变化时发射此信号。
- DESIGNABEL
- 表示属性是否在Qt Designer里可见,缺省为true。
- CONSTANT
- 表示属性值是一个常数,对于一个对象实例。READ指定的函数返回值是常数,但是每个实例的返回值可以不一样。具有CONSTANT关键字的属性不能有WRITE和NOTIFY关键字。
- FINAL
- 表示所定义的属性不能被子类重载。
例子:
Q_PROPERTY(bool focus READ hasFocus)
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled)
Q_PROPERTY(QCursor cursor Read cursor WRITE setCursoe RESET unsetCursor)
2.属性的使用
不管是否用READ和WRITE定义了接口函数,只要知道属性名称,就可以通过QQObject::property()
读取属性值,并通过QQObject::setProperty()
设置属性值。例如:
QPushButton *button = new QPushButton;
QObject *object = button;
object->setProperty("flat", true);
bool isFlat = object->property("flat");
3.动态属性
QObject::setProperty()
函数可以在运行时为类定义一个新的属性,称之为动态属性。动态属性是针对类的实例定义的。
动态属性可以用QObject::property()
查询,就如在类定义里用Q_PROPERTY
宏定义的属性一样。
例如,在数据表编辑界面上,一些字段是必填字段,就可以在初始化界面时为这些字段的关联显示组件定义一个新的required
属性,并设置为"true"
,如:
editName->setProperty("required", "true");
dombSex->setProperty("required", "true");
checkAgree->setProperty("required", "true");
然后,可以应用下面的样式定义将这种必填字段的背景颜色设置为亮绿色。
*[required="true"] (background-color: lime);
4.类的附加信息
属性系统还有一个宏Q_CLASSINFO()
可以为类的元对象定义“名称——值”信息,如:
class QMyClass : public QObject
{
Q_OBJECT
Q_CLASSINFO("author", "Wang")
Q_CLASSINFO("company", "UPC")
Q_CLASSINFO("version", "3.0.1")
public:
...
}
用Q_CLASSINFO()
宏定义附加类信息后,可以通过元对象的一些函数获取类的附加信息,如classInfo(int)
获取某个附加信息,函数原型定义如下:
QMetaClassInfo QMetaObject::classInfo(int index) const
返回值是QMetaClassInfo
类型,由name()
和value()
两个函数,可以获得类附加信息的名称和值。
信号与槽
1.connect()的不同参数形式
connect有多种形式:
QMetaObjcect::Connection QObject::connect(const QObject* sender,
const char *signal,
const QObject *receiver,
const char *method,
Qt::ConnectionType type = Qt::AutoConnection
);
QMetaObject::Connection QObject::connect(const QObject *sender,
const QMetaMethod &signal,
const QObject *receiver,
const QMetaMethod &method,
Qt::ConnectionType type = Qt::AuotConnection
);
第一种是通用的,调用格式:
connnect(sender, SLOT(signal()), receiver, SLOT(slot()));
例如:
connect(sender, SLOT(valueChanged(int)), this, SLOT(updateStatus(int)));
第二种仅限于信号名称唯一的(即,没有参数不同而同名的两个信号),调用例子:
connect(sender, &QLineEdit::textChanged, this, &widget::on_textChanged)l
第二种在对参数比较多的信号时比较方便。
参数 Qt::ConnectionType type
表示信号与槽的关联方式。
QAutoConnection
(缺省值)- 如果信号的接收者与发射者在同一个线程,就使用
Qt::DirectConnection
方式;否则使用Qt::QueuedConnection
方式,在信号发生时自动确定关联方式。
- 如果信号的接收者与发射者在同一个线程,就使用
Qt::DirectConnection
- 信号发射时槽函数立即执行,槽函数与信号在同一个线程。
Qt::QueuedConnection
- 在事件循环回到接收者线程后执行槽函数,槽函数与信号在不同的线程。
2.使用sender()获得信号发射者
在槽函数里,使用QObject::sender()
可以获取信号发射者的指针。如果知道信号发射者的类型,可用将指针投射为确定的类型,然后使用这个确定类的接口函数。
例如,在QSpinBox
的valueChanged(ind)
信号的槽函数里,可以通过sender()
和qobject_cast
获得信号发射者的指针,从而对信号发射者进行操作。
QSpinBox *spinBox = qobject_cast<QSpinBox *>(sender());
3.自定义信号及其使用
在自己设计的类里也可以自定义信号,信号就是在类定义里声明的一个函数,但是这个函数无需实现,只需发射(emit)。
例如,在下面的自定义类QPerson
的signals
部分定义一个信号ageChanged(int)
。
class QPerson : public QObject{
QOBJECT
private:
int m_age = 10;
public:
void incAge();
signals:
void ageChanged(int value);
}
信号函数必须是无返回值的函数,但是可以由入参,信号函数无需实现,只需在某些条件下发射信号。
例如,在incAge()
函数中发射信号,其代码如下:
void QPerson::incAge()
{
m_age++;
emit ageChanged(m_age); //发射信号
}