Qt - 进阶

框架

全局定义

<QtGlobal> 包含框架的一些全局定义:基本数据类型、全局函数、宏

<QtGlobal>中的宏定义

QT_VERSION // qt版本
Q_LITTLE_ENDIAN // 小端字节序
foreach()

元对象系统

元对象系统

QObject类时所有使用元对象系统的类 的基类
必须在类的开头插入宏Q_OBJECT,这样这个类才可以使用元对象系统的特性
MOC为每个QObject的子类提供必要的diamante来实现元对象系统的特性
MOC的作用:构建项目时,MOC会读取C++源文件,当它发现类的定义里有Q_OBJECT宏时,它就会为这个类生成另一个包含元对象元对象支持代码的C++源文件,这个生成的源文件连同类的实现文件一起被标准C++编译器编译了链接

元对象系统特性

元对象(元对象系统的特性是通过QObject的一些函数实现的)
类型信息
动态翻译
对象树
信号与槽
属性系统

QObject / QMetaObject

元对象meta-object:每个QObject及其子类的实例都有一个元对象,这个元对象是自动创建的
元对象是QMetaObject类型的实例

元对象:元对象存储了 类的实例 所属类的各种元数据,包括类信息元数据、方法元数据、属性元数据
元对象实际上是对类的描述

通过QMetaObject类的一系列函数,可以在运行时获取一个QObject对象的类信息和各种元数据

QMetaObject

QObject

QObject类时所有使用元对象系统的类 的基类
如果一个类的父类或上层父类是QObject,就可以使用信号与槽、属性等特性

QObject与元对象系统

QObject不等于元对象系统
元对象系统的特性是通过QObject的一些函数实现的

// 元对象 相关
QMetaObject *metaObject()
QMetaObject staticMetaObject()

// 类型
bool inherits()

// 对象树 相关
QObjectList &children()
QObject *parent() 
void setParent()
T findChild()
QList<T> findChildren()

// 信号与槽 相关
QMetaObject::Connection connect()
bool disconnect()
bool blockSignals()
bool signalsBlocked() 

// 属性系统 相关
QList<QByteArray> dynamicPropertyNames()
bool setProperty()
QVariant property()

例子

// 返回类名称
QPushButton *btn=new QPushButton();
const QMetaObject *meta=btn->metaObject(); // 获取该对象的元对象指针
QString str=QString(meta->className()); // "QPushButton"
qDebug()<<str; // "QPushButton"

// 判断继承
qDebug()<<btn->inherits("QPushButton"); // true
qDebug()<<btn->inherits("QObject"); // true     // 如果一个类时多继承的,其中一个类时QObject,则inherits("QObject") true
qDebug()<<btn->inherits("QWidget"); // true
qDebug()<<btn->inherits("QLabel"); // false


// 对象的元对象所描述类 的父类的元对象
const QMetaObject *metaSuper=btn->metaObject()->superClass();
str=QString(metaSuper->className()); // "QAbstractButton"
qDebug()<<str; // "QAbstractButton"


// 对象类型转换
QObject *btn2=new QPushButton(); // 基类指针指向子类对象
const QMetaObject *metaBtn=btn2->metaObject();
qDebug()<<QString(metaBtn->className()); // "QPushButton" !!!

QPushButton *btnPtr=qobject_cast<QPushButton*>(btn2);
const QMetaObject *metaBtn2=btnPtr->metaObject();
qDebug()<<QString(metaBtn2->className()); // "QPushButton"

对象树

对象树指的是表示对象间从属关系的树状结构
当对象树中的某个对象被删除时,它的子对象会被自动删除

在 Qt 中,对象树是一种组织 QObject 的方式,它是一种自上而下的层次结构。在对象树中,QObjects 可以成为其它 QObjects 的父对象或子对象。一个 QObject 的父对象是指它的直接上一级 QObject,而子对象是指它的直接下一级 QObject。

当我们添加一个 QObject 的子对象时,它便会被添加到其父对象的子对象列表中,而从父对象中删除子对象时,它也会从子对象列表中删除。

对象树在很多场景中都非常有用。比如,我们可以使用对象树来控制在程序退出时自动删除对象、在父对象被删除时自动删除子对象等。

QObject 中提供了一些与对象树相关的函数:
QObject::parent() 返回对象的父对象。
QObject::setParent() 设置对象的父对象,它将自动将对象添加到父对象的子对象列表中,并从之前的父对象中删除。
QObject::child() 返回子对象列表中的一个 QObject。
QObject::children() 返回子对象列表中的所有 QObject。
同时,QObject 还有一个名为 deleteLater() 的函数,该函数将对象的删除事件放入待处理事件队列中,以便在稍后的某个时间触发删除事件。

在使用 QObject 的时候,我们应该尽可能地使用对象树,以便正确地管理对象的生命周期。同时,我们需要注意,在有些情况下,对象的所有权可能会发生变化或者对象会从对象树中移除,这也需要引起我们的注意。

对象树与类继承

对象树:父对象和子对象,主要用于内存管理,父对象析构,先析构子对象
对象树和继承关系没有必然的联系

例子

QObjectList objList=ui->xxx.children();

for (int i=0;i<objList.size();i++)
{
	const QMetaObject *meta=ObjList.at(i)->metaObject(); // 获得QObject对象的元对象
	QString className=QString(meta->className());
	if (className=="xxx")
	{
		// set property...
	}
	
}

findChild / findChildren

// 根据对象名称,在对象的子对象中查找可以转换为类型T的子对象
QPushButton *btn=this->findChild<QPushButton *>("btnOK");

// 返回所有,或指定对象名称的子对象
QList<QPushButton *> btnList=this->findChildren<QPushButton *>();

deletelater

deleteLater() 是 Qt 中的一个重要概念。 在 Qt 中,当我们创建QObject的子类对象时,这些对象可以相互通信。但我们删除这些对象时,我们需要小心谨慎,以确保没有内存泄漏。如果我们使用 delete 运算符直接删除一个QObject及其子对象,则可能会导致程序崩溃或未定义的行为。这主要是由于QObject的工作原理,它使用一个对象树来跟踪所有子对象。因此,在删除对象时,我们需要知道其父对象。如果我们直接删除子对象,那么其父对象将无法知道其子对象已被销毁,可能会尝试访问已删除的对象。

为了解决这个问题,deleteLater() 函数被引入到QObject类中。 这个函数可以让我们告诉Qt,在稍后的某个时间点删除对象。 它将删除对象的内存空间,并确保不会修改正在进行的任何任务或事件循环。 我们可以通过简单调用 deleteLater() 函数来删除QObject的子类对象:

MyObject *obj = new MyObject(parent);
// ...
obj->deleteLater(); // 需要删除 obj

请注意,deleteLater() 函数实际上不是立即删除对象,而是将该对象的删除事件放入待处理事件队列中。因此,我们在使用 deleteLater() 函数时需要注意,在队列中是否有等待处理的事件,因为删除对象的事件将在处理完队列中的现有事件后才会被触发。

总之,deleteLater() 函数是一种关闭Qt程序时安全释放对象内存的一种简单、可靠而安全的方式,因此,在使用QObject实例时,我们应该总是使用deleteLater()来删除对象。

属性系统

用宏Q_PROPERTY定义属性,QObject的setProperty()函数会设置属性的值,或定义动态属性,property()函数返回属性的值

附加类信息

使用宏Q_CLASSINFO() 在类中定义一些类信息,类信息有名称和值,值只能用字符串表示

class MyCLass:public QObject
{
	Q_OBJECT
	Q_CLASSINFO("author","wang")
	Q_CLASSINFO("company","TJU")
	Q_CLASSINFO("author","wang")
	Q_CLASSINFO("VERSION","3.0.1")
	...
};

// 使用QMetaObject的一些函数可以获取类信息元数据,
char *QMetaObjectInfo::name() // 返回类信息的名称
char *QMetaObjectInfo::value() // 返回类信息的值

事件

事件类和事件类型

当应用程序收集事件时,Qt会将该事件发送到相应的对象进行处理,最终由事件接受者处理或丢弃该事件
每个事件都是QEvent类派生出的一个类对象(事件是对象),可以通过QObject::event()函数来进行发送和处理
如QKeyEvent,QMouseEvent,QPaintEvent,QTimerEvent

一个具体的事件是QEvent类或其派生类的实例
QEvent是所有事件类的基类,且不是一个抽象类,可以用于创建事件
每个事件都有一个唯一的事件类型,也有对应的事件类
有的事件类,可以处理多种类型的事件:QMouseEvent事件类,同他创建的事件的类型可以是QEvent::MouseButtonDblClick或QEvent::MouseMove等

void accept() // 接受事件,设置事件对象的接受标志 accept flag
void ignore() // 忽略事件,清除事件对象的接受标志
bool isAccepted() // 是否接受事件,true表示接受,false表示忽略
bool isInputEvent() // 事件对象是不是QInputEvent或其派生类的实例
bool isPointerEvent() // 事件对象是不是QPointerEvent或其他派生类的实例
bool isSinglePointEvent() // 事件对象是不是QSinglePointerEvent或其他派生类的实例
bool spontaneous() // 是不是自生事件,也就是窗口系统或底层操作系统事件
QEvent::Type type() // 事件类型

事件类与事件类型

我的理解,产生一个事件,该事件属于一个具体的事件类型(一个枚举类型),而要是想处理该事件类型及其对应的时间,就要利用与之对应的事件类来处理

例子

mouseMoveEvent(QMouseEvent *event) 是 QWidget 类的成员函数,它是用于处理鼠标移动事件的函数。当鼠标在该 QWidget 上移动时,该函数会被调用
QEvent::MouseMove 是一个事件类型,它表示鼠标移动事件。当鼠标在 QWidget 上移动时,该事件会被创建并发送到 QWidget 对象的事件队列中
在 QWidget 对象中,当一个鼠标移动事件被创建并发送到事件队列中时,该对象会自动调用 mouseMoveEvent(QMouseEvent *event) 函数,并借助QMouseEvent类来处理该事件

class MyWidget : public QWidget
{
public:
    MyWidget(QWidget *parent = nullptr) : QWidget(parent) {}

protected:
    void mouseMoveEvent(QMouseEvent *event) override
    {
        // 鼠标移动事件处理
        QPoint pos = event->pos();  // 获取鼠标位置
        Qt::MouseButton button = event->button();  // 获取鼠标按键
        Qt::KeyboardModifiers modifiers = event->modifiers();  // 获取键盘修饰键
        // 其他处理代码...
    }

    void mousePressEvent(QMouseEvent *event) override
    {
        // 鼠标按下事件处理
        QPoint pos = event->pos();  // 获取鼠标位置
        Qt::MouseButton button = event->button();  // 获取鼠标按键
        Qt::KeyboardModifiers modifiers = event->modifiers();  // 获取键盘修饰键
        // 其他处理代码...
    }
};

事件产生 / 来源

自生事件

由窗口系统产生的事件
如,QKeyEvent,QMouseEvent事件
自生事件会进入系统队列,然后被应用程序的事件循环逐个处理

发布事件

由Qt或应用程序产生的事件,应用程序使用静态函数QCoreApplication::postEvent()产生发布事件;发布事件会进入Qt事件队列,然后被应用程序的事件循环进行处理
如,QTimer定时器发生定时溢出时,Qt会自动发布QTimerEvent事件

发送事件

由Qt或应用程序定向发送给某个对象的时间,应用程序使用静态函数QCoreApplication::sendEvnet()产生发送事件,由对象的event()函数直接处理

事件循环 event loop

Qt事件循环检测当前的消息队列并等待事件的发送,如果在消息队列中有一个事件,那么它就会从队列中移除该事件并交给适当的接受者进行处理
事件循环还可以接受新的事件并将其添加到消息队列中,直到应用程序关闭

事件循环,是一个无限循环,负责从事件队列中取出事件并执行,直到程序退出,每次事件循环的执行,都会从事件队列中取出一个事件,然后执行相应的槽函数或处理事件

事件发送器 event sender

事件发送器负责想接收器发送事件(QWidget::mouseMoveEvent()将鼠标移动事件发送给接收方Widget)

事件过滤器 event filter

事件过滤器允许在事件到达接受者之前截取和修改它

从下面的事件处理过程 - 分派与传播 - 自定义类例子中中,一个界面组件如果要对事件进行处理,需要从父类继承定义一个新类,在新类中编写程序直接处理事件,或者将事件转换为信号

如果不想定义一个新类,可以用事件过滤器对界面组件的事件进行处理,它可以将一个对象的事件委托给另一个对象来监视并处理
如,一个窗口可以作为其界面上的QLabel组件的事件过滤器,派发给QLabel组件的时间由窗口去处理,这样就不需要为了处理某件事件而新定义一个标签类

被监视对象使用installEventFilter()函数将自己注册给监视对象,监视对象就是事件过滤器
监视对象重新实现eventFilter()函数,对监视到的事件进行处理
值得注意的是,必须得有组件使用了installEventFilter函数,否则,如果单纯在widget类中使用eventFilter函数并不会进行调用

// 被监视对象调用该函数,将filterObj对象设置为自己的事件过滤器
void QObject::installEventFilter(QObject *filterObj); 

// 作为事件过滤器的监视对象重新实现该函数,watched是被监视对象,event是产生的事件
// 返回true表示事件不会再传播给其他对象,事件处理结束
// 返回false表示事件会继续传给事件接收者做进一步处理
bool QObject::eventFilter(QObject *watched,QEvent *event); 

例子1

class MyEventFilter : public QObject
{
public:
    MyEventFilter(QObject *parent = nullptr) : QObject(parent) {}

protected:
    bool eventFilter(QObject *obj, QEvent *event) override
    {
        if (event->type() == QEvent::MouseButtonPress) {
            QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
            if (mouseEvent->button() == Qt::LeftButton) {
                qDebug() << "Left mouse button clicked!";
                return true; // Event handled
            }
        }

        // Pass the event on to the parent class
        return QObject::eventFilter(obj, event);
    }
};

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QWidget window;
    window.installEventFilter(new MyEventFilter(&window));
    window.show();

    return app.exec();
}

例子2

ui->label->installEventFilter(this);

bool Widget::eventFilter(QObject *watched,QEvent *event)
{
    if (watched==ui->label)
    {
        if (event->type()==QEvent::MouseButtonPress)
        {
            qDebug()<<"eventfilter func";
            return true; // 范式
        }
    }

    return QWidget::eventFilter(watched,event); // 范式

}

bool Widget::event(QEvent *event)
{
    if (event->type()==QEvent::MouseButtonPress)
    {
        qDebug()<<"evet func single click";
//        return true;
        event->accept(); // 这里event->accept与return true等价
    }
    else
    {
        return QWidget::event(event);
    }

}

void Widget::mousePressEvent(QMouseEvent *event)
{
    qDebug()<<"mousePress event func";
}

事件处理过程

任何从QObject派生的类都可以处理事件

event() / accept() / ignore()

一个类(可以是从QWidget派生的窗口类和界面组件类)接收到应用程序派发来的事件后,首先会调用event()函数处理
任何从QObject派生的类都可以重新实现函数event(),以便在接收到事件时进行处理
如果一个类重新实现了函数event(),需要在该函数的实现代码中设置是否接受事件;accept() 表示事件接收者会对事件进行处理,ignore()表示事件接收者不接收此事件
被接受的事件有事件接收者处理,被忽略的事件则传播到事件接收者的父容器组件,由父容器组件的event()函数处理,这叫事件的传播,(事件最终可能会传播给窗口),如果该事件经过多个对象处理后最终未被处理,则它将被忽略并从消息队列中删除

// 虚函数
bool QObject::event(QEvent *e)
{
	e->type(); // QEvent::Type type() // 可以得到事件的具体类型
}

例子

bool event(QEvent* e) override {
    if (e->type() == QEvent::MouseButtonPress) {
        qDebug() << "鼠标按下";
        e->accept(); // 消费事件,不传递给父级
        return true;
    }
    else if (e->type() == QEvent::MouseButtonRelease) {
        qDebug() << "鼠标释放";
        e->ignore(); // 不消费事件,传递给父级
        return false;
    }
    else {
        return QWidget::event(e); // 其他事件交给父类处理
    }
}

注意下面的例子,并不是在event函数中处理的,而是具体的事件处理函数中处理的

// 左键按下,事件会被接受并进行处理
// 否则,事件会被忽略并传递给父类的mousePressEvent函数进行处理

void Widget::mousePressEvent(QMouseEvent *event)
{
	if (event->button()==Qt::LeftButton)
	{
		// do something
		event->accept();
	}
	else
	{
		QWidget::mousePressEvent(event);
	}
}
event() 与具体事件处理函数

如果是标准事件类型,那么可以不重新实现event()函数
如果需要处理的事件在QWidget中没有定义事件处理函数,就需要重新实现event()函数,判断事件类型后调用自定义的事件处理函数

应用程序派发给界面组件的时间首先会由其他函数event()处理,如果函数event()不做任何处理,组件就会自动调用QWidget中与事件类型对应的默认事件处理函数

拦截
bool Widget::event(QEvent *event)
{
    if (event->type()==QEvent::MouseButtonPress)
    {
        QMouseEvent *btn=static_cast<QMouseEvent *>(event);
        if (btn->button()==Qt::LeftButton)
        {
            static int num=0;
            num++;
            qDebug()<<num<<" mouse press int event func";
//            return true; // return true 和event->accept 是一样的效果
            event->accept(); // 注意使用event->accept必须与else结合使用,否则逻辑不对
        }

    }
    else
    {
        return QWidget::event(event);
    }

}

void Widget::mousePressEvent(QMouseEvent *event)
{
    qDebug()<<"mouse press event func";
}
分派与传播 – 自定义类

另外,从下面的例子中可以看出,事件是直接分派到对应的对象上,然后在向上级(父类对象)传播

在下面这个例子中
label和widget都重新实现了mouseDoubleClickEvent时,双击窗口和双击label会调用不同的mouseDoubleClickEvent函数
如果只有widget重新实现了mouseDoubleClickEvent时,由于label没有重新实现,则会传播给父类,最终传播给widget,因此双击窗口和双击label调用的都是widget类中的mouseDoubleClickEvent函数
如果label中没有定义event事件,只实现了mouseDoubleClickEvent,双击label会直接进入到mouseDoubleClickEvent中

MyLabel类

.h

#ifndef TMYLABEL_H
#define TMYLABEL_H

#include <QObject>
#include <QWidget>
#include <QLabel>
#include <QEvent>
#include <QMouseEvent>

class TMyLabel : public QLabel
{
    Q_OBJECT
public:
    explicit TMyLabel(QWidget *parent = nullptr);
signals:
    void doubleClicked();
protected:
    bool event(QEvent *event);
    void mouseDoubleClickEvent(QMouseEvent *event);

};

#endif // TMYLABEL_H

.cpp

#include "tmylabel.h"

TMyLabel::TMyLabel(QWidget *parent)
    : QLabel{parent}
{
    /*
        在Qt中,控件默认情况下是不支持hover事件的,需要调用setAttribute()函数来开启hover事件的支持。setAttribute()函数是QWidget类的成员函数,用于设置控件的属性。

        this->setAttribute(Qt::WA_Hover, true);这句代码的作用是开启当前控件的hover事件支持。当这个控件被鼠标悬停时,将会触发hover事件。

        注意,对于一些特殊的控件,例如QLabel和QLineEdit,它们默认已经开启了hover事件支持,因此不需要调用setAttribute()函数来开启。
        在Qt中,控件默认情况下是不支持hover事件的,需要调用setAttribute()函数来开启hover事件的支持。setAttribute()函数是QWidget类的成员函数,用于设置控件的属性。

        this->setAttribute(Qt::WA_Hover, true);这句代码的作用是开启当前控件的hover事件支持。当这个控件被鼠标悬停时,将会触发hover事件。

        注意,对于一些特殊的控件,例如QLabel和QLineEdit,它们默认已经开启了hover事件支持,因此不需要调用setAttribute()函数来开启。
    */
    this->setAttribute(Qt::WA_Hover,true);
}

bool TMyLabel::event(QEvent *event)
{
    if (event->type()==QEvent::MouseButtonDblClick)
    {
        qDebug()<<"mylabel class event func";
    }
    
    if (event->type()==QEvent::HoverEnter)
    {
        QPalette plet=this->palette();
        plet.setColor(QPalette::WindowText,Qt::red);
        this->setPalette(plet);
    }
    else if (event->type()==QEvent::HoverLeave)
    {
        QPalette plet=this->palette();
        plet.setColor(QPalette::WindowText,Qt::black);
        this->setPalette(plet);
    }

    // 必须写上这句话,因为自定义event函数中只对两个事件做了处理,对于其他典型事件,还需要交给父类去处理
    return QLabel::event(event);
}

void TMyLabel::mouseDoubleClickEvent(QMouseEvent *event)
{
    qDebug()<<"label mouse double clicked";
    Q_UNUSED(event);
    emit doubleClicked();
}

Widget类

.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QMouseEvent>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;

protected:
    void mouseDoubleClickEvent(QMouseEvent *event);
	bool event(QEvent *event);
private slots:
    void do_doubleClick();
};
#endif // WIDGET_H

.cpp

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    connect(ui->label,&TMyLabel::doubleClicked,this,&Widget::do_doubleClick);

}

Widget::~Widget()
{
    delete ui;
}

bool Widget::event(QEvent *event)
{
    if (event->type()==QEvent::MouseButtonDblClick)
    {
        qDebug()<<"widget class event func";
    }


    return QWidget::event(event);
}

void Widget::mouseDoubleClickEvent(QMouseEvent *event)
{
    Q_UNUSED(event);
    qDebug()<<"win mouse double clicked";
    ui->label->setText("窗口被双击了");
    ui->label->adjustSize();
}

void Widget::do_doubleClick()
{
    ui->label->setText("label被双击了,槽函数响应");
    ui->label->adjustSize();
}


常用事件处理函数示例
  • paintEvent 在窗口或控件需要重绘时
  • closeEvent 窗口关闭
  • mousePressEvent
  • keyPressEvent
  • showEvent 与窗口有关
  • hideEvent 应用程序最小化或窗口关闭的时候
例子

.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QCloseEvent>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

protected:
    void paintEvent(QPaintEvent *event); // 在窗口需要重绘时,应用程序会想窗口发送QEvent::paint类型事件,窗口对象自动执行paintEvent函数
    void closeEvent(QCloseEvent *event);
    void keyPressEvent(QKeyEvent *event);
    void showEvent(QShowEvent *event);
    void hideEvent(QHideEvent *event);
    void mouseEvent(QMouseEvent *event);

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

.cpp

#include "widget.h"
#include "ui_widget.h"

#include <QPainter>
#include <QMessageBox>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    // 最后才是析构函数,在closeEvent之后
    qDebug()<<"destructor";
    delete ui;
}

void Widget::paintEvent(QPaintEvent *event)
{
    qDebug()<<"paint event";
    /*
        这是一个C++中常见的语句,表示当前变量 event 没有被使用,可以忽略此变量,避免编译器产生警告信息。通常在函数参数中定义了某个参数,但是在函数的实现中没有用到这个参数时,会使用这个语句来避免编译器警告。
    */
    Q_UNUSED(event);

    QPainter painter(this);
    painter.drawPixmap(0,0,this->width(),this->height(),QPixmap("./BG2.jpg"));

    QWidget::paintEvent(event);
}

void Widget::keyPressEvent(QKeyEvent *event)
{

}

void Widget::showEvent(QShowEvent *event)
{
    /*
        先调用showEvent,在调用PaintEvent
        值得注意的是,是有与最顶层窗口有关的时候,才会执行showEvent,与控件无关
        而paintevent,窗口控件的显示与更新都会触发这个事件
    */
    Q_UNUSED(event);
    qDebug()<<"show event";

}

void Widget::hideEvent(QHideEvent *event)
{
    // 应用程序最小化或窗口关闭的时候,都会触发该函数
    // 先触发closeEvent再触发hideEvent函数
    Q_UNUSED(event);
    qDebug()<<"hide event";
}

void Widget::mouseEvent(QMouseEvent *event)
{
	/*
		> pos()函数返回的是相对于当前控件的坐标;
		> position()函数返回的是相对于当前控件的坐标,但是如果鼠标事件来自于一个子控件,那么它返回的坐标相对于子控件的坐标;
		> scenePosition()函数返回的是相对于场景的坐标,也就是说它返回的是绝对坐标。
		因此,这三个函数的区别在于返回的坐标的参考系不同
	*/
    if (event->button()==Qt::LeftButton)
    {
        QPoint pt=event->pos(); // 在窗口上的相对坐标
        QPointF relaPt=event->position(); // 相对坐标
        QPointF winPt=event->scenePosition(); // 相对坐标
        QPointF globPt=event->globalPosition(); // 屏幕或虚拟桌面上的绝对坐标
    }
}

void Widget::closeEvent(QCloseEvent *event)
{
    qDebug()<<"close event";

    QString title="消息框";
    QString str="是否退出";
    QMessageBox::StandardButton result=QMessageBox::question(this,title,str,
                                                             QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel);

    if (result==QMessageBox::Yes)
    {
        event->accept(); // 接受事件,窗口可以关闭
    }
    else if (result==QMessageBox::No)
    {
        event->ignore(); // 忽略事件,窗口不可以关闭,当前对象不接受该事件,事件被传播到父容器(父对象),但是该窗口已经没有父容器,所以会忽略该事件
    }
    else
    {
        QWidget::closeEvent(event); // 要想真的忽略就不能再次调用该函数,否则相当于没有忽略,事件被传到了当前父类的函数中
    }


    /*
        注意:
        在重写虚函数QWidget::closeEvent()时,如果没有调用event->accept()或者event->ignore()函数,那么默认情况下,事件会被接受并继续进行处理,也就是会自动关闭窗口。

        这是因为,在Qt中,窗口关闭事件是一个特殊的事件,它的处理方式与其他事件不同。当窗口接收到关闭事件时,Qt会自动调用QWidget::closeEvent()函数来处理该事件,而不需要显式地调用event->accept()或者event->ignore()函数。在QWidget::closeEvent()函数内部,会处理窗口关闭事件,包括释放资源、发送信号、更新窗口状态等。

        但是,建议在重写QWidget::closeEvent()函数时,显式地调用event->accept()或者event->ignore()函数,以确保代码的清晰性和可读性。同时,如果你想要在关闭窗口前进行一些额外的操作,比如弹出提示框,需要在调用event->accept()或者event->ignore()函数之前进行操作,确保你的操作会在窗口关闭前完成。
    */
}

事件 / 信号槽

关系

信号槽机制和事件机制都用于处理对象间的通信与交互
一个对象可以在接收到一个事件后发出一个信号,其他对象可以连接到这个信号并执行相应的操作
当一个信号被触发时,它会被加入到事件队列中,等待事件循环处理,当事件循环处理到这个信号时,他会调用所连接到这个信号的槽函数

区别

信号槽机制是一种基于事件的模型,用于处理对象间的异步通讯
信号槽是一种解耦机制,信号对象只需要发出信号,不需要直到哪些对象会响应信号
信号槽机制可以跨线程使用

事件机制是一种基于消息的模型,用于处理对象间的同步交互,当一个对象接收到一个事件时,会根据事件的类型和属性来处理这个事件
事件机制是一种耦合机制,事件对象需要直接处理事件,并且需要直到哪些对象会接收事件
事件机制一般只能在同一线程中使用 ,这是因为Qt的事件处理系统是基于线程的,每个线程都有一个事件循环和事件队列,因此事件只能在其所属的线程中被处理,(一般来说,GUI事件,如鼠标点击事件等只能在主线程中触发,而不能在子线程中触发)

补充说明

事件通常是由窗口系统或应用程序产生的
信号则是qt或用户自定义的
qt为界面组件定义的信号通常是对事件的封装,如QPushButton的clicked()信号可以看做对QEvent::MouseButtonRelease类型事件的封装,但是qt的界面组件只将少数事件封装成了信号

从事件过滤角度看
下面的例子中点击button按钮,被拦截了,不会触发clicked信号
**例子 **

#include <QApplication>
#include <QPushButton>
#include <QMouseEvent>
#include <QDebug>

class EventFilter : public QObject
{
public:
    bool eventFilter(QObject* obj, QEvent* event) override
    {
        if (event->type() == QEvent::MouseButtonPress)
        {
            QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
            if (mouseEvent->button() == Qt::LeftButton)
            {
                qDebug() << "Mouse button pressed";
                return true; // 拦截点击事件
            }
        }
        return QObject::eventFilter(obj, event);
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QPushButton button("Click me");
    button.installEventFilter(new EventFilter());

    QObject::connect(&button, &QPushButton::clicked, [](){
        qDebug() << "Button clicked";
    });

    button.show();
    return a.exec();
}

信号 / 槽

关联

在槽函数中可以访问发送信号的对象,利用sender()函数,这是Qt信号-槽机制的关键特性之一,当一个连接的信号触发时,可以为槽函数提供一个指向发送信号对象的指针,即sender()函数返回对象,这样槽函数就可以使用这个指针来访问信号发送者对象的属性和方法,(如,QPushButton* clickedButton = qobject_cast<QPushButton*>(sender());
sender是QObject类的一个protected保护函数,在一个槽函数中调用sender函数可以获得信号发射者的QObject对象指针,进而可以修改发送者的属性等

例子

QObject::connect(button1, &QPushButton::clicked, this, &ExampleClass::handleButtonClicks);

QObject::connect(button2, &QPushButton::clicked, this, &ExampleClass::handleButtonClicks);

...

void ExampleClass::handleButtonClicks()
{
    QPushButton* clickedButton = qobject_cast<QPushButton*>(sender());
    if (clickedButton == button1)
    {
        qDebug() << "Button 1 is clicked!";
    }
    else if (clickedButton == button2)
    {
        qDebug() << "Button 2 is clicked!";
    }
}

连接类型(关联方式)

Qt::AutoConnection
Qt::DirectConnection
Qt::QueuedConnection
Qt::BlockingQueuedConnection

Qt::AutoConnection

默认值,如果信号接收者和发送者者在一个线程中,使用Qt::DirectConnection,如否则使用Qt::QueuedConnection方式
Qt::AutoConnection在信号发射时自动确定关联方式

Qt::DirectConnection

信号被发射时,槽函数立即运行,槽函数与信号在同一个线程中

直接连接存在的问题

直接连接下,qt信号槽机制是同步的,也就是一个信号发射后,槽函数立刻执行,如果槽函数执行过长,会阻塞当前线程(因为直接连接需要信号槽在一个线程中),从而导致界面无法响应用不操作
界面更新时通过事件循环机制来实现的,当一个槽函数执行事件过长时,会持续占用事件队列,导致其他事件无法及时处理,从而影响界面的流畅度

因此,应该尽量避免在槽函数中执行耗时的操作,如文件读写、网络请求等,如果执行这些操作可以将他们放在单独的线程中执行,从而避免阻塞主线程
还可以使用异步调用的方式,QtConcurreng、QThread等方法

Qt::QueuedConnection

在事件循环回到接收者线程后运行槽函数,槽函数与信号在不同的线程中
当信号被发射时,槽函数不会立即执行,而是将其封装成一个事件并放入到接收对象的事件队列中,等待接收对象处理完自己的事件后再执行槽函数,也就是等待下一次事件循环执行

具体来说,当使用Qt::QueuedConnection 连接方式时,发出信号的对象会将槽函数的参数封装成一个事件,并将事件添加到接收信号对象所在的线程的事件队列中,事件循环会从事件队列中取出事件并执行,保证了槽函数的执行是在接收信号对象所在的线程汇总完成的
注意:队列连接时,槽函数的执行是异步的,即槽函数的执行时机是不确定的
如果需要等待槽函数执行完成后在执行下一步操作,需要使用QEventLoop或者QFuture等机制来实现

每次事件循环的执行,都会从事件队列中取出一个事件,然后执行相应的槽函数或处理事件
但是注意的是,在信号和槽的执行过程中,也有可能会有其他的时间被胶乳到事件队列中,如定时器事件、鼠标事件等,如果在槽函数的执行过程中,有其他事件被加入到事件队列中,那些事件也会在下一次事件循环中被执行

队列连接的优点:保证槽函数的执行不会阻塞信号发射者,从而提高程序的响应速度和稳定性;此外,队列连接还可以保证槽函数的执行顺序和信号发射的顺序一致,避免了多线程变成中竞态条件和死锁问题

队列连接使用注意:
槽函数需要是线程安全的,因为他们可能会在不同的线程中执行
槽函数的执行可能会被延迟,因此不能依赖于执行顺序
信号和槽函数的参数类型必须一致,否则会导致编译错误
队列连接可能会导致事件积压,从而增加系统负担,因此应该尽量减少队列连接的使用

Qt::BlockingQueuedConnection

与Qt::QueuedConnection相似,区别是喜好函数会阻塞,直到槽函数运行完毕,当信号与槽函数在同一个线程中,绝对不能使用这种方式,否则会造成死锁

传参

信号槽机制内部使用了QVariant存储信号参数,因此可以接受各种数据类型
实际上,信号和槽函数直接的连接采用的是“松耦合”的模式,也就是说,信号和槽之前不直接传递参数,而是将信号和槽函数分别放在不同的对象中,通过信号槽机制相连
在使用QObject::connnect()函数将信号和槽连接时,Qt将生成一个中间对象QMetaObject::Connnect来维护信号槽之间的关联关系,并将这个对象的指针返回给调用者,这个中间对象会一直存在,直到信号槽之间的连接断开

例子: 在非主函数(main函数)中创建一个指针,此时该指针为局部变量,具有局部作用域,然后用该指针指向一个具有局部作用域(在栈区)创建的(整型)数组,然后将该指针作为参数,以信号槽的方式进行传递,
这个例子中,虽然传递的是指针参数,单并没有直接传递指针本身,而是在信号槽之间建立了中间对象,这个中间对象可以保证所指向的局部变量在信号槽被调用时仍然存在
但是需要注意的是,在局部变量作用域结束后,该变量被释放掉了,如果在信号槽之外访问这个变量,就会访问到已经释放的内存,从而到最后错误

因此在利用信号槽机制传递指针参数时,注意变量的作用域和生命周期

传递指针/数组

会修改原始数据

注意,在槽函数中访问指针时要注意,因为局部变量可能已经被销毁,如果访问已释放的内存,会导致错误

例子

SignalSlot.h

#ifndef SIGNALSLOT_H
#define SIGNALSLOT_H

#include <QObject>

class SignalSlot_singal : public QObject
{
    Q_OBJECT
public:
    explicit SignalSlot_singal(QObject *parent = nullptr);

signals:
    void send(int *arr,int size);
};

class SignalSlot_slot : public QObject
{
    Q_OBJECT
public:
    explicit SignalSlot_slot(QObject *parent = nullptr);

signals:

public slots:
    void receive(int *arr,int size);
};


#endif // SIGNALSLOT_H

SignalSlot.cpp

#include "signalslot.h"

SignalSlot_singal::SignalSlot_singal(QObject *parent)
    : QObject{parent}
{

}


SignalSlot_slot::SignalSlot_slot(QObject *parent)
    : QObject{parent}
{

}

void SignalSlot_slot::receive(int *arr,int size)
{
    for (int i=0;i<size;i++)
    {
        arr[i]++;
    }
}

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include "signalslot.h"

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    SignalSlot_singal *m_signal;
    SignalSlot_slot *m_slot;
};
#endif // WIDGET_H

widgt.cpp

#include "widget.h"
#include <QDebug>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    m_signal=new SignalSlot_singal(this);
    m_slot=new SignalSlot_slot(this);

    connect(m_signal,&SignalSlot_singal::send,m_slot,&SignalSlot_slot::receive);


    int data[5]={1,2,3,4,5};
    for (int i=0;i<5;i++)
    {
        qDebug().noquote()<<data[i];
    }
    qDebug()<<"============";

    emit m_signal->send(data,5);

    for (int i=0;i<5;i++)
    {
        qDebug().noquote()<<data[i];
    }
    qDebug();
}

Widget::~Widget()
{
}

// 输出
1
2
3
4
5
============
2
3
4
5
6

例子 - 纯C++思考

#include <iostream>

using namespace std;

class A
{
private:
    int *arrA;
public:
    A(/* args */);

    void funcA(int *arr)
    {
        cout<<"int funcA"<<endl;
        // arrA=arr;
        arrA=new int[3];
        memcpy(arrA,arr,sizeof(int)*3);
        cout<<arrA<<endl;
        cout<<arr<<endl;
        for (int i=0;i<3;i++)
        {
            arrA[i]++;
        }
        cout<<endl;
    }
};

A::A(/* args */)
{
}

class B
{
private:
    A *objA;
    int *arr;
public:
    B(/* args */);

    void funcB(){
        arr=new int[3];
        arr[0]=1;
        arr[1]=2;
        arr[2]=3;
        cout<<"in funcB"<<endl;
        cout<<arr<<endl;
        printArr(arr);
        objA->funcA(arr);
        printArr(arr);
    }

    void printArr(int *arr)
    {
        for (int i=0;i<3;i++)
        {
            cout<<arr[i];
        }
        cout<<endl;
    }

};

B::B(/* args */)
{
    objA=new A();
}

int main()
{
    A objA;
    B objB;
    objB.funcB();
    
    return 0;
}

// 输出结果
in funcB
0xf71510
123
int funcA
0xf71550
0xf71510
123

解除关联

解除与一个发射者所有信号的连接
解除与一个特定信号的所有连接
解除与一个特定接收者的所有连接
解除特定的一个信号与槽的连接

异步调用

三种方式

  • Qt::QueuedConnection
  • QtConcurrent
  • QThread
QtConcurrent

一种多线程并发框架,可以方便的实现异步调用,使用QtConcurrent可以将一个函数或者函数对象放在独立的线程中执行,从而避免阻塞主线程
在使用QtConcurrent时,需要注意线程安全问题,保证共享资源的正确性

qDebug()

https://stackoverflow.com/questions/5209823/how-to-call-qdebug-without-the-appended-spaces-and-newline

事件补充 - win32

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值