1. Qt事件

信号槽与事件之间的关系

信号槽:我们之前对信号槽的定义是,用户进行各种操作,就有可能能产生信号,我们可以给这个信号关联一个参函数,当触发这个信号的时候执行对应的槽函数。

事件:其实和信号槽机制很像,也是用户进行各种操作,同样也可能产生事件。同样可以给对应的事件关联处理函数(处理逻辑),当事件触发的时候就能够处理对应的处理函数。

事件本身是操作系统提供的机制,Qt也同样把操作系统事件进行了封装拿到了Qt中。但是对应事件的代码边写起来不方便。于是Qt对于事件又进行了封装,就得到了信号槽。所以对于信号槽来说事件就是它的底层机制。而实际上Qt开发过程中,绝大部分和用户的交互都是通过信号槽机制进行的。但是有些特殊情况下信号槽不一定能完成,比如某些用户的动作没有对应的Qt信号,那么此时就需要通过重写事件处理函数来手动处理用户的响应。

在Qt平台中使用一个对象来表示一个事件。所有的Qt事件均继承于抽象类QEvent。事件是由系统或者Qt平台本身在不同的时刻发出的。当用户按下鼠标、敲下键盘,或者是窗口需要重新绘制的时候,都会发出⼀个相应的事件。⼀些事件是在用户操作时发出,如键盘事件、鼠标事件等,另⼀些事件则是由系统本身自动发出,如定时器事件。常见的Qt事件如下:
在这里插入图片描述

事件名称 描述
鼠标事件 鼠标左键、鼠标右键、鼠标滚轮,鼠标的移动,鼠标按键的按下和松开
键盘事件 按键类型、按键按下、按键松开
定时器事件 定时时间到达
进入离开事件 鼠标的进入和离开
滚轮事件 鼠标滚轮滚动
绘屏事件 重绘屏幕的某些部分
显示隐藏事件 窗口的显示和隐藏
移动事件 窗口位置的变化
窗口事件 是否为当前窗口
大小改变事件 窗口大小改变
焦点事件 键盘焦点移动
拖拽事件 用鼠标进行拖拽

2. 处理事件

之前的信号槽机制是通过connect函数进行关联度的,但是Qt事件的处理方式就有点不一样了。Qt的事件是通过重写某个事件处理函数来进行的。在Qt中,几乎所有的Event事件都是虚函数,所有可以进行重写实现。这里用到的是多态机制。步骤一般是,首先创建子类,继承自Qt中已有的事件类,然后在子类中重写处理函数。这样后续事件触发的时候就会通过多态机制执行我们重写的事件处理函数了。

2.1 enterEvent && leaveEvent

示例:实现鼠标进入事件(enterEvent)和离开事件(leaveEvent)

[virtual protected] void QWidget::enterEvent(QEvent *event)

This event handler can be reimplemented in a subclass to receive widget enter events which are passed in the event parameter.
An event is sent to the widget when the mouse cursor enters the widget.
See also leaveEvent(), mouseMoveEvent(), and event().
[virtual protected] void QWidget::leaveEvent(QEvent *event)

This event handler can be reimplemented in a subclass to receive widget leave events which are passed in the event parameter.
A leave event is sent to the widget when the mouse cursor leaves the widget.
See also enterEvent(), mouseMoveEvent(), and event().

首先创建一个Label类继承自QLabel
在这里插入图片描述
在这里插入图片描述

此时就需要在生成的子类中重写上面两个函数,注意一定要保证函数名和参数类型保持一致
Label.h

#include <QLabel>

class Label : public QLabel
{
    Q_OBJECT
public:
    Label(QWidget* parent);

    void enterEvent(QEvent* envet);
    void leaveEvent(QEvent* envet);
};

Label.cpp

#include <QDebug>
Label::Label(QWidget* parent) : QLabel(parent)
{

}
void Label::enterEvent(QEvent *envet)
{
    (void) envet;
    qDebug() << "enterEvent";
}
void Label::leaveEvent(QEvent *envet)
{
    (void) envet;
    qDebug() << "leaveEvent";
}

但是这个时候还不能进行触发。现在可以通过两种方式进行触发,第一种就是使用我们自己创建的Label类生成,第二种就是使用ui界面的升级控件来进行。

第一种:

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    Label* label = new Label(this);
    label->setText("这是一个代码生成的标签");
}

第二种方法,右击控件,选择提升为
在这里插入图片描述
此时就可以触发事件了。


示例:实现一个鼠标移动到按钮上时,按钮移动

同样的首先要创建一个PushButton继承自QPushBUtton
pushButton.h

#include <QWidget>
#include <QPushButton>

class PushButton : public QPushButton
{
    Q_OBJECT
public:
    PushButton(QWidget* parent);
    void enterEvent(QEvent* event); // 重写鼠标进入事件
public:
    QRect rect; // 创建一个QRect类来存放父窗口的窗口信息
};

pushButton.cpp

#include <QDebug>
#include <random>
PushButton::PushButton(QWidget* parent) : QPushButton(parent)
{
    rect = parent->geometry(); // 获取父窗口的窗口信息
    srand((unsigned)0);// 设置随机种子
}

void PushButton::enterEvent(QEvent *event)
{
    QRect r = this->geometry(); // 获取按钮的信息
    qDebug() << r << rect;
    int x = rand() % (rect.width() - r.width());
    int y = rand() % (rect.height() - r.height());
    this->setGeometry(x, y, r.width(), r.height());// 重写设置按钮的信息
}

widget.cpp

#include "pushbutton.h"
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    PushButton* button = new PushButton(this);
    button->setText("按钮");
}

2.2鼠标事件

  1. 函数介绍:鼠标按下事件
[override virtual protected] void QLabel::mousePressEvent(QMouseEvent *ev)
Reimplements: QWidget::mousePressEvent(QMouseEvent *event).

同时这里说明一下,这个鼠标按下指的是只要鼠标上的按钮按下就会触发,无论是鼠标左键,右键,侧键,中键,只要是鼠标上的按钮就可以触发。

void Label::mousePressEvent(QMouseEvent *ev)
{
    if (ev->button() == Qt::LeftButton){
        qDebug() << "鼠标左键被按下";
    }else if (ev->button() == Qt::RightButton){
        qDebug() << "鼠标右键被按下";
    }
    // 这个是以屏幕的左上角为原点
    qDebug() << ev->globalX() << ev->globalY();
    // 这个是以标签的左上角为原点
    qDebug() << ev->x() << ", " << ev->y();
}

在这里插入图片描述


  1. 同理我们还有:鼠标释放事件
[override virtual protected] void QLabel::mouseReleaseEvent(QMouseEvent *ev)
Reimplements: QWidget::mouseReleaseEvent(QMouseEvent *event).

  1. 鼠标双击事件
[virtual protected] void QWidget::mouseDoubleClickEvent(QMouseEvent *event)
This event handler, for event event, can be reimplemented in a subclass to receive mouse double click events for the widget.

  1. 鼠标移动事件
[virtual protected] void QWidget::mouseMoveEvent(QMouseEvent *event)

This event handler, for event event, can be reimplemented in a subclass to receive mouse move events for the widget.
If mouse tracking is switched off, mouse move events only occur if a mouse button is pressed while the mouse is being moved. If mouse tracking is switched on, mouse move events occur even if no mouse button is pressed.
QMouseEvent::pos() reports the position of the mouse cursor, relative to this widget. For press and release events, the position is usually the same as the position of the last mouse move event, but it might be different if the user's hand shakes. This is a feature of the underlying window system, not Qt.
If you want to show a tooltip immediately, while the mouse is moving (e.g., to get the mouse coordinates with QMouseEvent::pos() and show them as a tooltip), you must first enable mouse tracking as described above. Then, to ensure that the tooltip is updated immediately, you must call QToolTip::showText() instead of setToolTip() in your implementation of mouseMoveEvent().

前面的代码我们都是在自定义的Label中完成的,并且鼠标的作用范围也只是在Label范围内生效。但是如果我们想在窗口中执行也适可以的,我们只需要直接在Widget(QWidget的子类)进行扩展就行了。也就是直接在WIdget中重写事件函数即可。

但是这个鼠标移动事件和上面的鼠标点击时间有所不同,由于我们鼠标只需要随便移动一下就可以触发移动事件,也就会产生大量的事件,一旦我们要处理一些复杂的任务的时候,就很容易让程序的负担加重,就很容易卡顿。所以Qt为了程序的流畅性,默认情况下是不会对鼠标进行追踪的,也就是鼠标移动的时候不会调用mouseMoveEvent函数。而要让程序追踪鼠标就必须主动告诉程序就需要调用setMouseTracking函数。

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 把这个选项设置为true,才能够追踪到鼠标的移动位置
    this->setMouseTracking(true);
}

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

void Widget::mouseMoveEvent(QMouseEvent *event)
{
    qDebug() << event->x() << ", " << event->y();
}

  1. 鼠标滚轮事件
[virtual protected] void QWidget::wheelEvent(QWheelEvent *event)

This event handler, for event event, can be reimplemented in a subclass to receive wheel events for the widget.
If you reimplement this handler, it is very important that you ignore() the event if you do not handle it, so that the widget's parent can interpret it.
The default implementation ignores the event.

可以通过delta()函数获取滚动的数值。

void Widget::wheelEvent(QWheelEvent *event)
{
    total += event->delta();
    qDebug() << total;
}

3. 键盘按键事件

[virtual protected] void QWidget::keyPressEvent(QKeyEvent *event)

This event handler, for event event, can be reimplemented in a subclass to receive key press events for the widget.
A widget must call setFocusPolicy() to accept focus initially and have focus in order to receive a key press event.
If you reimplement this handler, it is very important that you call the base class implementation if you do not act upon the key.
The default implementation closes popup widgets if the user presses the key sequence for QKeySequence::Cancel (typically the Escape key). Otherwise the event is ignored, so that the widget's parent can interpret it.
Note that QKeyEvent starts with isAccepted() == true, so you do not need to call QKeyEvent::accept() - just do not call the base class implementation if you act upon the key.
void Widget::keyPressEvent(QKeyEvent *event)
{
    qDebug() << event->key();
    // 当我们需要按下组合键时需要用到modifiers函数
    if (event->key() == Qt::Key_A && event->modifiers() == Qt::ControlModifier)
        qDebug() << "按下了Ctrl + A";
}

在这里插入图片描述

4. 定时事件

4.1设置定时

Qt中在进行窗口程序的处理过程中,经常要周期性的执行某些操作,或者制作⼀些动画效果,使用定时器就可以实现。所谓定时器就是在间隔⼀定时间后,去执行某⼀个任务。定时器在很多场景下都会使用到,如弹窗自动关闭之类的功能等。

Qt中的定时器分为QTimerEvent和QTimer这2个类。

  • QTimerEvent类用来描述⼀个定时器事件。在使用时需要通过startTimer()函数来开启⼀个定时器,这个函数需要输入一个以毫秒为单位的整数作为参数来表明设定的时间,它返回的整型值代表这个定时器,而且这个定时器唯一标识这个定时器。当定时器溢出时(即定时时间到达)就会触发timerEvent()函数,使用killTimer()关闭一个定时器,从而获取该定时器的编号来进行相关操作。
  • QTimer类来实现⼀个定时器,它提供了更高层次的编程接口,如:可以使用信号和槽,还可以设置只运行一次的定时器。而QTimer其实就是QTimerEvent封装后的结果。

方法一:使用QTimerEvent
timerEvent函数介绍

[virtual protected] void QObject::timerEvent(QTimerEvent *event)

This event handler can be reimplemented in a subclass to receive timer events for the object.
QTimer provides a higher-level interface to the timer functionality, and also more general information about timers. The timer event is passed in the event parameter.
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 此处开启定时器
    timerId = this->startTimer(1000);
}

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

void Widget::timerEvent(QTimerEvent *event)
{
    // 一个程序种可能存在多个定时器,所以需要使用timerId来确定是那个定时器触发的
    if (event->timerId() != timerId){
        return;
    }
    int value = ui->lcdNumber->intValue();
    if (value <= 0){
        // 停止定时器
        this->killTimer(this->timerId);
        return;
    }
    value -= 1;
    ui->lcdNumber->display(value);
}

方法二:使用QTimer

#include <QTimer>
#include <QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 此处开启定时器
    timerId = this->startTimer(1000);

    QTimer* time = new QTimer(this);
    connect(ui->pushButton, &QPushButton::clicked, this, [=](){
        time->start(1000);
    });
    connect(time, &QTimer::timeout, this, [=](){
        static int num = 1;
        ui->label->setText(QString::number(num++));
    });
    connect(ui->pushButton_2, &QPushButton::clicked, this, [=](){
        time->stop();
    });
}

在这里插入图片描述

4.2 获取系统日期及时间

在Qt中获取系统日期和时间可以通过QTimer和QDataTime类获取

QDateTime类提供了字符串格式的时间。字符串形式的时间输出格式由toString()方法中的format参数列表决定,可用的参数列表如下:
在这里插入图片描述
现在我们实现一个例子,展示的效果就是我们结合一个定时器,每过一秒中就显示当前时间,同时各两个按钮,一个开始一个停止按钮。

#include <QDateTime>
#include <QTimer>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QTimer* time = new QTimer(this);
    connect(ui->pushButton, &QPushButton::clicked, this, [=](){
        time->start(1000);
    });
    connect(ui->pushButton_2, &QPushButton::clicked, this, [=](){
        time->stop();
    });
    connect(time, &QTimer::timeout, this, &Widget::handel);
}

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

void Widget::handel()
{
    QString str = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
    ui->label->setText(str);
}

在这里插入图片描述

5.事件分发器

在Qt中,事件分发器(EventDispatcher)是⼀个核⼼概念,⽤处理GUI应用程序中的事件。事件分发器负责将事件从⼀个对象传递到另⼀个对象,直到事件被处理或被取消。每个继承自QObject类或QObject类本身都可以在本类中重写boolevent(QEvent*e)函数,来实现相关事件的捕获和拦截。

5.1派发器的原理

在Qt中,我们发送的事件都是传给了QObject对象,更具体点是传给了QObject对象的event()函数。所有的事件都会进入到这个函数里面,那么我们处理事件就要重写这个event()函数。event()函数本身不去处理事件,而是根据事件类型(type值)调用不同的事件处理函数。事件分发器就是工作在应用程序向下分发事件的过程中,如下图:

在这里插入图片描述

如上图,事件分发器用于分发事件。在此过程中,事件分发器也可以做拦截操作。事件分发器主要是通过bool event(QEvent *e)函数来实现。其返回值为布尔类型,若为ture,代表拦截,不向下分发。

Qt中的事件是封装QEvent类中,在Qt助手中输入QEvent可以查看其所包括的事件类型,如下图所示:
在这里插入图片描述
在这里插入图片描述
现在我们实现一个拦截器,拦截鼠标点击事件

void Widget::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton){
        qDebug() << "鼠标左键被按下";
    }
}

bool Widget::event(QEvent *event)
{
    if (event->type() == QEvent::MouseButtonPress){
        qDebug() << "event鼠标左键被按下";
        return true; // 返回true,代表不向下分发
    }
    // 剩下的交给父类处理(默认处理)
    return QWidget::event(event);
}

于是当我们点击鼠标左键的时候就不会执行mousePressEvent了,但是上面的代码还是会出现一点问题,就是当我们连续点击鼠标左键的时候他还是会执行mousePressEvent。这是当你快速点击鼠标左键时,Qt 会检测到这是一个双击操作,此时第二个点击产生的事件类型不再是QEvent::MouseButtonPress,而是 QEvent::MouseButtonDblClick。由于 event() 没有拦截 MouseButtonDblClick,这个事件会走到 return QWidget::event(event); 这一行。然后 Qt 父类的QWidget::event() 分发这个双击事件 → 调用 mouseDoubleClickEvent()。而 mouseDoubleClickEvent() 的默认实现恰恰就是调用mousePressEvent():

在这里插入图片描述

6. 移动窗口和改变大小事件

  1. moveEvent
[virtual protected] void QWidget::moveEvent(QMoveEvent *event)

This event handler can be reimplemented in a subclass to receive widget move events which are passed in the event parameter. When the widget receives this event, it is already at the new position.
  1. resizeEvent
[virtual protected] void QWidget::resizeEvent(QResizeEvent *event)

This event handler can be reimplemented in a subclass to receive widget resize events which are passed in the event parameter. When resizeEvent() is called, the widget already has its new geometry. The old size is accessible through QResizeEvent::oldSize().
The widget will be erased and receive a paint event immediately after processing the resize event. No drawing need be (or should be) done inside this handler.

执行效果展示

void Widget::moveEvent(QMoveEvent *event)
{
    qDebug() << event->pos();
}

void Widget::resizeEvent(QResizeEvent *event)
{
    qDebug() << event->size();
}
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐