QObject类是所以Qt类的基类,也是Qt对象模型的核心。这个模型中的核心特性就是能让对象键无缝通信的信号和槽的机制。我们可以使用connect()函数将一个信号连接到一个槽上,也可以使用disconnect()删除这个连接。为了防止无休止的的信号通知,还可以使用blockSignals()来临时阻塞信号。而connectNotify()和disconnectNotify()两个函数可以让我们能够跟踪一个对象上信号的连接变化。
QObject对象使用对象树的方式来组织它们自己。所以,当你以另一个对象作为父来创建一个QObject对象时,该对象会自动的将它自己添加的父级的孩子列表中,父级会接管该对象的所有权,也就是说,父级会在自己的析构函数中自动释放它的所有孩子。我们可以使用对象名通过findChild()或findChildren()函数在对象树中查找一个或多个对象。
每一个QObject对象都有一个objectName(),并且它的类名也可以使用metaObject()对象来获取。还可以使用inherits()来判断一个对象所属的类是否继承自另一个类。
当一个对象被销毁时,它会发出destroyed()信号。我们可以捕捉该信号来做一些最后的处理。
QObject对象可以使用event()函数来处理事件,还可以通过installEventFilter()和eventFilter()函数来过滤或拦截其他对象的事件。QObject还支持基本的定时器。
注意,对于所有实现信号、槽、或者属性的QObject对象来说,Q_OBJECT宏都是必须的。我们推荐在每一个QObject的子类中使用这个宏,无论其是否实现信号、槽或属性,这可以避免一些奇怪的行为。
Qt中,所有的控件都派生自QObject。而QObject中的isWidgetType()函数可以判断一个对象是否是一个控件。
线程亲和性
QObject对象有一个线程亲和性,或者是它生存在某个特定的线程中。当一个QObject对象接收到一个queued signal或一个posted event时,相应的槽函数或事件处理器会在该对象所生存的线程中执行。
注意,如果一个线程没有线程亲和性,或者,如果它生存的线程没有运行事件循环,那么它不能接收到queued singal或posted event。
默认情况下,QObject对象生存在创建它的那个线程中。但我们可以使用thread()函数来查询对象的线程亲和性,还可以使用moveToThread()函数来改变一个对象的线程亲和性。并且,所有的对象都和它的父生存在同一个线程中。因此:
- 在调用setParent()时,如果涉及到的两个对象不在同一个线程,就会失败。
- 当一个QObject对象被移动到其他线程时,它们所有孩子也会自动被移动。
- moveToThread()在对象有父级的时候,会失败
- 如果一个QObject对象是在QThread::run()中被创建的,那么它们不能成为QThread对象的孩子,因为QThread对象并不生存在调用QThread::run()的那个线程中。
注意,一个QObject对象的成员变量不会自动成为该对象的孩子。对象间的父子关系必须通过向子对象的构造函数传递一个指针,或通过调用setParent()来实现。如果没有这样做,那么,当调用moveToThread()时,QObject对象的成员变量会仍然存在于老线程中。
没有拷贝构造函数或赋值运算符
QObject对象既 没有拷贝构造函数也没有赋值运算符。实际上,它们在QObject类中都进行了声明,只不过是放在了private区域,并使用了Q_DISABLE_COPY()宏进行了禁用。这样一来,你应该在需要QObject子类作为值的地方,使用一个QObject指针来代替。例如,因为没有拷贝构造函数,你不能将QObject对象作为值存储到容器类中, 必须使用指针来存储。
自动连接
Qt的元对象系统提供了一个自动连接信号和槽的机制。只要对象定义了合适的对象名,并且相应的槽函数的声明遵循移动的命名约定,那么就会在运行时通过QMetaObject::connectSlotsByName()函数来自动进行信号和槽的连接。我们在之前的例子中,经常使用的“转到槽...”操作,就通过这种机制实现的。
动态属性
从Qt4.2开始,就可以在运行时动态的从QObject对象上添加或移除属性。动态属性不需要在编译时进行声明,但它们提供了和静态属性一样的效率,并使用同一套API进行操作,即使用property()函数来读取属性值,使用setProperty()函数来修改属性值。
而从Qt4.3开始,Qt Designer也支持了动态属性,并且Qt的标准控件和用户自定义的控件都可以被赋予动态属性。
国际化
所有QObject的子类都支持Qt的翻译特性,也使我们可以把应用程序的用户界面翻译成不同的语言。而为了使用户可见的文本可以被翻译,我们必须使用tr()函数来将这些文本包围起来。
说了QObject的几个特性外,下面我们来看几个QObject类的重要的成员函数。
- bool QObject::blockSignals(bool block)
使用该函数,我们可以临时的阻塞一些信号。如果block为true,那么该对象发出的信号将被阻塞,也就是说发出的信号不会调用任何与它连接的槽函数。如果block为false,则取消阻塞。注意,destroyed()信号不会被阻塞。并且,信号阻塞期间所发出的信号不会被缓存。
- void QObject::childEvent(QChildEvent *event)
子类可以重新实现这个事件处理器来处理子对象的添加和删除事件。除此之外,QEvent::ChildPolished事件会在子对象被抛光时或者被抛光的对象被添加到孩子列表中时发送。如果收到一个ChildPolished事件,那么该子对象的构造函数通常情况下已经完成了。但是,这是无法保证的,并且,在一个控件的构造过程中,可能产生多次ChildPolished事件。
对每一个子控件,你会收到一个ChildAdded事件,0个或多个ChildPolished事件,一个ChildRemoved事件。
- const QObjectList &QObject::children() const
返回一个孩子列表。并且,链表的顺序是按子对象添加的顺序排列的,第一个添加的子对象或控件在列表中的第一位,最后一个添加的在最后一位。也就是说,子对象或子控件是以append的形式追加的。
- QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
- QMetaObject::Connection connect(const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod &method, Qt::ConnectionType type = Qt::AutoConnection)
- QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection)
- QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
- QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection)
- bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
- bool disconnect(const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod &method)
- bool disconnect(const QMetaObject::Connection &connection)
- bool disconnect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method)
信号和槽进行连接和取消连接的函数。并且,一个信号可以连接到多个信号和槽上,通用,多个信号也可以连接到一个槽函数上。如果一个信号被连接到多个槽函数,当该信号被发送时,这些槽函数被调用的顺序和它们被连接的顺序一样。connect()函数如果调用成功,会返回一个QMetaObject::Connection对象,代表一个连接的句柄;如果调用失败,返回的连接句柄将是无效的。
默认情况下,一个信号会发送给每一个你生成的连接。可以使用disconnect()来打破所有的连接。
如果你向connect()函数中type参数传入一个Qt::UniqueConnection,那么,只有在当前不存在一个通用的连接时,即同一个对象将同一个信号连接到同一个槽函数,该调用才会成功。
最后一个可选的参数type,描述了连接的类型。特别情况下,它决定了一个特定的信号是立即被递送给一个槽函数,还是暂时排队,以后再递送。如果信号被排队了,那么信号携带的参数类型必须是Qt 元对象系统所认识的,因为,Qt在幕后会拷贝这些参数,并将其存储在一个事件对象中。
而对于disconnect()函数,可以传入0作为参数,意味着,“任何信号”,“任何接收对象”,“接收对象的任何槽函数”。
但sender不能传0。即不能再一次调用中取消多个对象的信号连接。
如果signal是0,即断开receiver和method再任何信号上的连接。如果非0,只断开特定的信号连接。
如果receiver是0,即断开到signal的所以连接。
如果method是0,即断开连接到receiver的任何东西
- void QObject::deleteLater()
规划一个对象的释放。该对象会在程序控制返回到事件循环时被释放。如果当调用这个函数时,事件循环还为运行,比如,在QCorApplication()函数前调用该函数,那么该对象会在事件循环启动时被立即释放。如果该函数在主事件循环停止之后被调用,那么该对象将不会被释放。从Qt4.8开始,如果在一个对象上调用该函数,但该对象所在的线程没有运行事件循环,那么该对象会在线程结束时被销毁。
注意,进入和离开一个新的事件循环,比如打开一个模态对话框,不会执行延迟的释放操作;对于将要释放的对象来说,程序控制必须返回到调用该函数的事件循环中,才会被释放。
另外,在一个对象上多次调用该函数是安全的;因为当第一次被延迟的释放事件被递送之后,该对象的任何未决的事件都会从事件队列中删除。
- bool QObject::event(QEvent *e)
可以重新实现该虚函数来自定义对象的行为。该函数接受发送给该对象的事件,并在事件被处理时返回true。对于所有我们自己不处理的事件,经过调用父类的实现。如:
- class MyClass : public QWidget
- {
- Q_OBJECT
-
- public:
- MyClass(QWidget *parent = 0);
- ~MyClass();
-
- bool event(QEvent* ev)
- {
- if (ev->type() == QEvent::PolishRequest) {
-
- doThings();
- return true;
- } else if (ev->type() == QEvent::Show) {
-
- doThings2();
- QWidget::event(ev);
- return true;
- }
-
- return QWidget::event(ev);
- }
- };
- T QObject::findChild(const QString &name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const
- QList QObject::findChildren(const QString &name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const
- QList QObject::findChildren(const QRegExp ®Exp, Qt::FindChildOptions options = Qt::FindChildrenRecursively) const
- QList QObject::findChildren(const QRegularExpression &re, Qt::FindChildOptions options = Qt::FindChildrenRecursively) const
根据名字来查找子控件,如果不存在该控件,则返回0。如果忽略了name参数,则会匹配所有的对象。该搜索过程,默认是递归的,除非指明了FindDirectChildrenOnly选项。
如果存在多个匹配的对象,会返回直接祖先。如果有多个直接祖先,哪一个被返回将是未定义的。
- void QObject::moveToThread(QThread *targetThread)
改变对象及其孩子的线程亲和性。但如果带对象有父,则无法移动。所有的事件处理会在targetThread中继续进行。
如果targetThread是0,那么该对象及其孩子的所以的事件处理会停止。
与该对象有关的所以获得定时器会被重置,即定时器先在当前线程中停止,再在targetThread中以同样的interval重新启动。所以,频繁的在不同线程间移动一个对象,会导致定时器事件延迟。
在线程亲和性改变之前,对象会收到一个QEvent::ThreadChange事件。我们可以处理该事件,进行一些特殊的操作。注意,递送给该对象的任何新事件都会在targetThread中进行处理。
- int QObject::startTimer(int interval, Qt::TimerType timerType = Qt::CoarseTimer)
- void QObject::killTimer(int id)
- void QObject::timerEvent(QTimerEvent *event)
开启一个定时器,并返回该定时器的标识符;或在不能开启定时器时返回0。
成功开启定时器后,每隔interval,就会接收到一个TimerEvent事件,该事件又是由timerEvent()函数来处理。直到调用killTimer(),定时器停止。
如果interval是0,那么定时器事件会在没有其他窗口事件需要处理时才被递送。通过这个方式,可以实现一个空闲处理的功能。
当然,上面这些函数并不是QObject的全部内容,大家可以去Qt帮助文档中,浏览QObject的详细内容,包括我们此处没讲到的一些函数。另外,QObject中还有一些常用的宏定义,大都比较简单,大家可以自行研读