QObject

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) {
              // overwrite handling of PolishRequest if any
              doThings();
              return true;
          } else  if (ev->type() == QEvent::Show) {
              // complement handling of Show if any
              doThings2();
              QWidget::event(ev);
              return true;
          }
          // Make sure the rest of events are handled
          return QWidget::event(ev);
      }
  };

T QObject::findChild(const QString &name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const
QList<T> QObject::findChildren(const QString &name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const
QList<T> QObject::findChildren(const QRegExp &regExp, Qt::FindChildOptions options = Qt::FindChildrenRecursively) const
QList<T> 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中还有一些常用的宏定义,大都比较简单,大家可以自行研读。






<think>我们讨论的是Qt框架中的两个基础类:QObject和QWidget。它们在Qt的类继承树中占据核心位置,理解它们的区别和联系对于开发Qt应用程序至关重要。###回答:QWidget与QObject的关系和区别1.**继承关系**:QWidget是QObject的直接子类,这意味着QWidget继承了QObject的所有特性,并添加了与图形用户界面(GUI)相关的功能。```cppclassQObject{/*...*/};classQWidget:publicQObject{/*...*/};```2.**核心功能差异**:-**QObject**:-提供Qt的核心功能:对象树管理、信号槽机制、事件处理机制、属性系统等。-非GUI对象的基础(如QTimer、QThread等)。-没有可视化表示。-**QWidget**:-继承QObject的所有功能。-添加了窗口部件(Widget)的功能:绘制窗口、处理用户输入(鼠标、键盘等)、样式管理、布局管理等。-是用户界面的基本组成单元(如按钮、窗口、对话框等)。3.**使用场景**:-当需要创建一个带有图形界面的组件时,使用QWidget(或它的子类)。-当需要创建一个非可视的后台对象(如管理数据或逻辑)时,使用QObject。4.**对象树和内存管理**:-两者都支持对象树:当父对象被销毁时,会自动销毁所有子对象。-对于QWidget,还存在额外的窗口树:子QWidget通常位于父QWidget的坐标系内,并随父窗口一起显示或隐藏。5.**事件处理**:-QObject提供基本的事件处理(`event()`函数和`installEventFilter`方法)。-QWidget扩展了事件处理,增加了与界面相关的事件(如绘图事件、鼠标事件、窗口事件等)。###代码示例:创建和使用QObject与QWidget```cpp//示例1:纯QObject的使用(非可视对象)classDataManager:publicQObject{Q_OBJECTpublic:explicitDataManager(QObject*parent=nullptr):QObject(parent){}voidloadData(){/*...*/}};//示例2:QWidget的使用(可视窗口部件)classMyWidget:publicQWidget{Q_OBJECTpublic:explicitMyWidget(QWidget*parent=nullptr):QWidget(parent){//初始化界面QPushButton*button=newQPushButton("Clickme",this);connect(button,&QPushButton::clicked,this,&MyWidget::handleClick);}privateslots:voidhandleClick(){/*...*/}};//示例3:在应用程序中使用intmain(intargc,char*argv[]){QApplicationapp(argc,argv);//创建QObject树DataManagermanager;//...使用manager//创建QWidget树MyWidgetmainWindow;mainWindow.show();returnapp.exec();}```###注意事项-每个QtGUI应用程序必须有一个QApplication实例(也是QObject),它负责事件循环和系统通信。-QWidget只能在主线程中使用(GUI相关操作必须发生在主线程)。-使用QObject时,如果需要在不同线程中使用,需使用信号槽进行线程间通信(注意:QObject及其子类具有线程亲和性)。###常见错误1.**在非主线程中操作QWidget**:这会导致未定义行为或崩溃。解决方案:使用信号槽将操作发送到主线程。2.**混淆父子关系**:如果将一个QWidget的父对象设置为一个非QWidget的QObject(如QObject),则可能无法正确显示。因为QWidget需要QWidget作为父对象才能正确设置窗口系统属性。---§§相关问题§§:1.QObject的元对象系统(Meta-ObjectSystem)是什么?2.信号槽机制如何实现线程间通信?3.QWidget和QWindow有什么区别?4.如何正确创建一个无父窗口的顶级QWidget?</think> ### QWidget与QObject:核心区别与联系 在Qt框架中,**QObject**和**QWidget**是核心基础类,它们的关系体现了Qt的对象模型和GUI框架设计: #### 1. **继承关系** ```cpp // 核心继承链 class QObject { /* ... */ }; // 基础对象类 class QPaintDevice { /* ... */ }; // 绘图设备(非直接继承) class QWidget : public QObject, public QPaintDevice { /* ... */ }; // 窗口部件 ``` #### 2. **核心功能对比表** | 特性 | QObject | QWidget | |--------------------------|--------------------------------|--------------------------------------| | **核心目的** | Qt对象模型的基础 | 可视化窗口部件的基础 | | **内存管理** | 支持对象树(parent-child) | 继承对象树 + 窗口系统资源管理 | | **信号/槽机制** | ✅ (核心功能) | ✅ (继承自QObject) | | **事件处理** | 基础事件系统(event()) | 完整GUI事件系统(鼠标/键盘/绘图等) | | **可视化能力** | ❌ | ✅ (支持渲染和布局) | | **坐标系统** | ❌ | ✅ (几何尺寸、位置、变换) | | **样式控制** | ❌ | ✅ (QSS样式表支持) | #### 3. **内存管理差异** ```cpp // QObject的父子关系管理 QObject* parent = new QObject; QObject* child = new QObject(parent); // 自动管理生命周期 // QWidget的额外窗口管理 QWidget* window = new QWidget; QPushButton* btn = new QPushButton(window); // 同时是子对象和子窗口 btn->setAttribute(Qt::WA_DeleteOnClose); // 窗口关闭时自动删除 ``` #### 4. **事件处理对比** ```cpp // QObject基础事件处理 class MyObject : public QObject { protected: bool event(QEvent *e) override { if(e->type() == QEvent::User) { handleCustomEvent(); return true; } return QObject::event(e); } }; // QWidget完整GUI事件处理 class MyWidget : public QWidget { protected: void mousePressEvent(QMouseEvent *e) override { qDebug() << "Clicked at:" << e->pos(); } void paintEvent(QPaintEvent *) override { QPainter painter(this); painter.drawText(rect(), "Hello Qt"); } }; ``` #### 5. **使用场景原则** - **使用QObject当**: - 需要非可视化后台对象(如数据模型) - 实现信号/槽通信 - 管理对象生命周期(通过父子关系) - 创建轻量级服务(如网络请求、定时器) - **使用QWidget当**: - 需要可视化UI组件 - 处理用户输入事件 - 管理窗口布局和渲染 - 需要样式控制(QSS) - 实现自定义绘制 #### 6. **常见误用与修正** ```cpp // 错误:尝试直接在QObject上使用可视化方法 QObject obj; obj.resize(100, 50); // 编译错误!QObject无resize方法 // 正确:使用QWidget派生类 class CustomWidget : public QWidget { public: explicit CustomWidget(QWidget* parent = nullptr) : QWidget(parent) { resize(100, 50); } }; ``` ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值