解码Qt事件处理与自定义绘图

Qt事件核心概述

事件的本质

Qt是事件驱动的编程框架,程序的所有交互行为(如点击鼠标、按下键盘、窗口重绘)均由事件触发。QEvent是所有事件类的基类,封装了事件的类型、状态等核心信息,贯穿程序整个生命周期。

常见事件类型

事件类型 触发场景
键盘事件 键盘按键按下/松开
鼠标事件 鼠标移动、按键按下/松开、双击、滚轮滚动
绘图事件 窗口显示/隐藏、被遮挡后恢复、调用update()/repaint()
拖放事件 鼠标拖拽控件/数据并释放
定时器事件 调用startTimer()后指定时间到达时
进入/离开事件 鼠标指针移入/移出Widget区域
移动事件 Widget的位置(x/y坐标)发生改变时
显示/隐藏事件 Widget调用show()/hide()或被父窗口控制显示/隐藏时
焦点事件 Widget获取/失去焦点(如点击输入框、切换窗口)
自定义事件 程序内部自定义的业务事件(如数据加载完成、状态变更)

事件调度流程(核心)

  • 程序启动后创建QApplication对象(Qt应用唯一入口),该对象初始化事件循环(Event Loop) 并进入等待状态;
  • 用户操作(如点击鼠标)或系统触发(如定时器到期)生成事件,事件被封装为QEvent子类对象并加入事件队列
  • 事件循环从队列中取出事件,根据事件类型和目标对象(QObject子类)分发事件;
  • 目标对象通过重写事件处理函数响应事件,处理完成后事件自动销毁,事件循环继续处理下一个事件;
  • 事件处理过程中可能生成新事件(如点击按钮弹出对话框),新事件会加入队列等待处理;
  • 程序退出时(如调用qApp->quit()),事件循环终止,程序结束。

事件处理核心接口

class Q_CORE_EXPORT QObject
{
public:
    /**
     * @brief 事件分发核心函数,所有事件都会先经过此函数
     * @param event 待处理的事件对象(QEvent子类,如QMouseEvent、QKeyEvent)
     * @return bool 返回true表示事件已处理,不再向下传递;返回false表示未处理,交由父类处理
     * @note 子类重写时,未处理的事件必须调用父类的event(),否则会破坏事件传递逻辑
     */
    virtual bool event(QEvent *event);

    /**
     * @brief 事件过滤器函数,用于拦截目标对象的事件
     * @param watched 被监视的目标对象(如某个输入框、按钮)
     * @param event 被拦截的事件对象
     * @return bool 返回true表示拦截事件(不再传递给watched);返回false表示放行
     * @note 使用前需通过`watched->installEventFilter(this)`安装过滤器,适用于全局拦截多个对象的事件
     */
    virtual bool eventFilter(QObject *watched, QEvent *event);
};

class Q_WIDGETS_EXPORT QWidget : public QObject, public QPaintDevice
{
public:
    /**
     * @brief 重写QObject::event,扩展Widget专属事件的分发逻辑
     * @param event 待处理的事件对象
     * @return bool 事件处理状态(同QObject::event)
     * @note QWidget会优先处理鼠标、键盘、绘图等可视化相关事件,再传递给父类
     */
    bool event(QEvent *event) override;
};

鼠标事件(QMouseEvent/QWheelEvent)

核心概念

  • QMouseEvent:处理鼠标左键/右键/中键的按下、松开、双击、移动;
  • QWheelEvent:单独处理鼠标滚轮滚动(不属于QMouseEvent);
  • 鼠标移动事件默认仅在按下鼠标按键时触发,调用setMouseTracking(true)可开启“无按键移动跟踪”;
  • 鼠标按下时,Qt会自动“捕捉”鼠标轨迹,直到最后一个按键松开,期间父窗口持续接收鼠标事件。

核心处理函数(QWidget虚函数)

/**
 * @brief 鼠标按下事件处理函数,鼠标按键按下时触发
 * @param event 鼠标事件对象,包含按键类型、坐标、修饰键(如Ctrl/Shift)等信息
 * @return void 无返回值
 * @note event->button()返回按下的具体按键(Qt::LeftButton/RightButton/MidButton),仅单次按下有效;
 *       event->buttons()返回当前所有按下的按键(适用于多键同时按下,如左键+右键)
 */
virtual void mousePressEvent(QMouseEvent *event);

/**
 * @brief 鼠标松开事件处理函数,鼠标按键松开时触发
 * @param event 鼠标事件对象
 * @return void 无返回值
 * @note 与mousePressEvent配对,通常用于完成“点击”逻辑(按下+松开)
 */
virtual void mouseReleaseEvent(QMouseEvent *event);

/**
 * @brief 鼠标双击事件处理函数,连续快速按下/松开同一按键时触发
 * @param event 鼠标事件对象
 * @return void 无返回值
 * @note 双击的速度由系统设置(如Windows的“鼠标双击速度”)决定,Qt会自动识别
 */
virtual void mouseDoubleClickEvent(QMouseEvent *event);

/**
 * @brief 鼠标移动事件处理函数,鼠标位置变化时触发
 * @param event 鼠标事件对象,event->pos()返回相对于当前Widget的坐标,event->globalPos()返回屏幕坐标
 * @return void 无返回值
 * @note 默认需按下按键才触发,调用setMouseTracking(true)后无按键也触发
 */
virtual void mouseMoveEvent(QMouseEvent *event);

/**
 * @brief 鼠标滚轮事件处理函数,滚轮滚动时触发
 * @param event 滚轮事件对象,Qt5.15+推荐使用angleDelta()(返回QPoint,x=水平滚轮,y=垂直滚轮),传统delta()返回整数值(正向上/负向下,每格120)
 * @return void 无返回值
 */
virtual void wheelEvent(QWheelEvent *event);

/**
 * @brief 鼠标进入Widget区域事件处理函数
 * @param event 通用事件对象(无额外鼠标参数)
 * @return void 无返回值
 */
virtual void enterEvent(QEvent *event);

/**
 * @brief 鼠标离开Widget区域事件处理函数
 * @param event 通用事件对象
 * @return void 无返回值
 */
virtual void leaveEvent(QEvent *event);

示例代码

#include <QMainWindow>
#include <QMouseEvent>
#include <QDebug>

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    /**
     * @brief 构造函数,初始化鼠标跟踪
     * @param parent 父窗口指针,用于内存管理
     */
    MainWindow(QWidget *parent = nullptr) : QMainWindow(parent)
    {
        setMouseTracking(true); // 开启无按键鼠标移动跟踪
    }

protected:
    void mousePressEvent(QMouseEvent *event) override
    {
        // 判断按下的按键类型
        if (event->button() == Qt::LeftButton)
        {
            qDebug() << "鼠标左键按下,位置:" << event->pos();
        }
        else if (event->button() == Qt::RightButton)
        {
            qDebug() << "鼠标右键按下,位置:" << event->pos();
        }
        else if (event->button() == Qt::MidButton)
        {
            qDebug() << "鼠标中键按下,位置:" << event->pos();
        }
    }

    void mouseReleaseEvent(QMouseEvent *event) override
    {
        qDebug() << "鼠标按键松开,按键:" << event->button() << ",位置:" << event->pos();
    }

    void mouseDoubleClickEvent(QMouseEvent *event) override
    {
        qDebug() << "鼠标双击,按键:" << event->button();
    }

    void mouseMoveEvent(QMouseEvent *event) override
    {
        static int count = 0;
        qDebug() << "鼠标移动,次数:" << ++count << ",位置:" << event->pos();
    }

    void wheelEvent(QWheelEvent *event) override
    {
        // Qt5.15+推荐使用angleDelta()
        QPoint delta = event->angleDelta();
        if (delta.y() > 0)
        {
            qDebug() << "滚轮向上滚动,偏移量:" << delta.y();
        }
        else
        {
            qDebug() << "滚轮向下滚动,偏移量:" << delta.y();
        }
    }

    void enterEvent(QEvent *event) override
    {
        qDebug() << "鼠标进入窗口";
    }

    void leaveEvent(QEvent *event) override
    {
        qDebug() << "鼠标离开窗口";
    }
};

image

键盘事件(QKeyEvent)

核心概念

键盘事件触发于按键按下/松开,需重写keyPressEvent/keyReleaseEvent处理;

  • 只有获得焦点的Widget才能接收键盘事件(可通过setFocusPolicy(Qt::StrongFocus)设置焦点策略);
  • QKeyEvent包含按键值、修饰键(Ctrl/Shift/Alt)、是否自动重复(长按按键触发)等信息。

核心处理函数

/**
 * @brief 按键按下事件处理函数,按键按下时触发
 * @param event 键盘事件对象,包含按键值、修饰键等信息
 * @return void 无返回值
 * @note event->key()返回按键枚举值(如Qt::Key_Left/Key_Right/Key_A);
 *       event->modifiers()返回修饰键(如Qt::ControlModifier/Ctrl、Qt::ShiftModifier/Shift);
 *       event->isAutoRepeat()判断是否为长按自动重复触发(如长按A键连续输入)
 */
virtual void keyPressEvent(QKeyEvent *event);

/**
 * @brief 按键松开事件处理函数,按键松开时触发
 * @param event 键盘事件对象
 * @return void 无返回值
 * @note 与keyPressEvent配对,适用于“按下触发动作,松开恢复”的场景(如游戏按键)
 */
virtual void keyReleaseEvent(QKeyEvent *event);

示例代码(窗口移动+快捷键)

#include <QWidget>
#include <QKeyEvent>
#include <QPoint>
#include <QDebug>

class MyWidget : public QWidget
{
    Q_OBJECT
public:
    /**
     * @brief 构造函数,设置焦点策略
     * @param parent 父部件指针
     */
    MyWidget(QWidget *parent = nullptr) : QWidget(parent)
    {
        setFocusPolicy(Qt::StrongFocus); // 允许获取焦点,才能接收键盘事件
        resize(400, 300);
    }

protected:
    void keyPressEvent(QKeyEvent *event) override
    {
        // 跳过自动重复的事件(长按按键)
        if (event->isAutoRepeat())
        {
            return;
        }

        // 组合键:Ctrl+Q退出程序
        if (event->key() == Qt::Key_Q && (event->modifiers() & Qt::ControlModifier))
        {
            qDebug() << "Ctrl+Q触发,程序退出";
            close();
            return;
        }

        // 方向键移动窗口
        QPoint pos = this->pos(); // 获取当前窗口位置
        if (event->key() == Qt::Key_Left)
        {
            pos.setX(pos.x() - 10); // 左移10像素
            move(pos);
        }
        else if (event->key() == Qt::Key_Right)
        {
            pos.setX(pos.x() + 10); // 右移10像素
            move(pos);
        }
        else if (event->key() == Qt::Key_Up)
        {
            pos.setY(pos.y() - 10); // 上移10像素
            move(pos);
        }
        else if (event->key() == Qt::Key_Down)
        {
            pos.setY(pos.y() + 10); // 下移10像素
            move(pos);
        }
        else
        {
            // 未处理的事件交给父类
            QWidget::keyPressEvent(event);
        }
    }

    void keyReleaseEvent(QKeyEvent *event) override
    {
        if (event->isAutoRepeat())
        {
            return;
        }
        qDebug() << "按键松开:" << event->key() << "(枚举值)";
    }
};

image

定时器事件(QTimerEvent)

核心概念

  • QTimerEvent是定时器触发的事件,底层基于startTimer()实现,轻量但功能简单;
  • 复杂定时场景推荐使用QTimer(支持信号槽、单次触发、暂停等);
  • 每个定时器有唯一ID,通过startTimer()创建,killTimer()销毁,析构时需手动停止避免内存泄漏。

核心接口与函数

/**
 * @brief 启动定时器,创建一个定时触发的事件源
 * @param interval 定时间隔(毫秒),如1000表示每1秒触发一次
 * @return int 成功返回定时器ID(非负整数),失败返回-1
 * @note 定时器ID是唯一标识,需保存用于后续停止定时器
 */
int QObject::startTimer(int interval);

/**
 * @brief 停止指定ID的定时器,终止定时事件触发
 * @param id 要停止的定时器ID(startTimer的返回值)
 * @return void 无返回值
 * @note 停止不存在的ID不会报错,建议停止前检查ID有效性
 */
void QObject::killTimer(int id);

/**
 * @brief 定时器事件处理函数,定时间隔到达时触发
 * @param event 定时器事件对象,event->timerId()返回触发的定时器ID
 * @return void 无返回值
 * @note 需重写此函数区分不同ID的定时器逻辑
 */
virtual void QObject::timerEvent(QTimerEvent *event);

示例代码(多定时器+动态启停)

#include <QObject>
#include <QTimerEvent>
#include <QDebug>

class TimerObject : public QObject
{
    Q_OBJECT
public:
    /**
     * @brief 构造函数,启动两个定时器
     * @param parent 父对象指针
     */
    TimerObject(QObject *parent = nullptr) : QObject(parent)
    {
        // 启动两个定时器:1秒/次、5秒/次
        m_timer1Id = startTimer(1000); // 1000ms = 1s
        m_timer2Id = startTimer(5000); // 5000ms = 5s
        qDebug() << "定时器启动,ID1:" << m_timer1Id << ",ID2:" << m_timer2Id;
    }

    /**
     * @brief 析构函数,停止所有定时器
     */
    ~TimerObject() override
    {
        // 析构时停止所有定时器(避免内存泄漏)
        if (m_timer1Id != -1)
        {
            killTimer(m_timer1Id);
        }
        if (m_timer2Id != -1)
        {
            killTimer(m_timer2Id);
        }
    }

protected:
    void timerEvent(QTimerEvent *event) override
    {
        // 区分不同定时器ID
        if (event->timerId() == m_timer1Id)
        {
            static int count = 0;
            qDebug() << "1秒定时器触发,次数:" << ++count;

            // 触发10次后停止该定时器
            if (count >= 10)
            {
                killTimer(m_timer1Id);
                m_timer1Id = -1; // 标记为已停止
                qDebug() << "1秒定时器已停止";
            }
        }
        else if (event->timerId() == m_timer2Id)
        {
            static int count = 0;
            qDebug() << "5秒定时器触发,次数:" << ++count;
        }
        else
        {
            // 未知定时器ID,交给父类处理
            QObject::timerEvent(event);
        }
    }

private:
    int m_timer1Id = -1; // 1秒定时器ID,初始值-1表示未启动
    int m_timer2Id = -1; // 5秒定时器ID
};

// 主函数调用示例
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    TimerObject timerObj;
    return app.exec();
}

image

自定义事件

核心概念

  • 自定义事件是QEvent的子类,用于实现程序内部的“自定义信号”(比信号槽更灵活,支持事件队列、优先级);
  • 事件类型需使用QEvent::User及以上(QEvent::UserQEvent::MaxUser为用户自定义范围);
  • 发送方式:postEvent()(异步,加入事件队列)、sendEvent()(同步,立即处理)。

核心接口与完整示例

#include <QApplication>
#include <QWidget>
#include <QEvent>
#include <QDebug>

/**
 * @brief 自定义事件类,继承QEvent并指定唯一事件类型
 * @note 事件类型需大于等于QEvent::User(推荐QEvent::User + 自定义偏移,避免冲突)
 */
class CustomEvent : public QEvent
{
public:
    /**
     * @brief 自定义事件构造函数
     * @param data 自定义数据(示例:整数型业务数据)
     * @note 事件类型固定为QEvent::User + 1,需保证全局唯一
     */
    CustomEvent(int data) : QEvent(Type(QEvent::User + 1)), m_data(data) {}

    /**
     * @brief 获取自定义事件的业务数据
     * @return int 存储的整数数据
     */
    int getData() const { return m_data; }

private:
    int m_data; // 自定义业务数据
};

/**
 * @brief 事件接收者类,重写event()或customEvent()处理自定义事件
 */
class EventReceiver : public QWidget
{
    Q_OBJECT
public:
    EventReceiver(QWidget *parent = nullptr) : QWidget(parent) {}

protected:
    /**
     * @brief 方式1:重写event()函数处理自定义事件(通用方式)
     * @param event 待处理的事件对象
     * @return bool 事件是否被处理
     */
    bool event(QEvent *event) override
    {
        // 判断事件类型是否为自定义事件
        if (event->type() == QEvent::User + 1)
        {
            CustomEvent *customEvent = dynamic_cast<CustomEvent*>(event);
            if (customEvent)
            {
                qDebug() << "通过event()接收自定义事件,数据:" << customEvent->getData();
            }
            return true; // 事件已处理,不再传递
        }
        // 其他事件交给父类处理
        return QWidget::event(event);
    }

    /**
     * @brief 方式2:重写customEvent()函数(Qt提供的自定义事件专用处理函数)
     * @param event 待处理的自定义事件对象
     * @return void 无返回值
     * @note 仅处理类型为QEvent::User及以上的事件,无需手动判断类型
     */
    void customEvent(QEvent *event) override
    {
        CustomEvent *customEvent = dynamic_cast<CustomEvent*>(event);
        if (customEvent)
        {
            qDebug() << "通过customEvent()接收自定义事件,数据:" << customEvent->getData();
        }
        // 调用父类函数,确保事件传递(可选)
        QWidget::customEvent(event);
    }
};

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    /**
		 * @brief 创建事件接收者对象
		 * @note EventReceiver是自定义的QWidget子类,重写了event()和customEvent()函数,用于处理自定义事件;
		 *       该对象创建在栈上,生命周期由作用域管理(main函数结束时自动析构);
		 *       调用show()后,对象成为可视化Widget,进入Qt的事件循环体系,具备接收和处理事件的能力;
		 *       只有继承QObject的类(QWidget是QObject子类)才能接收Qt事件,非QObject类无法处理事件。
		 */
    EventReceiver receiver;
    receiver.show();

    // 方式1:异步发送事件(推荐)
    CustomEvent *asyncEvent = new CustomEvent(100); // 堆上创建,Qt自动销毁
    /**
		 * @brief 异步发送事件(核心函数)
		 * @param receiver 事件接收对象(必须是QObject子类,如EventReceiver、QWidget等),事件最终会分发到该对象的event()函数
		 * @param event 待发送的事件对象(需堆上new创建,Qt会在事件处理完成后自动销毁,无需手动delete)
		 * @param priority 事件优先级,可选值:
		 *                 - Qt::HighEventPriority(高优先级,优先处理)
		 *                 - Qt::NormalEventPriority(默认,常规优先级)
		 *                 - Qt::LowEventPriority(低优先级,最后处理)
		 * @return void 无返回值
		 * @note 1. 异步特性:事件不会立即处理,而是加入Qt的全局事件队列,等待事件循环轮询处理;
		 *       2. 内存安全:事件对象必须堆上创建(new),Qt内部会接管内存,处理完后自动delete,避免内存泄漏;
		 *       3. 线程安全:可跨线程发送事件,Qt会自动将事件投递到接收者所在线程的事件队列;
		 *       4. 适用场景:非紧急事件(如自定义业务事件、UI更新事件),避免阻塞主线程。
		 */
    QCoreApplication::postEvent(&receiver, asyncEvent, Qt::HighEventPriority);

    // 方式2:同步发送事件
    CustomEvent syncEvent(200); // 栈上创建,无需手动销毁
    /**
		 * @brief 同步发送事件(核心函数)
		 * @param receiver 事件接收对象(QObject子类),事件会立即分发到该对象的event()函数
		 * @param event 待发送的事件对象(推荐栈上创建,无需手动销毁,函数返回后可复用或自动析构)
		 * @return bool 事件是否被接收者处理(即receiver的event()函数返回true表示已处理,false表示未处理)
		 * @note 1. 同步特性:函数调用后会立即触发receiver的event()函数,直到事件处理完成才返回;
		 *       2. 内存安全:事件对象推荐栈上创建(如示例中的syncEvent),若堆上创建需手动delete,Qt不会接管;
		 *       3. 无优先级:同步事件跳过事件队列,直接处理,无视优先级设置;
		 *       4. 线程限制:若接收者在其他线程,不能跨线程调用sendEvent,会导致未定义行为(需用postEvent);
		 *       5. 适用场景:紧急事件(如必须立即响应的系统事件),但需避免在主线程同步发送耗时事件(阻塞UI)。
		 */
    QCoreApplication::sendEvent(&receiver, &syncEvent);

    return app.exec();
}

image

关键说明

  • postEvent():事件对象需用new创建,Qt会在事件处理完成后自动销毁,避免内存泄漏;
  • sendEvent():事件对象建议用栈创建,同步调用后不会自动销毁,手动delete会崩溃;
  • 优先级:高优先级事件会先于低优先级事件被处理,适用于紧急业务场景(如系统告警)。

绘图事件(QPaintEvent)与QPainter

核心概念

  • QPaintEvent:触发于Widget需要重绘时(如显示、被遮挡、调用update()),必须重写paintEvent()处理;
  • QPainter:Qt绘图核心类,支持绘制图形、文本、图像,提供画笔(QPen)、画刷(QBrush)、坐标变换等功能;
  • 绘图流程:创建QPainter → 设置绘图属性(画笔、画刷) → 调用绘制函数 → 结束绘图(自动或手动end())。

QPainter常用接口

#include <QPainter>
#include <QPen>
#include <QBrush>
#include <QFont>
#include <QImage>

/**
 * @brief QPainter核心接口示例(结合paintEvent使用)
 */
void paintEvent(QPaintEvent *event) override
{
    Q_UNUSED(event);
    QPainter painter(this); // 创建绘图对象,绑定当前Widget

    // 1. 基础设置:抗锯齿(提升绘制质量)
    painter.setRenderHint(QPainter::Antialiasing, true);

    // 2. 画笔设置(绘制轮廓、线条)
    QPen pen;
    pen.setColor(Qt::red); // 颜色
    pen.setWidth(2);       // 线宽
    pen.setStyle(Qt::DashLine); // 线型(实线、虚线等)
    painter.setPen(pen);

    // 3. 画刷设置(填充图形)
    QBrush brush;
    brush.setColor(Qt::yellow); // 填充颜色
    brush.setStyle(Qt::SolidPattern); // 填充样式(实心、渐变等)
    painter.setBrush(brush);

    // 4. 绘制基础图形
    painter.drawRect(10, 10, 200, 100); // 矩形(x,y,宽,高)
    painter.drawEllipse(250, 10, 100, 100); // 椭圆(x,y,宽,高——在矩形内)
    painter.drawLine(10, 150, 350, 150); // 直线(起点x,y → 终点x,y)

    // 5. 绘制文本
    QFont font("Arial", 16, QFont::Bold);
    painter.setFont(font);
    painter.setPen(Qt::blue);
    painter.drawText(QRect(10, 180, 350, 50), Qt::AlignCenter, "Qt绘图示例");

    // 6. 绘制图像
    QImage image("test.png"); // 加载图像(需确保路径正确)
    if (!image.isNull())
    {
        painter.drawImage(QPoint(10, 250), image.scaled(100, 100)); // 缩放绘制
    }

    // 7. 坐标变换(平移、旋转、缩放)
    painter.save(); // 保存当前绘图状态
    painter.translate(200, 300); // 平移坐标系(原点移至(200,300))
    painter.rotate(45); // 旋转45度
    painter.scale(0.8, 0.8); // 缩放0.8倍
    painter.drawRect(-50, -50, 100, 100); // 基于新坐标系绘制
    painter.restore(); // 恢复之前的绘图状态

    // 8. 绘制路径(复杂图形)
    QPainterPath path;
    path.moveTo(300, 250);
    path.lineTo(350, 300);
    path.lineTo(300, 350);
    path.closeSubpath(); // 闭合路径
    painter.setBrush(Qt::green);
    painter.drawPath(path);
}

完整绘图示例(自定义绘图Widget)

#include <QWidget>
#include <QPainter>
#include <QPen>
#include <QBrush>
#include <QFont>

class PaintWidget : public QWidget
{
    Q_OBJECT
public:
    PaintWidget(QWidget *parent = nullptr) : QWidget(parent)
    {
        resize(400, 400);
        setWindowTitle("Qt绘图示例");
    }

protected:
    void paintEvent(QPaintEvent *event) override
    {
        Q_UNUSED(event);
        QPainter painter(this);

        // 1. 绘制带边框的矩形
        painter.setPen(QPen(Qt::red, 2));
        painter.setBrush(Qt::yellow);
        painter.drawRect(20, 20, 150, 100);

        // 2. 绘制圆形(椭圆的特殊情况)
        painter.setPen(QPen(Qt::blue, 1));
        painter.setBrush(Qt::cyan);
        painter.drawEllipse(200, 20, 100, 100);

        // 3. 绘制文本
        painter.setPen(Qt::black);
        painter.setFont(QFont("宋体", 14));
        painter.drawText(20, 150, "矩形区域");
        painter.drawText(220, 150, "圆形区域");

        // 4. 绘制三角形(通过路径)
        QPainterPath path;
        path.moveTo(50, 200);
        path.lineTo(150, 200);
        path.lineTo(100, 280);
        path.closeSubpath();
        painter.setPen(Qt::green);
        painter.setBrush(Qt::lightGray);
        painter.drawPath(path);

        // 5. 绘制渐变背景(线性渐变)
        QLinearGradient gradient(0, 300, width(), 300);
        gradient.setColorAt(0, Qt::red);
        gradient.setColorAt(0.5, Qt::yellow);
        gradient.setColorAt(1, Qt::blue);
        painter.setBrush(gradient);
        painter.setPen(Qt::NoPen);
        painter.drawRect(0, 300, width(), 100);
    }
};

// 主函数调用
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    PaintWidget widget;
    widget.show();
    return app.exec();
}

image

自定义部件(Custom Widget)

核心思路

通过继承QWidget或其子类(如QPushButton),重写paintEvent()(绘图)、mousePressEvent()(交互)等函数,实现自定义功能的部件。

示例1:基础圆形部件

// MyCircleWidget.h
#include <QWidget>
#include <QPainter>

class MyCircleWidget : public QWidget
{
    Q_OBJECT
public:
    explicit MyCircleWidget(QWidget *parent = nullptr) : QWidget(parent)
    {
        setMinimumSize(100, 100); // 设置最小尺寸
    }

protected:
    void paintEvent(QPaintEvent *event) override
    {
        Q_UNUSED(event);
        QPainter painter(this);
        painter.setRenderHint(QPainter::Antialiasing); // 抗锯齿

        // 绘制黄色圆形,蓝色边框
        painter.setPen(QPen(Qt::blue, 3));
        painter.setBrush(Qt::yellow);
        // 调整圆形位置,与Widget边界有10像素间距
        QRect circleRect = rect().adjusted(10, 10, -10, -10);
        painter.drawEllipse(circleRect);
    }
};

// 使用方式
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MyCircleWidget circleWidget;
    circleWidget.resize(200, 200);
    circleWidget.show();
    return app.exec();
}

image

示例2:圆形进度条(带百分比)

#include <QWidget>
#include <QPainter>
#include <QTimer>
#include <QFont>
#include <QtMath>

class CircleProgressBar : public QWidget
{
    Q_OBJECT
public:
    explicit CircleProgressBar(QWidget *parent = nullptr) : QWidget(parent), m_progress(0)
    {
        setMinimumSize(150, 150);
        // 模拟进度增长(实际使用时可通过setProgress()设置)
        QTimer *timer = new QTimer(this);
        // 修复:捕获列表添加 this 和 timer
        connect(timer, &QTimer::timeout, this, [this, timer]() {
            setProgress(m_progress + 1);
            if (m_progress >= 100) timer->stop();
        });
        timer->start(50);
    }

    /**
     * @brief 设置进度值
     * @param progress 进度(0-100)
     */
    void setProgress(int progress)
    {
        m_progress = qBound(0, progress, 100); // 限制在0-100
        update(); // 触发重绘
    }

protected:
    void paintEvent(QPaintEvent *event) override
    {
        Q_UNUSED(event);
        QPainter painter(this);
        painter.setRenderHint(QPainter::Antialiasing);

        int width = this->width();
        int height = this->height();
        int radius = qMin(width, height) / 2 - 10; // 圆半径(减去边框间距)
        QPoint center(width / 2, height / 2); // 圆心

        // 1. 绘制背景圆(灰色边框)
        painter.setPen(QPen(Qt::lightGray, 8));
        painter.setBrush(Qt::NoBrush);
        painter.drawEllipse(center, radius, radius);

        // 2. 绘制进度圆弧(蓝色)
        painter.setPen(QPen(Qt::blue, 8));
        // 圆弧范围:从-90度(顶部)开始,顺时针绘制m_progress%的圆弧
        int startAngle = -90 * 16; // Qt中角度以1/16度为单位
        int spanAngle = m_progress * 3.6 * 16; // 360度 * m_progress% = 进度角度
        painter.drawArc(center.x() - radius, center.y() - radius,
                       radius * 2, radius * 2,
                       startAngle, spanAngle);

        // 3. 绘制百分比文本
        painter.setPen(Qt::black);
        painter.setFont(QFont("Arial", 18, QFont::Bold));
        QString text = QString("%1%").arg(m_progress);
        painter.drawText(rect(), Qt::AlignCenter, text);
    }

private:
    int m_progress; // 进度值(0-100)
};

// 使用方式
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    CircleProgressBar progressBar;
    progressBar.resize(200, 200);
    progressBar.show();
    return app.exec();
}

image

水波进度条(高级绘图示例)

实现原理

基于正弦函数生成波浪线,通过定时器更新波浪相位实现“波动”效果,结合圆形裁剪实现圆形水波进度条。

完整实现代码

#include <QWidget>
#include <QPainter>
#include <QPainterPath>
#include <QTimer>
#include <cmath>

class WaveProgressBar : public QWidget
{
    Q_OBJECT
public:
    explicit WaveProgressBar(QWidget *parent = nullptr) : QWidget(parent),
        m_progress(0), m_waveOffset(0), m_waveAmplitude(8), m_waveFrequency(0.03)
    {
        setMinimumSize(150, 150);
        // 定时器:每20ms更新一次波浪相位
        m_timer = new QTimer(this);
        connect(m_timer, &QTimer::timeout, this, [this]() {
            m_waveOffset += 0.1;
            update();
        });
        m_timer->start(20);
    }

    /**
     * @brief 设置进度值
     * @param progress 进度(0-100)
     */
    void setProgress(int progress)
    {
        m_progress = qBound(0, progress, 100);
        update();
    }

protected:
    void paintEvent(QPaintEvent *event) override
    {
        Q_UNUSED(event);
        QPainter painter(this);
        painter.setRenderHint(QPainter::Antialiasing);

        int width = this->width();
        int height = this->height();
        QRect widgetRect = rect();

        // 1. 绘制圆形背景(半透明浅蓝色)
        painter.setPen(Qt::NoPen);
        painter.setBrush(QColor(0, 180, 255, 50));
        painter.drawEllipse(widgetRect.adjusted(5, 5, -5, -5));

        // 2. 计算波浪参数
        float waveHeight = height * m_progress / 100.0; // 波浪高度(进度对应)
        float waveY = height - waveHeight; // 波浪起始Y坐标

        // 3. 绘制波浪路径
        QPainterPath wavePath;
        wavePath.moveTo(0, height); // 左下角起点
        wavePath.lineTo(0, waveY);  // 左侧波浪起点

        // 生成正弦波浪线
        for (int x = 0; x <= width; x++)
        {
            // 正弦函数:y = A*sin(ωx + φ) + 基准Y坐标
            float y = waveY + m_waveAmplitude * sin(m_waveFrequency * x + m_waveOffset);
            wavePath.lineTo(x, y);
        }

        wavePath.lineTo(width, waveY); // 右侧波浪终点
        wavePath.lineTo(width, height); // 右下角终点
        wavePath.closeSubpath();

        // 4. 裁剪波浪路径为圆形(与背景圆重合)
        QPainterPath circlePath;
        circlePath.addEllipse(widgetRect.adjusted(5, 5, -5, -5));
        QPainterPath clippedPath = wavePath.intersected(circlePath); // 取交集

        // 5. 绘制波浪填充色(半透明蓝色)
        painter.setBrush(QColor(0, 180, 255, 150));
        painter.drawPath(clippedPath);

        // 6. 绘制进度文本
        painter.setPen(Qt::white);
        painter.setFont(QFont("Arial", 20, QFont::Bold));
        QString text = QString("%1%").arg(m_progress);
        painter.drawText(widgetRect, Qt::AlignCenter, text);
    }

private:
    int m_progress;          // 进度值(0-100)
    float m_waveOffset;      // 波浪相位偏移(控制波动)
    float m_waveAmplitude;   // 波浪振幅(高度)
    float m_waveFrequency;   // 波浪频率(密度)
    QTimer *m_timer;         // 波动定时器
};

// 使用方式
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    WaveProgressBar progressBar;
    progressBar.resize(200, 200);
    progressBar.setProgress(60); // 设置初始进度60%
    progressBar.show();
    return app.exec();
}

image

Qt软键盘(自定义事件模拟输入)

核心需求

实现一个软键盘,点击按键时向当前焦点输入框发送键盘事件,模拟真实键盘输入。

完整实现代码

自定义按键类(KeyButton.h)

#ifndef KEYBUTTON_H
#define KEYBUTTON_H

#include <QPushButton>
#include <QKeyEvent>
#include <QApplication>

/**
 * @brief 软键盘自定义按键类,点击时发送键盘事件
 */
class KeyButton : public QPushButton
{
    Q_OBJECT
public:
    explicit KeyButton(QWidget *parent = nullptr) : QPushButton(parent), m_key(-1)
    {
        initStyle();
    }

    explicit KeyButton(const QString &text, QWidget *parent = nullptr) : QPushButton(text, parent), m_key(-1)
    {
        initStyle();
    }

    /**
     * @brief 设置按键对应的键盘键值(如Qt::Key_A、'a')
     * @param key 键值(Qt::Key枚举或ASCII码)
     */
    void setKey(int key) { m_key = key; }

signals:
    /**
     * @brief 按键点击信号,传递键值
     * @param key 按键对应的键值
     */
    void keyClicked(int key);

private slots:
    /**
     * @brief 按键点击槽函数,发送键盘事件到焦点控件
     */
    void onKeyClicked()
    {
        // 如果未设置键值,从按钮文本获取(如文本为"a",则键值为'a')
        if (m_key == -1 && !text().isEmpty())
        {
            m_key = text().at(0).toLatin1();
        }

        if (m_key != -1)
        {
            emit keyClicked(m_key);
            // 创建键盘按下事件,发送给当前焦点控件
            QKeyEvent *pressEvent = new QKeyEvent(QEvent::KeyPress, m_key, Qt::NoModifier, text());
            QApplication::postEvent(QApplication::focusWidget(), pressEvent);

            // 创建键盘松开事件(模拟完整按键流程)
            QKeyEvent *releaseEvent = new QKeyEvent(QEvent::KeyRelease, m_key, Qt::NoModifier, text());
            QApplication::postEvent(QApplication::focusWidget(), releaseEvent);
        }
    }

private:
    /**
     * @brief 初始化按键样式(去除焦点边框,设置大小)
     */
    void initStyle()
    {
        setFocusPolicy(Qt::NoFocus); // 去除按键焦点(避免点击后焦点转移)
        setFixedSize(60, 60);        // 设置按键固定大小
        setStyleSheet("QPushButton { font-size: 20px; }");
        // 绑定点击信号到槽函数
        connect(this, &QPushButton::clicked, this, &KeyButton::onKeyClicked);
    }

private:
    int m_key; // 按键对应的键盘键值
};

#endif // KEYBUTTON_H

软键盘主窗口(SoftKeyboard.h)

#ifndef SOFTKEYBOARD_H
#define SOFTKEYBOARD_H

#include <QWidget>
#include <QGridLayout>
#include "KeyButton.h"

/**
 * @brief 软键盘主窗口,包含多个KeyButton
 */
class SoftKeyboard : public QWidget
{
    Q_OBJECT
public:
    explicit SoftKeyboard(QWidget *parent = nullptr) : QWidget(parent)
    {
        setWindowTitle("软键盘");
        initLayout();
    }

private:
    /**
     * @brief 初始化键盘布局(3x3按键示例)
     */
    void initLayout()
    {
        QGridLayout *layout = new QGridLayout(this);
        layout->setSpacing(10);

        // 创建按键(示例:a、b、c、d、e、f、删除、清空、确认)
        KeyButton *btnA = new KeyButton("a");
        KeyButton *btnB = new KeyButton("b");
        KeyButton *btnC = new KeyButton("c");
        KeyButton *btnD = new KeyButton("d");
        KeyButton *btnE = new KeyButton("e");
        KeyButton *btnF = new KeyButton("f");

        KeyButton *btnBackspace = new KeyButton("←");
        btnBackspace->setKey(Qt::Key_Backspace); // 设置删除键对应的键值

        KeyButton *btnClear = new KeyButton("清空");
        connect(btnClear, &KeyButton::keyClicked, this, [this]() {
            // 向焦点输入框发送清空信号(示例:QLineEdit)
            QWidget *focusWidget = QApplication::focusWidget();
            if (focusWidget)
            {
                QMetaObject::invokeMethod(focusWidget, "clear", Qt::QueuedConnection);
            }
        });

        KeyButton *btnConfirm = new KeyButton("确认");
        connect(btnConfirm, &KeyButton::keyClicked, this, [this]() {
            qDebug() << "确认按钮点击";
            // 可添加确认逻辑(如提交输入内容)
        });

        // 添加按键到布局
        layout->addWidget(btnA, 0, 0);
        layout->addWidget(btnB, 0, 1);
        layout->addWidget(btnC, 0, 2);
        layout->addWidget(btnD, 1, 0);
        layout->addWidget(btnE, 1, 1);
        layout->addWidget(btnF, 1, 2);
        layout->addWidget(btnBackspace, 2, 0);
        layout->addWidget(btnClear, 2, 1);
        layout->addWidget(btnConfirm, 2, 2);

        setLayout(layout);
        resize(300, 300);
    }
};

#endif // SOFTKEYBOARD_H

测试程序(main.cpp)

#include <QApplication>
#include <QWidget>
#include <QLineEdit>
#include <QVBoxLayout>
#include "SoftKeyboard.h"

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

    // 创建主窗口,包含输入框和软键盘
    QWidget mainWidget;
    mainWidget.setWindowTitle("软键盘测试");

    QVBoxLayout *layout = new QVBoxLayout(&mainWidget);

    // 添加三个输入框(测试焦点切换)
    QLineEdit *edit1 = new QLineEdit;
    QLineEdit *edit2 = new QLineEdit;
    QLineEdit *edit3 = new QLineEdit;
    edit1->setPlaceholderText("输入框1");
    edit2->setPlaceholderText("输入框2");
    edit3->setPlaceholderText("输入框3");

    // 添加软键盘
    SoftKeyboard *keyboard = new SoftKeyboard;

    layout->addWidget(edit1);
    layout->addWidget(edit2);
    layout->addWidget(edit3);
    layout->addWidget(keyboard);

    mainWidget.resize(400, 500);
    mainWidget.show();

    return app.exec();
}

image

关键说明

  • 自定义按键KeyButton通过setFocusPolicy(Qt::NoFocus)去除焦点,避免点击后输入框失去焦点;
  • 点击按键时,通过QApplication::postEvent()向当前焦点控件发送QKeyEvent,模拟真实键盘输入;
  • 支持焦点切换:点击不同输入框后,软键盘输入会自动指向当前焦点的输入框。

事件过滤器(Event Filter)

核心作用

拦截一个或多个QObject的事件,在事件到达目标对象前进行预处理(如修改、拦截、日志记录)。

完整示例(拦截输入框键盘事件)

#include <QApplication>
#include <QWidget>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QEvent>
#include <QKeyEvent>
#include <QDebug>

/**
 * @brief 事件过滤器类,拦截QLineEdit的键盘事件
 */
class InputFilter : public QObject
{
    Q_OBJECT
public:
    explicit InputFilter(QObject *parent = nullptr) : QObject(parent) {}

protected:
    /**
     * @brief 重写eventFilter()函数,拦截事件
     * @param watched 被监视的对象(如QLineEdit)
     * @param event 被拦截的事件
     * @return bool true=拦截事件,false=放行事件
     */
    bool eventFilter(QObject *watched, QEvent *event) override
    {
        // 仅拦截QLineEdit的键盘按下事件
        if (watched->isWidgetType() && dynamic_cast<QLineEdit*>(watched) &&
            event->type() == QEvent::KeyPress)
        {
            QKeyEvent *keyEvent = dynamic_cast<QKeyEvent*>(event);
            if (keyEvent)
            {
                // 拦截数字键(禁止输入数字)
                if (keyEvent->key() >= Qt::Key_0 && keyEvent->key() <= Qt::Key_9)
                {
                    qDebug() << "拦截数字键:" << keyEvent->key();
                    return true; // 拦截事件,不传递给输入框
                }
                // 允许其他键(如字母、符号)
                qDebug() << "放行键:" << keyEvent->key();
            }
        }
        // 其他事件放行,交给原对象处理
        return QObject::eventFilter(watched, event);
    }
};

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

    QWidget mainWidget;
    QVBoxLayout *layout = new QVBoxLayout(&mainWidget);

    QLineEdit *edit1 = new QLineEdit;
    QLineEdit *edit2 = new QLineEdit;
    edit1->setPlaceholderText("禁止输入数字(输入框1)");
    edit2->setPlaceholderText("禁止输入数字(输入框2)");

    // 创建事件过滤器
    InputFilter *filter = new InputFilter;
    // 为两个输入框安装事件过滤器
    edit1->installEventFilter(filter);
    edit2->installEventFilter(filter);

    layout->addWidget(edit1);
    layout->addWidget(edit2);
    mainWidget.resize(300, 150);
    mainWidget.show();

    return app.exec();
}

关键步骤

  • 创建事件过滤器类,重写eventFilter()函数;
  • 为目标对象(如QLineEdit)调用installEventFilter()安装过滤器;
  • eventFilter()中判断事件类型和目标对象,决定是否拦截;
  • 不需要时调用removeEventFilter()移除过滤器。

总结

Qt事件处理是Qt应用的核心,涵盖鼠标、键盘、定时器、绘图等所有交互场景,关键要点:

  • 事件通过事件循环分发,QEvent是所有事件的基类;
  • 处理事件的三种方式:重写特定事件函数(如mousePressEvent)、重写event()、使用事件过滤器;
  • 自定义事件适用于复杂业务场景,支持异步发送和优先级;
  • QPainter是绘图核心,结合paintEvent()可实现自定义部件和复杂绘图效果;
  • 软键盘、水波进度条等高级组件,本质是事件处理与绘图的结合。
  • 掌握Qt事件处理机制,可灵活实现各类交互功能,适配桌面、嵌入式等不同平台的Qt应用开发。
posted @ 2025-12-19 18:43  YouEmbedded  阅读(0)  评论(0)    收藏  举报