当前位置: 首页 > news >正文

Qt通过QPainter 绘制网格,以及滑动界面消除格子的方式来验证TP触摸屏的准确性

做完接到个任务,要将Linux嵌入式的手持设备的TP测试界面,由原来的画线,改为消除网格的方式。
就是将这样的:
在这里插入图片描述
换成这样的:
在这里插入图片描述
其实绘制比较简单。就是想借此说一下Qt的绘图系统以及paintEvent()事件的工作机制。

一、Qt绘图系统核心原理

1.1 绘图系统架构

Qt的绘图系统基于三要素构建:

  • QPainter:执行绘制操作的"画笔",提供超过200个图形绘制方法;
  • QPaintDevice:绘制载体(如QWidget、QPixmap),屏幕分辨率480x800的适配可通过设置单元格尺寸实现;
    QPaintEngine:设备抽象层,开发者通常无需直接操作;

三者关系如下:
QPainter → QPaintEngine → QPaintDevice

1.2 paintEvent工作机制

当发生以下事件时触发重绘:

  • 窗口首次显示
  • 窗口尺寸调整
  • 被遮挡区域重新可见
  • 主动调用update()/repaint()
    update(); // 请求异步重绘(推荐)
    repaint(); // 立即同步重绘(慎用)

1.3 双缓冲技术

Qt在paintEvent中默认启用双缓冲机制,通过以下方式优化绘制:

  • 先在内存绘制完整图像;
  • 单次写入显存避免闪烁;
  • 自动处理多线程绘制同步;

二、核心类与函数详解

2.1 QPainter关键方法举例

在这里插入图片描述
实际QPainter的方法很多,之前文章中也列举过,各种跟图形绘制的方法。这里只侧重列一下跟本次绘制和消除网格相关的。

2.2 QWidget绘图函数

// 必须重写的核心函数 
void paintEvent(QPaintEvent *event) override {
    QPainter painter(this);
    // 绘制操作 ,开始你绘制你想要的图画
}

// 鼠标事件处理函数 (在嵌入式里边,没有鼠标,其实就是手触点击)
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;

三、网格颜色交互实现方案

3.1 设计思路

所谓整个界面的网格,可以看作时一个个独立的小格子纵横排列的矩阵形成;
界面对象构造,界面初始化时,默认调用paintEvent()事件,先绘制一个默认颜色的网格;
当鼠标按下(实际嵌入式里边就是手指触摸按下),触发mousePressEvent()事件,记录点触的坐标位置;
将点触的坐标位置转换成网格里的格子;
对格子进行颜色变换的重绘;
鼠标继续滑动(手指滑动),触发mouseMoveEvent()事件,事件响应中使用轨迹插值算法保证连续绘制;

3.2 完整代码实现

// ColorGridWidget.h 
#include <QWidget>
#include <QVector>
 
class ColorGridWidget : public QWidget {
    Q_OBJECT 
public:
    explicit ColorGridWidget(QWidget *parent = nullptr);
 
protected:
    void paintEvent(QPaintEvent *) override;
    void mousePressEvent(QMouseEvent *) override;
    void mouseMoveEvent(QMouseEvent *) override;
 
private:
    const int CELL_SIZE = 32;    // 适配480x800,我的设备就是480*800的竖屏
    const int ROWS = 25;         // 800/32=25 
    const int COLS = 15;         // 480/32=15
    
    QVector<QVector<QColor>> gridColors; // 颜色状态矩阵 
    QPoint lastPos;                     // 轨迹记录 
 
    void initGrid();
    void updateCell(const QPoint &pos);
    QPoint posToGrid(const QPoint &pos) const;
};
// ColorGridWidget.cpp  
#include "ColorGridWidget.h"
#include <QPainter>
#include <QMouseEvent>
 
ColorGridWidget::ColorGridWidget(QWidget *parent) 
    : QWidget(parent) {
    setAttribute(Qt::WA_StaticContents);  // 优化静态内容绘制 
    initGrid();
    //固定界面尺寸
    setFixedSize(480, 800);
}
 
void ColorGridWidget::initGrid() {
    gridColors.resize(ROWS); 
    for (auto &row : gridColors) {
        row.resize(COLS); 
        row.fill(Qt::red);   // 初始化红色网格 
    }
}
 
void ColorGridWidget::paintEvent(QPaintEvent *) {
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing); 
    
    // 绘制单元格颜色 
    for (int row=0; row<ROWS; ++row)
    {
        for (int col=0; col<COLS; ++col)
        {
        //整个页面的网格其实就是一个个小矩形作为单个格子拼起来的
            QRect cellRect(col*CELL_SIZE, row*CELL_SIZE, CELL_SIZE, CELL_SIZE);        
            // 填充颜色 
            painter.fillRect(cellRect,  gridColors[row][col]);
            // 绘制2px宽的黑色网格线
            painter.setPen(QPen(Qt::black,  2));
            //绘制小格子
            painter.drawRect(cellRect); 
        }
    }
}
 
QPoint ColorGridWidget::posToGrid(const QPoint &pos) const {
//这里其实是将像素点换成网格位置,类似一种坐标转换
    return QPoint(
        qBound(0, pos.x() / CELL_SIZE, COLS-1),
        qBound(0, pos.y() / CELL_SIZE, ROWS-1)
    );
}
 
void ColorGridWidget::updateCell(const QPoint &pos)
{
    QPoint gridPos = posToGrid(pos);
    if (gridColors[gridPos.y()][gridPos.x()] != Qt::green) {
        gridColors[gridPos.y()][gridPos.x()] = Qt::green;
        // 局部更新优化 
        update(QRect(gridPos.x()*CELL_SIZE, gridPos.y()*CELL_SIZE,
                    CELL_SIZE, CELL_SIZE));
    }
}
 
void ColorGridWidget::mousePressEvent(QMouseEvent *event) {
    if (event->button() == Qt::LeftButton) {
        lastPos = event->pos();
        updateCell(lastPos);
    }
}
 
void ColorGridWidget::mouseMoveEvent(QMouseEvent *event) {
    if (event->buttons() & Qt::LeftButton) {
        // 轨迹插值算法保证连续绘制 
        QLineF line(lastPos, event->pos());
        qreal length = line.length(); 
        const qreal STEP = CELL_SIZE * 0.6;  // 优化轨迹密度 
        
        for (qreal i=0; i<length; i+=STEP) {
            QPoint curPos = line.pointAt(i/length).toPoint(); 
            updateCell(curPos);
        }
        lastPos = event->pos();
    }
}

3.3 技术要点解析

(1). 坐标转换算法

posToGrid()通过整除计算将物理坐标转换为网格索引,qBound确保索引不越界.

(2). 轨迹插值优化

使用QLineF进行线性插值,STEP参数控制采样密度,平衡绘制精度与性能.

(3). 局部更新机制

update(QRect(…))仅重绘受影响区域,相比全局重绘性能提升.

(4). 渲染优化技巧
  • 开启WA_StaticContents属性减少重绘区域;
  • 设置Antialiasing实现抗锯齿;
  • 预计算ROWS/COLS避免运行时重复计算;

至此,项目要求的网格形状绘制完成,滑动消除格子。

四、扩展功能

在已经实现的基础上,如果想做的更通用些,更完美写,可以试试:

4.1 功能增强方向

  • 多色笔刷选择
// 在类中添加颜色选择成员 
QColor currentColor = Qt::green;
 
// 右键菜单设置颜色 
void contextMenuEvent(QContextMenuEvent *)
{
    QColor color = QColorDialog::getColor();
    if (color.isValid())  currentColor = color;
}
  • 撤销/重做功能
// 使用QStack记录状态变化 
QStack<GridState> undoStack;
// 每次修改时压栈
void updateCell()
{
    undoStack.push(currentState); 
    // ...修改操作 
}

4.2 需要注意的问题

  • 绘图残留的问题
    如果由绘制的残留显示,可以在paintEvent开始时先清除背景
painter.eraseRect(rect()); 
  • 跨设备绘制异常的情况
    如果是外部设备调用,可以把QPainter 单列出来,遵循"begin-end"配对原则:
QPainter painter;
if (painter.begin(this))  {
    // 绘制操作 
    painter.end(); 
}

相关文章:

  • SSM共享充电宝系统
  • 数据库的sql语句
  • centos 7 停更后如何升级kernel版本 —— 筑梦
  • MySQL InnoDB 引擎中的聚簇索引和非聚簇索引有什么区别?
  • k8s使用containerd作为容器运行时配置Harbor私有仓库与阿里云私有仓库以及镜像加速器,k8s基于containerd如何配置harbor私有仓库
  • C#上位机--一元运算符
  • Nginx反向代理出现502 Bad Gateway问题的解决方案
  • 初级网络工程师之从入门到入狱(十二)
  • 大模型应用:多轮对话(prompt工程)
  • MT-Metrics
  • 在PyTorch使用UNet进行图像分割【附源码】
  • MySQL—使用binlog日志恢复数据
  • 学习笔记-07生产者-消费者模型4种实现方式
  • C++基础学习
  • 【Golang学习之旅】Go-zero + Gen:如何使用 Gen 提升 Go 开发效率
  • AI学习资料留档(持续更新)
  • windows下适用msvc编译ffmpeg 适用于ffmpeg-7.1
  • 解释 Node.js 的事件循环机制,理解微任务(microtask)与宏任务(macrotask)的区别?
  • Qt监控系统远程回放/录像文件远程下载/录像文件打上水印/批量多线程极速下载
  • JVM 面试
  • 网上书城网站开发的结论和不足/微信小程序怎么做店铺
  • 高端品牌网站建设网站开发注意什么/随州今日头条新闻
  • 深圳有做网站的公司有哪些/seo变现培训
  • 济南集团网站建设流程/网站优化推广平台
  • 家庭电脑做网站/p站关键词排名
  • 电商网站的人员团队建设/广告营销案例分析