(12)Qt事件系统(one)

简介: 本文详细介绍了Qt事件系统,包括各种系统事件、鼠标事件、键盘事件、定时器等的处理方法和示例代码。

Qt Event System

在Qt中,事件是派生自抽象QEvent类的对象,它表示应用程序内发生的事情或应用程序需要知道的外部活动的结果。事件可以由QObject子类的任何实例接收和处理,但它们与小部件尤其相关(例如自定义的按钮Button类)。Qt程序需要在main()函数创建一个QApplication对象,然后调用它的exec()函数。这个函数就是开始Qt的事件循环。在执行exec()函数之后,程序将进入事件循环来监听应用程序的事件,当事件发生时,Qt将创建一个事件对象

事件处理的方法

传递事件的通常方式是调用虚函数。例如,QPaintEvent通过调用QWidget::paintEvent()来传递。这个虚函数负责进行适当的响应,通常是重新绘制小部件。如果在虚函数的实现中不执行所有必要的工作,则可以调用基类的实现。

系统事件处理函数

基本事件

事件处理函数是重写父类或者基类的虚函数,不需要手动调用,会在事件循环中自动调用。

#禁用Qt6以前的函数(Cmake中)
add_compile_definitions(QT_DISABLE_DEPRECATED_BEFORE=0x060000)

窗口显示事件

除隐藏和显示窗口外,窗口最小化会发送窗口隐藏事件,正常显示会发送窗口显示事件。

#include <QApplication>
#include <QWidget>
#include <QDebug>
#include <QMessageBox>
#include <QMoveEvent>

class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget* parent = nullptr)
        :QWidget(parent)
    {
        resize(640,480);
    }
    ~Widget(){}
    // 窗口显示事件
    void showEvent(QShowEvent* ev) override
    {
        QMessageBox::information(this, "提示", "窗口显示");
    }
};

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

    Widget w;
    w.show();

    return a.exec();
}
#include "main.moc"

窗口关闭事件

当Qt从窗口系统接收到一个顶级小部件的窗口关闭请求时,将用给定的事件调用此事件处理程序。

默认情况下,接受事件并关闭小部件。您可以重新实现此函数,以更改小部件响应窗口关闭请求的方式。例如,您可以通过在所有事件上调用ignore()来防止窗口关闭。

主窗口应用程序通常使用该函数的重新实现来检查用户的工作是否已保存,并在关闭前请求权限。

void Widget::closeEvent(QCloseEvent* ev)override
{
    auto ret = QMessageBox::question(this, "温馨提示", "你有未保存的操作,是否保存并关闭?");
    if (ret == QMessageBox::StandardButton::Yes)
    {
        // 接收关闭
        ev->accept();
        // 与前一句代码效果相同 (设置为true表示接收该事件)
        //ev->setAccepted(true);
    }
    else
    {
        // 不接收关闭
        ev->ignore();
        //ev->setAccepted(false);
    }
}

窗口隐藏事件

void Widget::hideEvent(QHideEvent* ev)override
{
    qInfo() << "我隐藏啦~";
}

窗口移动事件

// 窗口或控件移动事件
void moveEvent(QMoveEvent* ev) override
{
    qInfo() << "移动事件: " << ev->oldPos() << ev->pos();
}

窗口大小改变事件

// 窗口大小改变事件
void resizeEvent(QResizeEvent* ev) override
{
    qInfo() << "窗口大小改变: " << ev->oldSize() << ev->size();
}

窗口状态改变事件

如果需要检测程序中,某些东西是否发生了改变,可以通过void QWidget::changeEvent(QEvent *event)来检测。

  • 以下是常用事件:

    • QEvent::FontChange

    • QEvent::WindowTitleChange

    • QEvent::IconTextChange

    • QEvent::ModifiedChange

    • QEvent::MouseTrackingChange

    • QEvent::WindowStateChange

  • QEvent::WindowStateChange窗口状态改变为例!

void Widget::changeEvent(QEvent* ev)override
{
    if (ev->type() == QEvent::WindowStateChange)
    {
        auto we = static_cast<QWindowStateChangeEvent*>(ev);
        qInfo() << "oldState" << we->oldState();
        qInfo() << "curState" << this->windowState();
    }
}

鼠标事件

鼠标进入、离开事件

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

class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget* parent = nullptr)
        :QWidget(parent)
    {
        resize(640,480);
        btn = new QPushButton("Button", this);
        //启用鼠标追踪
        setMouseTracking(true);
    }
    ~Widget(){}
    //鼠标进入
    void enterEvent(QEnterEvent* ev) override
    {
        //鼠标进入事件(进入客户区响应)
        btn->setStyleSheet("background-color:red;");
    }
    //鼠标离开
    void leaveEvent(QEvent* ev) override
    {
        //一般是重写按钮类,然后重写鼠标进入和离开事件
        btn->setStyleSheet("background-color:green;");
    }
private:
    QPushButton* btn;
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}
#include "main1.moc"

鼠标按下抬起事件

 //鼠标按下
void mousePressEvent(QMouseEvent* ev)override
{
    //判断那个键按下
    if(ev->button() == Qt::MouseButton::LeftButton)
    {
        //鼠标左键按下 按钮右移
        btn->move(btn->x() + 10,btn->y());
        //鼠标点击的坐标(局部的坐标)
        btn->setText(QString("%1,%2")
                     .arg(ev->pos().x())
                     .arg(ev->pos().y()));
    }
}
//鼠标抬起
void mouseReleaseEvent(QMouseEvent* ev) override
{
    //判断什么键释放了
    if(ev->button() == Qt::MouseButton::LeftButton)
    {
        btn->move(btn->pos()+QPoint(0,10));
    }
}

鼠标双击事件

//鼠标双击
void mouseDoubleClickEvent(QMouseEvent* ev) override
{
    qDebug() << __FUNCTION__;
}

鼠标移动事件

使用鼠标移动事件的时候需要启用鼠标追踪

//启用鼠标追踪
setMouseTracking(true);
//鼠标移动
void mouseMoveEvent(QMouseEvent* ev) override
{
    //鼠标移动事件不会自动追踪鼠标,只有当有鼠标按键按下状态才会进行鼠标的追踪
    qInfo() << __FUNCTION__;
    //当启用鼠标追踪之后无需按下也可追踪鼠标

    //获取鼠标按键(不能通过ev->button()获取)左右键均按下获取不到Qt::MouseButton::LeftButton
    if(ev->buttons() == Qt::MouseButton::LeftButton)   //buttons == button state
    {
        qInfo() << "鼠标左键按下并移动" << ev->buttons();
    }
    //这样,左右键均按下的情况也可以获取到
    if(ev->buttons() & Qt::MouseButton::LeftButton)
    {
        qInfo() << ev->buttons();
    }
}

没有启用鼠标追踪的情况下,鼠标不按下不能进行鼠标追踪

启用鼠标追踪后,不按下也能追踪鼠标的位置

鼠标滚轮事件

返回轮子旋转的相对量,单位为八分之一度。正值表示转轮向前旋转,远离用户;负值表示转轮向后向用户旋转。angleDelta().y()提供自上一个事件以来旋转普通垂直鼠标滚轮的角度。如果鼠标有水平滚轮,angleDelta().x()提供水平鼠标滚轮旋转的角度,否则就是0。有些鼠标允许用户倾斜滚轮来进行水平滚动,有些触摸板支持水平滚动手势;它也会出现在angleDelta().x()中。

大多数鼠标类型的工作步长为15度,在这种情况下,delta值是120的倍数;即120单位* 1/8 = 15度。

然而,有些鼠标的滚轮分辨率更高,发送的delta值小于120单位(小于15度)。为了支持这种可能性,可以累计添加来自事件的增量值,直到达到120的值,然后滚动小部件,或者可以部分滚动小部件以响应每个轮事件。但是为了提供更原生的感觉,您应该更喜欢在有pixelDelta()的平台上使用它。

//鼠标滚轮
void wheelEvent(QWheelEvent* ev) override
{
    qInfo() << ev->angleDelta().x()/8     //横向滚动 120单位/8 =  15度
        << ev->angleDelta().y()/8;    //纵向滚动 120单位/8 =  15度
}

示例:无边框窗口移动

在做窗口应用程序时,为了使窗口更简洁,有时候会将窗口setWindowFlag();属性设置为“Qt::WindowType::FramelessWindowHint”,这时,窗口将不可拖动,但我们可以通过以下代码尝试解决无边框窗体的拖动问题。

#include <QApplication>
#include <QWidget>
#include <QMouseEvent>
#include <QPushButton>

class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget* parent = nullptr)
        :QWidget(parent)
    {
        resize(640,480);

        //设置窗口无边框
        setWindowFlag(Qt::WindowType::FramelessWindowHint);

        auto btn = new QPushButton("关闭窗口", this);
        connect(btn,&QPushButton::clicked,this,&Widget::close);
    }
    ~Widget()
    {

    }
    void mouseMoveEvent(QMouseEvent* ev) override
    {
        if(ev->buttons() & Qt::MouseButton::LeftButton)
        {
            QPoint pos = ev->pos();
            //鼠标位置的全局坐标 - 鼠标按下的位置(局部坐标)
            //move(mapToGlobal(pos) - pressPos);
            move(ev->globalPosition().toPoint() - pressPos);
        }
    }
    void mousePressEvent(QMouseEvent* ev) override
    {
        pressPos = ev->pos();
    }
private:
    QPoint pressPos;        //保存鼠标按下的坐标
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}
#include "main.moc"

键盘事件

键盘按下抬起事件

// 基本使用    判断是什么按键按下(字符不区分大小写)不是字符消息
void keyPressEvent(QKeyEvent* ev) override
{
    //如果ev没有使用,不想让编译器提示未使用的变量
    Q_UNUSED(ev)
    //当窗口中有上下左右键时,按钮中有控件(焦点在控件上按上下左右是没有反应的)
    qInfo() << "keyPressEvent:" << Qt::Key(ev->key());
    //返回此键生成的Unicode文本。不同平台按下Shift、Control、Alt和Meta等修饰键时的返回值不同,可能返回空字符串。
    qInfo() << "text:" << ev->text();
    //获取按下了什么键盘修饰符(shift、control、alt、meta(windows键)、keypad(小键盘,即数字键))
    qInfo() << ev->modifiers();
}

//键盘按下事件
void keyPressEvent(QKeyEvent* ev) override
{
    switch(ev->key())
    {
        case Qt::Key::Key_Up:
            qInfo() << "up";
            break;
        default:
            break;
    }
    //快捷键 Ctrl + A
    if(ev->modifiers() & Qt::KeyboardModifier::ControlModifier &&
       ev->key() == Qt::Key::Key_A)
    {
        qInfo() << "Ctrl + A";
    }
    //直接有判断是否有某个快捷键
    if(ev->matches(QKeySequence::Copy))
    {
        qInfo() << "matches" << "Ctrl + C";
    }
}

// 键盘抬起事件
void keyReleaseEvent(QKeyEvent* ev) override
{
    switch(ev->key())
    {
        case Qt::Key::Key_Up:
            qInfo() << "up release";
            break;
        default:
            break;
    }
}

//获取快捷键(显示)
for(int i = 0;i < 71; i++)
{
    qInfo() << QKeySequence::StandardKey(i)
        << QKeySequence(QKeySequence::StandardKey(i)).toString();
}

//设置窗口关闭时销毁对象(而不是隐藏)
setAttribute(Qt::WidgetAttribute::WA_DeleteOnClose);

键盘事件实现按钮移动

class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget* parent = nullptr)
        :QWidget(parent)
    {
        setWindowFlag(Qt::WindowType::FramelessWindowHint);
        resize(640, 480);
        button = new QPushButton("玩蛇", this);
        // 设置按钮不可获取焦点(这样才能触发上下左右按键)
        button->setFocusPolicy(Qt::FocusPolicy::NoFocus);
    }
    void keyPressEvent(QKeyEvent* ev)override
    {
        switch (ev->key())
        {       
        case Qt::Key::Key_Up:
            button->move(button->pos() + QPoint(0, -1));
            break;
        case Qt::Key::Key_Down:
            button->move(button->pos() + QPoint(0, 1));
            break;
        case Qt::Key::Key_Left:
            button->move(button->pos() + QPoint(-1, 0));
            break;
        case Qt::Key::Key_Right:
            button->move(button->pos() + QPoint(1, 0));
            break;
        }
    }
private:
    QPushButton* button = nullptr;
};

焦点事件

这个事件处理程序可以在子类中重新实现,以接收小部件的键盘焦点事件(焦点接收)。

小部件通常必须将focuspolicy()设置为Qt::NoFocus以外的东西,以便接收焦点事件。(注意,程序员可以在任何小部件上调用setFocus(),即使是那些通常不接受焦点的小部件。)

默认实现更新小部件(不指定focusPolicy()的窗口除外)。

// 按tab键可以切换焦点
#include <QApplication>
#include <QWidget>
#include <QPushButton>

class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget* parent = nullptr)
        :QWidget(parent)
    {
        resize(640,480);

        for(int i = 0; i < 3; i++)
        {
            btns[i] = new QPushButton("Button",this);
            btns[i]->move(i*100,i*100);
        }
        //为窗口设置焦点之后才可以触发焦点事件
        this->setFocus();
    }
    ~Widget()
    {

    }
    //焦点进入事件
    void focusInEvent(QFocusEvent* ev)override
    {
        qInfo() << "focus In";
        btns[1]->setStyleSheet("border:3px solid red;");
    }
    //焦点退出事件
    void focusOutEvent(QFocusEvent* ev)override
    {
        qInfo() << "focus Out";
        btns[1]->setStyleSheet("border:3px solid green;");
    }
private:
    QPushButton* btns[3];
};

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

    Widget w;
    w.show();

    return a.exec();
}
#include "main.moc"

右键菜单

右键菜单信号与槽方式

// 信号方式
#include <QApplication>
#include <QWidget>
#include <QContextMenuEvent>

class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget* parent = nullptr)
        :QWidget(parent)
    {
        resize(640,480);
        // 设置上下文菜单选项
        this->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);

        connect(this, &Widget::customContextMenuRequested, this, &Widget::slot_contextMenu);
    }
    ~Widget()
    {

    }
private slots:
    void slot_contextMenu(const QPoint& pos)
    {
        qInfo() << "右键菜单弹出" << pos;
    }
};

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

    Widget w;
    w.show();

    return a.exec();
}
#include "main.moc"

右键菜单事件方式

// contextMenuEvent方式
 void  Widget::contextMenuEvent(QContextMenuEvent* ev)
 {
     // 直接在此处弹出菜单即可
     qInfo() << "报告长官,请求弹出上下文菜单,也就是右键菜单!"
             << "请弹出在\n"
             << "全局坐标:"<<ev->globalPos()
             << "局部坐标:"<<ev->pos() << "位置";     
 }

拖拽事件

#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QDragEnterEvent>
#include <QMimeData>

class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget* parent = nullptr)
        :QWidget(parent)
    {
        resize(640,480);

        //设置窗口接受拖拽
        setAcceptDrops(true);
    }
    ~Widget()
    {

    }
    //拖拽事件
    //使用时需要设置窗口接受拖拽
    void dragEnterEvent(QDragEnterEvent* ev)override
    {
        const QMimeData* pData = ev->mimeData();
        quint64 pos = pData->text().lastIndexOf(".");
        QString str = pData->text().right(pData->text().size() - pos);
        qInfo() << str;
        if(str == ".doc")
        //拖拽进入控件时调用
            ev->acceptProposedAction();     //接受拖拽的建议操作
    }
    void dropEvent(QDropEvent* ev)override
    {
        //拖拽放下时调用
        const QMimeData* pData = ev->mimeData();
        qInfo() << pData->text();                   //获取文件路径
    }
};

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

    Widget w;
    w.show();

    return a.exec();
}
#include "main.moc"

输入法事件

// 输入法事件
void inputMethodEvent(QInputMethodEvent* ev)
{
    qInfo() << ev->commitString() << ev->preeditString();
}

定时器

定时器事件方式实现

// 定时器事件实现定时器
class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget* parent = nullptr)
        :QWidget(parent)
    {
         //启动一个定时器,并返回定时器ID(可设置定时精度)
        _timerID = startTimer(1000);
    } 
    void timerEvent(QTimerEvent* ev)override
    {
        if (ev->timerId() == _timerID)
        {
            qInfo()<<_timerID<<"timeout";
        }
    }
private:
    int _timerID;
};

QTimer信号与槽方式创建定时器

// 定时器的实现方式2(QTimer)
#include <QTimer>

class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget* parent = nullptr)
        :QWidget(parent)
    {
         //创建定时器
        m_timer = new QTimer(this);
        connect(m_timer, &QTimer::timeout, this, [](){
            qInfo() << "hello world";
        });
        // 定时器开始(一秒执行一次)
        m_timer->start(1000);
    } 
private:
    QTimer* m_timer;
};

成员函数方式
// 使用成员函数callOnTimeout代替connect
#include <QTimer>

class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget* parent = nullptr)
        :QWidget(parent)
    {
         //创建定时器
        m_timer = new QTimer(this);
        m_timer->callOnTimeout(this, [](){
            qInfo() << "hello world";
        });
        //m_timer->callOnTimeout([](){qInfo() << "Hello World!";});
        // 定时器开始(一秒执行一次)
        m_timer->start(1000);
    } 
private:
    QTimer* m_timer;
};

单发
// QTimer静态成员函数(单次发射)
QTimer::singleShot(1000, [](){
    qInfo() << "Hello World";
});

相关文章
|
27天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
4天前
|
人工智能 Rust Java
10月更文挑战赛火热启动,坚持热爱坚持创作!
开发者社区10月更文挑战,寻找热爱技术内容创作的你,欢迎来创作!
438 17
|
7天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
20天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
7天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
379 2
|
22天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
24天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2600 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
6天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
286 2
|
4天前
|
编译器 C#
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
106 65
|
24天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1582 17
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码