QT基础之双缓冲机制介绍

本文档介绍了如何在Qt中利用双缓冲机制创建一个简单的绘图工具,以消除重绘时的闪烁现象。通过QPixmap作为缓冲区,先在缓冲区上绘制,然后一次性将图像绘制到控件上,实现平滑的绘图体验。同时,文章展示了如何通过QPainter和鼠标事件来实现实时绘图,并提供了主选项区域的实现,允许用户选择画笔样式、颜色和线宽。
摘要由CSDN通过智能技术生成


更多参见
QT基础与实例应用目录

代码链接

GitHub链接 :DoubleBufferMechanism

介绍

双缓冲机制,是指在控件绘制时,会首先将要绘制的内容绘制在一个图片中,再将图片一次性地绘制到控件上。早期Qt版本,如果直接在控件上进行绘制工作,那么在控件重绘时会产生闪烁的现象,重绘越频繁闪烁越明显。双缓冲机制可以有效的消除这一现象。自Qt5版本后,QWidget控件已经能够自动处理闪烁问题,虽然在控件上直接绘图时不用再担心闪烁问题,但是双缓冲机制很多场合仍然有其用武之地。比如,当需绘制的内容比较复杂并且需要频繁刷新,或者每次只需要绘制整个控件的一小部分时,依然采用双缓冲机制是需要的。

以下通过介绍实现一个简单的绘图工具,来介绍双缓冲机制的实现。图像先绘制在pixmap上,再将整个pixmap绘制到中央部件的绘图区。

请添加图片描述

QMainWindow 对象作为主窗口,QToolBar 对象作为工具栏,QWidget 对象作为主窗口的中央窗体,也就是绘图区。通过对绘图区鼠标事件的重定义,来实现绘图功能。

请添加图片描述

实现步骤

工程创建

如下图创建工程,基类为QMainWindow,取消创建界面

请添加图片描述

右键工程添加以QWidget为基类的绘图显示区DrawWidget

请添加图片描述

绘图区实现

绘图区需要重定义鼠标事件mousePressEventmouseMoveEvent,重定义重绘事件paintEvent以及尺寸变化事件resizeEvent,以及一些参数的设置,画笔颜色画笔线宽画笔风格 参见QT基础之一文介绍QPainter绘制基础图形(画笔画刷设置,填充铺展渐变效果)_____主选项区域实现

drawwidget.h文件

class DrawWidget : public QWidget
{
    Q_OBJECT
public:
    explicit DrawWidget(QWidget *parent = nullptr);
    void mouseMoveEvent(QMouseEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;
    void paintEvent(QPaintEvent *event) override;
    void resizeEvent(QResizeEvent *event) override;
public slots:
    void SetPenStyle(Qt::PenStyle style); // 画笔风格
    void SetPenWidth(int penWidth); // 画笔线宽
    void SetPenColor(QColor color); // 画笔颜色
    void ClearAll(); // 清空所有
private:
    QPixmap *pix; // 绘制的图像
    QPoint startPos;
    QPoint endPos;
    Qt::PenStyle style;
    int penWidth;
    QColor color;
};

drawwidget.cpp文件

DrawWidget::DrawWidget(QWidget *parent) : QWidget(parent)
{
    setAutoFillBackground(true); // 背景色 设置
    setPalette(QPalette(Qt::white)); // QWidget设置白色为背景
    pix = new QPixmap(size()); // 绘图区同尺寸的 pixmap 用于接收绘制内容
    pix->fill(Qt::white); // 填充图像背景为白色
    setMinimumSize(600,400); // 绘制区窗体最小尺寸
}

void DrawWidget::mouseMoveEvent(QMouseEvent *event)
{
    // 鼠标移动 绘制移动的轨迹
    QPainter *painter = new QPainter; // QPainter对象
    QPen pen; // QPen 对象
    // 设置 pen 的属性
    pen.setStyle(style);
    pen.setWidth(penWidth);
    pen.setColor(color);
    // painter 无参数构造,使用 begin  参见 https://blog.csdn.net/kenfan1647/article/details/116266875
    painter->begin(pix); // 在调用begin()时,所有绘画工具设置(setPen(),setBrush()等)都将重置为默认值
    painter->setPen(pen);
    // 绘制从startPos 到鼠标当前位置的直线
    painter->drawLine(startPos,event->pos()); // 先在 pix 上绘制
    painter->end();
    startPos = event->pos();
    update(); // 再 重绘绘图区 将 pix 绘制到 控件
}

void DrawWidget::mousePressEvent(QMouseEvent *event)
{
    this->startPos = event->pos(); // 记录鼠标按下的起始位置
}

void DrawWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.drawPixmap(QPoint(0,0),*pix); // 再绘制 pix 到 绘图区, pix 和 绘图区 同尺寸
}

void DrawWidget::resizeEvent(QResizeEvent *event)
{
    if(width() != pix->width() || height() != pix->height()){ // 尺寸是否改变
        QPixmap *newPix = new QPixmap(size()); //新尺寸的 pix
        newPix->fill(Qt::white);
        QPainter painter(newPix);
        painter.drawPixmap(QPoint(0,0),*pix);  //  将 图像绘制到新 pix 上
        pix = newPix;
    }
    QWidget::resizeEvent(event);
}

void DrawWidget::SetPenStyle(Qt::PenStyle style)
{
    this->style = style;
}

void DrawWidget::SetPenWidth(int penWidth)
{
    this->penWidth = penWidth;
}

void DrawWidget::SetPenColor(QColor color)
{
    this->color = color;
}

void DrawWidget::ClearAll()
{
    // 将 新的 pix 替代以前的
    QPixmap *clearPix = new QPixmap(size()); //新尺寸的 pix
    clearPix->fill(Qt::white);
    pix = clearPix;
    update();
}
注意Painter的无参数构造
QPainter *painter = new QPainter; // QPainter对象
painter->begin(pix); // 在调用begin()时,所有绘画工具设置(setPen(),setBrush()等)都将重置为默认值
painter->setPen(pen);
// 绘制从startPos 到鼠标当前位置的直线
painter->drawLine(startPos,event->pos()); // 在 pix 上绘制
painter->end();

可参见 https://blog.csdn.net/kenfan1647/article/details/116266875

主选项区域实现

创建了一个QToolBar,用于绘制区各种选项的选择。

mainwindow.h文件

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
public slots:
    void PenColorSelect(); // penColorBtn 按钮响应 用于选择颜色
    void SetPenStyle(); // 设置画笔style到 中心绘制区
private:
    void createToolBar(); // 创建工具条
    DrawWidget *drawWidget; // 绘制区
    QToolBar *toolBar;
    QLabel *penStyleLabel; // 画笔风格
    QComboBox *penStyleComboBox;
    QLabel *penWidthLabel; // 画笔线宽
    QSpinBox *penWidthSpinBox;
    QToolButton *penColorBtn; // 画笔颜色选择
    QToolButton *clearBtn;
};

mainwindow.cpp文件

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    drawWidget = new DrawWidget;
    setCentralWidget(drawWidget); // 设置drawWidget 为中央部件
    createToolBar(); // 创建工具条
    setMinimumSize(600,400); // 设置主窗口 最小尺寸 与 drawWidget 同步
    SetPenStyle(); // 设置画笔style
    // 设置 画笔初始线宽
    drawWidget->SetPenWidth(penWidthSpinBox->value());
    // 设置 画笔初始颜色
    drawWidget->SetPenColor(Qt::black);
}

MainWindow::~MainWindow()
{
}

void MainWindow::PenColorSelect()
{
    QColor color = QColorDialog::getColor(Qt::black,this);
    if(color.isValid()){
        drawWidget->SetPenColor(color);
        QPixmap pix(20,20);
        pix.fill(color);
        penColorBtn->setIcon(QIcon(pix));
    }
}

void MainWindow::SetPenStyle()
{
    drawWidget->SetPenStyle(static_cast<Qt::PenStyle>(penStyleComboBox->
                itemData(penStyleComboBox->currentIndex(),Qt::UserRole).toInt()));
}

void MainWindow::createToolBar()
{
    toolBar = addToolBar(tr("Tool"));

    // 画笔风格
    penStyleLabel = new QLabel(tr("画笔风格:"));
    penStyleComboBox = new QComboBox;
    penStyleComboBox->addItem(tr("SolidLine"),static_cast<int>(Qt::SolidLine));
    penStyleComboBox->addItem(tr("DashLine"),static_cast<int>(Qt::DashLine));
    penStyleComboBox->addItem(tr("DotLine"),static_cast<int>(Qt::DotLine));
    penStyleComboBox->addItem(tr("DashDotLine"),static_cast<int>(Qt::DashDotLine));
    penStyleComboBox->addItem(tr("DashDotDotLine"),static_cast<int>(Qt::DashDotDotLine));
    penStyleComboBox->addItem(tr("CustomDashLine"),static_cast<int>(Qt::CustomDashLine));
    connect(penStyleComboBox,SIGNAL(activated(int)),this,SLOT(SetPenStyle()));

    // 画笔线宽
    penWidthLabel = new QLabel(tr("线宽:"));
    penWidthSpinBox = new QSpinBox;
    connect(penWidthSpinBox,SIGNAL(valueChanged(int)),drawWidget,SLOT(SetPenWidth(int)));

    // 颜色 选择
    penColorBtn = new QToolButton; // 选择按钮
    QPixmap pixmap(20,20);
    pixmap.fill(Qt::black);
    penColorBtn->setIcon(pixmap);
    connect(penColorBtn,SIGNAL(clicked()),this,SLOT(PenColorSelect()));

    // 清除按钮
    clearBtn = new QToolButton;
    clearBtn->setText(tr("清空"));
    connect(clearBtn,SIGNAL(clicked()),drawWidget,SLOT(ClearAll()));

    toolBar->addWidget(penStyleLabel);
    toolBar->addWidget(penStyleComboBox);
    toolBar->addWidget(penWidthLabel);
    toolBar->addWidget(penWidthSpinBox);
    toolBar->addWidget(penColorBtn);
    toolBar->addWidget(clearBtn);
}

希望我的文章对于大家有帮助,由于个人能力的局限性,文中可能存在一些问题,欢迎指正、补充!

  • 7
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
要在 Qt C++ 中使用双缓冲技术进行绘图,可以按照以下步骤进行操作: 1. 创建一个自定义的 QWidget 子类,例如 MyPaintWidget,用于绘制图形。 ```cpp class MyPaintWidget : public QWidget { Q_OBJECT public: explicit MyPaintWidget(QWidget *parent = nullptr); protected: void paintEvent(QPaintEvent *event) override; private: QImage buffer; // 双缓冲区 }; MyPaintWidget::MyPaintWidget(QWidget *parent) : QWidget(parent) { // 设置窗口属性,启用双缓冲绘制 setAttribute(Qt::WA_PaintOnScreen); setAttribute(Qt::WA_NoSystemBackground); } void MyPaintWidget::paintEvent(QPaintEvent *event) { // 创建绘图对象,并将其绑定到双缓冲QPainter painter(&buffer); // 在双缓冲区上进行绘制 painter.fillRect(rect(), Qt::white); // 绘制白色背景 painter.setPen(Qt::black); // 设置画笔颜色为黑色 painter.drawLine(0, 0, width(), height()); // 绘制一条线段 // 将双缓冲区的内容绘制到窗口上 painter.begin(this); painter.drawImage(0, 0, buffer); painter.end(); } ``` 2. 在主窗口的构造函数中创建 MyPaintWidget 对象,并将其添加到布局中。 ```cpp MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { MyPaintWidget *paintWidget = new MyPaintWidget(this); setCentralWidget(paintWidget); } ``` 通过以上步骤,我们创建了一个自定义的 QWidget 子类,并在其中实现了双缓冲绘图的功能。在 paintEvent 函数中,首先将绘图操作绘制在双缓冲区(buffer)上,然后再将双缓冲区的内容绘制到窗口上。这样可以避免图形闪烁的问题,并提高绘图的效率。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值