二十二、简易画图工具


目录

一、设计需求

二、示例代码

三、代码解析

        1、setAutoFillBackground函数的作用

        2、resizeEvent被调用的时机

四、Qt5双缓冲机制

五、总结


一、设计需求

        实现一个简单的绘图工具,可以选择线型、线宽及颜色等基本要素。

二、示例代码

mainwindow.cpp

#include "mainwindow.h"
#include <QToolBar>
#include <QColorDialog>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    drawWidget =new DrawWidget;   	//新建一个DrawWidget对象
    setCentralWidget(drawWidget);	//新建的DrawWidget对象作为主窗口的中央窗体

    createToolBar();                //实现一个工具栏

    setMinimumSize(600,400);       	//设置主窗口的最小尺寸

    ShowStyle();                    //初始化线型,设置控件中的当前值作为初始值
    drawWidget->setWidth(widthSpinBox->value()); 	//初始化线宽
    drawWidget->setColor(Qt::black);             	//初始化颜色
}

MainWindow::~MainWindow()
{
    
}
/**
 * @brief MainWindow::createToolBar
 */
void MainWindow::createToolBar()
{
    QToolBar *toolBar = addToolBar("Tool");   	//为主窗口新建一个工具栏对象

    styleLabel =new QLabel(tr("线型风格:"));   	//创建线型选择控件
    styleComboBox =new QComboBox;
    styleComboBox->addItem(tr("SolidLine"),static_cast<int>(Qt::SolidLine));
    styleComboBox->addItem(tr("DashLine"),static_cast<int>(Qt::DashLine));
    styleComboBox->addItem(tr("DotLine"),static_cast<int>(Qt::DotLine));
    styleComboBox->addItem(tr("DashDotLine"),static_cast<int>(Qt::DashDotLine));
    styleComboBox->addItem(tr("DashDotDotLine"),static_cast<int>(Qt::DashDotDotLine));
    connect(styleComboBox,SIGNAL(activated(int)),this,SLOT(ShowStyle())); //关联相应的槽函数

    widthLabel =new QLabel(tr("线宽:"));    		//创建线宽选择控件
    widthSpinBox =new QSpinBox;
    connect(widthSpinBox,SIGNAL(valueChanged(int)),drawWidget,SLOT(setWidth(int)));
    colorBtn =new QToolButton;                  //创建颜色选择控件
    QPixmap pixmap(20,20);
    pixmap.fill(Qt::black);
    colorBtn->setIcon(QIcon(pixmap));
    connect(colorBtn,SIGNAL(clicked()),this,SLOT(ShowColor()));

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

    toolBar->addWidget(styleLabel);
    toolBar->addWidget(styleComboBox);
    toolBar->addWidget(widthLabel);
    toolBar->addWidget(widthSpinBox);
    toolBar->addWidget(colorBtn);
    toolBar->addWidget(clearBtn);
}

void MainWindow::ShowStyle()
{
    drawWidget->setStyle(styleComboBox->itemData(styleComboBox->currentIndex(),Qt::UserRole).toInt());
}
void MainWindow::ShowColor()
{
    QColor color = QColorDialog::getColor(static_cast<int>(Qt::black),this);	//使用标准颜色对话框QColorDialog获得一个颜色值
    if(color.isValid())
    {
        drawWidget->setColor(color);
        QPixmap p(20,20);
        p.fill(color);
        colorBtn->setIcon(QIcon(p));
    }
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QToolButton>
#include <QLabel>
#include <QComboBox>
#include <QSpinBox>
#include "drawwidget.h"

class MainWindow : public QMainWindow
{
    Q_OBJECT
    
public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();
    void createToolBar();
public slots:
    void ShowStyle();
    void ShowColor();
private:
    DrawWidget *drawWidget;
    QLabel *styleLabel;
    QComboBox *styleComboBox;
    QLabel *widthLabel;
    QSpinBox *widthSpinBox;
    QToolButton *colorBtn;
    QToolButton *clearBtn;
};

#endif // MAINWINDOW_H

drawwidget.cpp

#include "drawwidget.h"
#include <QtGui>
#include <QPen>

DrawWidget::DrawWidget(QWidget *parent) :
    QWidget(parent)
{
    setAutoFillBackground(true);    //对窗体背景色的设置,填充背景色的一种方式
    setPalette(QPalette(Qt::white));

    pix =new QPixmap(size());      	//此QPixmap对象用来准备随时接收绘制的内容
    pix->fill(Qt::white);           //填充背景色为白色
    setMinimumSize(600,400);      	//设置绘制区窗体的最小尺寸
}

/**
 * @brief DrawWidget::setStyle 接收主窗体传来的线型风格参数
 * @param s
 */
void DrawWidget::setStyle(int s)
{
    style = s;
}

/**
 * @brief DrawWidget::setWidth 接收主窗体传来的线宽参数值
 * @param w
 */
void DrawWidget::setWidth(int w)
{
    weight = w;
}

/**
 * @brief DrawWidget::setColor 接收主窗体传来的画笔颜色值
 * @param c
 */
void DrawWidget::setColor(QColor c)
{
    color = c;
}
/**
 * @brief DrawWidget::mousePressEvent
 * @param e
 * @note  重新定义鼠标按下事件,记录当前鼠标的位置
 */
void DrawWidget::mousePressEvent(QMouseEvent *e)
{
    startPos = e->pos();
}
/**
 * @brief DrawWidget::mouseMoveEvent 重写鼠标移动事件
 * @param e
 */
void DrawWidget::mouseMoveEvent(QMouseEvent *e)
{
    //创建Qpainter对象
    QPainter *painter = new QPainter;

    QPen pen;
    pen.setStyle((Qt::PenStyle)style);
    pen.setWidth(weight);
    pen.setColor(color);

    //开始对绘画设备进行绘制
    painter->begin(pix);
    painter->setPen(pen);
    painter->drawLine(startPos,e->pos());
    painter->end();

    //更新鼠标当前位置,为下次绘制做准备
    startPos =e->pos();
    //触发paintEvent,重新绘制窗体
    update();
}
/**
 * @brief DrawWidget::paintEvent 重写绘画事件
 */
void DrawWidget::paintEvent(QPaintEvent *)
{
    //构造函数调用begin(),析构函数调用end()
    QPainter painter(this);
    painter.drawPixmap(QPoint(0,0),*pix);
}

/**
 * @brief DrawWidget::resizeEvent
 * @param event
 */
void DrawWidget::resizeEvent(QResizeEvent *event)
{
    //调整绘制区域的大小
    if(height()>pix->height()||width()>pix->width())
    {
        QPixmap *newPix = new QPixmap(size());
        newPix->fill(Qt::white);
        QPainter p(newPix);
        p.drawPixmap(QPoint(0,0),*pix);
        pix = newPix;
    }
    //完成其余的操作
    QWidget::resizeEvent(event);
}

/**
 * @brief DrawWidget::clear
 */
void DrawWidget::clear()
{
    QPixmap *clearPix =new QPixmap(size());
    clearPix->fill(Qt::white);
    pix = clearPix;
    update();
}

 drawwidget.h

#ifndef DRAWWIDGET_H
#define DRAWWIDGET_H

#include <QWidget>
#include <QtGui>
#include <QMouseEvent>
#include <QPaintEvent>
#include <QResizeEvent>
#include <QColor>
#include <QPixmap>
#include <QPoint>
#include <QPainter>
#include <QPalette>

class DrawWidget : public QWidget
{
    Q_OBJECT
public:
    explicit DrawWidget(QWidget *parent = 0);
    void mousePressEvent(QMouseEvent *);
    void mouseMoveEvent(QMouseEvent *);
    void paintEvent(QPaintEvent *);
    void resizeEvent(QResizeEvent *);

signals:
    
public slots:
    void setStyle(int);
    void setWidth(int);
    void setColor(QColor);
    void clear();
private:
    QPixmap *pix;
    QPoint startPos;
    QPoint endPos;
    int style;
    int weight;
    QColor color;
};

#endif // DRAWWIDGET_H

main.cpp

#include "mainwindow.h"
#include <QApplication>
#include <QFont>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QFont font("ZYSong18030",12);
    a.setFont(font);
    MainWindow w;
    w.show();
    
    return a.exec();
}

三、代码解析

        1、setAutoFillBackground函数的作用

        在 Qt 中,setAutoFillBackground 函数用于控制窗口部件(如 QWidget)的背景是否自动填充。这个函数的作用主要与窗口部件的背景颜色或背景图像有关。

        如果设置为 true,Qt 会自动填充窗口部件的背景颜色或背景图像。如果设置为 false,则不会自动填充背景,背景颜色或图像的处理需要由程序员手动管理(在paintEvent中绘制背景)。

        如果你在 paintEvent 中手动绘制背景,可能会选择禁用自动填充背景。在这种情况下,你可以通过 setAutoFillBackground(false) 来避免重复的背景填充操作。

#include <QApplication>
#include <QWidget>
#include <QPainter>

class CustomWidget : public QWidget {
public:
    CustomWidget(QWidget *parent = nullptr) : QWidget(parent) {
        // 禁用自动填充背景
        setAutoFillBackground(false);
        // 设置调色板背景颜色
        QPalette palette = this->palette();
        palette.setColor(QPalette::Background, Qt::red);
        setPalette(palette);
    }

protected:
    void paintEvent(QPaintEvent *event) override {
        // 手动绘制背景
        QPainter painter(this);
        painter.fillRect(rect(), palette().background());
        // 绘制其他内容
        painter.setBrush(Qt::blue);
        painter.drawRect(50, 50, 100, 100);
    }
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    CustomWidget widget;
    widget.resize(200, 200);
    widget.show();
    return app.exec();
}

        2、resizeEvent被调用的时机

        在 Qt 中,resizeEvent 是一个虚拟函数,用于处理窗口部件(QWidget)的大小调整事件。resizeEvent 在窗口部件大小发生变化后被调用。

        在 resizeEvent 函数中,直接调用 width() 和 height() 确实能够获取到最新的窗口部件的宽度和高度。原因如下:

  • resizeEvent 调用时机:resizeEvent 在窗口部件的尺寸发生变化后被调用,此时窗口部件已经调整到了新的尺寸。
  • width() 和 height() 方法:这些方法返回的是窗口部件当前的宽度和高度。因为 resizeEvent 是在尺寸调整完成后触发的,所以这些方法在 resizeEvent 中返回的是调整后的最新尺寸。

四、Qt5双缓冲机制

        Qt 的双缓冲机制是一种用于提高图形界面绘制性能和减少闪烁现象的技术。在许多图形程序中,直接在屏幕上绘制可能会导致不平滑的视觉效果,因为图形操作可能会在不同的时间点进行,从而导致图像的部分更新和画面闪烁。

双缓冲的工作原理:
        双缓冲机制的基本思想是使用两个缓冲区来缓冲图形的绘制过程:

  • 前台缓冲区:这是用户实际看到的缓冲区,显示在屏幕上。
  • 后台缓冲区:这是一个隐藏的缓冲区,用于绘制即将显示的内容。

工作流程:

  • 绘制到后台缓冲区:所有的绘图操作先在后台缓冲区中完成。这意味着用户在后端进行绘制操作时并不会看到任何变化。
  • 交换缓冲区:一旦后台缓冲区的内容准备好,Qt 会进行缓冲区交换,将后台缓冲区的内容切换到前台缓冲区,用户就可以看到新的内容。
  • 清空后台缓冲区:在交换后的下一帧,后台缓冲区将被清空准备下一次绘制。

实现:

  • 重写 paintEvent 方法:在自定义的 QWidget 或 QGraphicsView 中,你可以重写 paintEvent 方法进行绘制。
  • 使用 QPixmap 或 QImage 作为后台缓冲区:你可以创建一个 QPixmap 或 QImage,先在这个图像上绘制所有内容,再将其绘制到窗口中。

示例代码:

#include <QApplication>
#include <QWidget>
#include <QPainter>

class DoubleBufferedWidget : public QWidget {
public:
    DoubleBufferedWidget(QWidget *parent = nullptr) : QWidget(parent) {
        setFixedSize(400, 300);
    }

protected:
    void paintEvent(QPaintEvent *event) override {
        // 创建一个 QPixmap 作为后台缓冲区
        QPixmap buffer(size());
        buffer.fill(Qt::white); // 用白色填充

        // 在后台缓冲区上进行绘制
        QPainter painter(&buffer);
        painter.setBrush(Qt::blue);
        painter.drawRect(50, 50, 100, 100); // 绘制一个蓝色矩形

        // 将后台缓冲区内容绘制到窗口
        QPainter windowPainter(this);
        windowPainter.drawPixmap(0, 0, buffer);
    }
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    DoubleBufferedWidget window;
    window.show();
    return app.exec();
}

优点:

  • 减少闪烁:通过在后台绘制,可以避免用户在屏幕上看到逐帧的绘制过程。
  • 提高性能:在某些情况下,减少重绘的次数也可以提高应用程序的运行效率。

五、总结

        双缓冲机制是 Qt 中一项重要的性能优化技术,对于需要频繁更新界面的应用程序,特别是动画和图形密集型的应用,使用双缓冲可以显著提升用户体验。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值