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

Qt QPainter使用方法

Qt QPainter使用方法

1. 重绘的实现流程

重绘需要重写控件的:void paintEvent(QPaintEvent *event) override 虚函数

2. QPainter的构造&初始化问题

2.1 先构造,再调用 begin() 初始化

备注:此处的初始化指的是调用 begin 函数 传入(QPaintDevice *device)

构造和初始化都在 paintEvent 函数里面

void MyWidget::paintEvent(QPaintEvent *)
{
    QPainter p;					// 构造
    p.begin(this);				// 初始化
    p.drawLine(...);        // drawing code
    p.end();
}

构造函数和初始化(begin函数)分开,但是一定要在paintEvent函数里面初始化;

// .h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QPainter>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

protected:
    void paintEvent(QPaintEvent *event) override;

private:
    Ui::MainWindow *ui;
    QPainter* m_pPainter;
};

#endif // MAINWINDOW_H

// .cpp
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include <QPainter>
#include <QtDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , m_pPainter(new QPainter())
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    if (nullptr != m_pPainter)
    {
        delete m_pPainter;
        m_pPainter = nullptr;
    }

    delete ui;
}

void MainWindow::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);

	// 一定要在paintEvent函数里面初始化
    bool bRtn = m_pPainter->begin(this);

    if (bRtn)
    {
        qDebug() << "Initialize Successful!";
    }
    else
    {
        qDebug() << "Initialize Failed!";
    }

    m_pPainter->setPen(QPen(QColor(Qt::red)));
    m_pPainter->drawLine(0, 0, size().width(), size().height());
    m_pPainter->end();
}

2.2 构造&初始化一起

通过构造函数传入 QPaintDevice *device,免去了begin函数的调用,因为构造函数没有执行反馈的结果,所以不利于判断是否初始化成功;

void MyWidget::paintEvent(QPaintEvent *)
{
    QPainter p(this);
    p.drawLine(...);        // drawing code
}

为何要把初始化过程放在paintEvent函数里面?
QPainter 类构造函数和begin函数手册中说明如下:
QPainter::QPainter(QPaintDevice *device)
Constructs a painter that begins painting the paint device immediately.
This constructor is convenient for short-lived painters, e.g. in a QWidget::paintEvent() and should be used only once. The constructor calls begin() for you and the QPainter destructor automatically calls end().
Here’s an example using begin() and end():

void MyWidget::paintEvent(QPaintEvent *)
{
    QPainter p;
    p.begin(this);
    p.drawLine(...);        // drawing code
    p.end();
}

The same example using this constructor:

void MyWidget::paintEvent(QPaintEvent *)
{
    QPainter p(this);
    p.drawLine(...);        // drawing code
}

Since the constructor cannot provide feedback when the initialization of the painter failed you should rather use begin() and end() to paint on external devices, e.g. printers.

bool QPainter::begin(QPaintDevice *device)
Begins painting the paint device and returns true if successful; otherwise returns false.
Notice that all painter settings (setPen(), setBrush() etc.) are reset to default values when begin() is called.
The errors that can occur are serious problems, such as these:

  painter->begin(0); // impossible - paint device cannot be 0

  QPixmap image(0, 0);
  painter->begin(&image); // impossible - image.isNull() == true;

  painter->begin(myWidget);
  painter2->begin(myWidget); // impossible - only one painter at a time

Note that most of the time, you can use one of the constructors instead of begin(), and that end() is automatically done at destruction.
Warning: A paint device can only be painted by one painter at a time.
Warning: Painting on a QImage with the format QImage::Format_Indexed8 is not supported.

  文字比较多,我们找出重点一句:A paint device can only be painted by one painter at a time. 被绘制的设备只能在同一时间被一个绘制者绘制,翻译有点绕口,说人话就是:一幅画在某一时间只能有一个画家在上面画画;
  只有paintEvent函数执行时才能换个画家,因为QWidget在自我绘制窗口时另一个画家在占用,所以在控件在自我绘制时,调用QPainter的begin函数传入this时会失败;
  当用户绘制事件触发时,控件会首先调用QPainter::end()释放出权限, 然后进入paintEvent ()虚函数;QPainter的初始化只能放在paintEvent ()函数之中。

3. 利用双缓冲机制绘图

3.1 实现步骤

相当于让QPixmap作为一个缓冲区;

  • 将需要绘制的图形绘在一个QPixmap对象上
  • 触发界面重绘事件
  • 在paintEvent函数中绘制QPixmap

3.2 程序示例

键盘按下,图像移动一下,涉及到的技术栈:键盘事件,重绘等

// .h
#ifndef PAINTWIDGET_H
#define PAINTWIDGET_H

#include <QWidget>
#include <memory>

namespace Ui {
class PaintWidget;
}

using std::unique_ptr;

class PaintWidget : public QWidget
{
    Q_OBJECT

public:
    explicit PaintWidget(QWidget *parent = nullptr);
    ~PaintWidget();

protected:
    void keyPressEvent(QKeyEvent *event) override;
    void paintEvent(QPaintEvent *event) override;

private:
    void InitializeWidget();
    void DrawPixmap();

private:
    Ui::PaintWidget *ui;
    unique_ptr<QPixmap> m_upPixmap;     // 作为一个绘图设备是,使用双缓冲机制实现图形的绘制

    int m_nStartX;
    int m_nStartY;
    int m_nStep;

    int m_nWidth;
    int m_nHeight;

    QImage m_Image;
};

#endif // PAINTWIDGET_H


// .cpp
#include "PaintWidget.h"
#include "ui_PaintWidget.h"
#include <QKeyEvent>
#include <QPaintEvent>
#include <QPen>
#include <QPainter>

PaintWidget::PaintWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::PaintWidget)
{
    ui->setupUi(this);
    InitializeWidget();
}

PaintWidget::~PaintWidget()
{
    delete ui;
}

void PaintWidget::keyPressEvent(QKeyEvent *event)
{
    int nStep = 0;

    if (Qt::ControlModifier == event->modifiers())
    {
        nStep = 1;
    }
    else
    {
        m_nStartX = m_nStartX - m_nStartX % m_nStep;
        m_nStartY = m_nStartY - m_nStartY % m_nStep;

        nStep = m_nStep;
    }

    if (Qt::Key_Left == event->key())
        m_nStartX = (m_nStartX - nStep) < 0 ? 0 : (m_nStartX - nStep);

    if (Qt::Key_Right == event->key())
        m_nStartX = (m_nStartX + nStep + m_Image.width()) > m_nWidth ? (m_nStartX + m_Image.width()) : (m_nStartX + nStep);

    if (Qt::Key_Up == event->key())
        m_nStartY = (m_nStartY - nStep) < 0 ? 0 : (m_nStartY - nStep);

    if (Qt::Key_Down == event->key())
        m_nStartY = (m_nStartY + nStep + m_Image.height()) > m_nHeight ? (m_nStartY + m_Image.height()) : (m_nStartY + nStep);

    if (Qt::Key_Home == event->key())
    {
        m_nStartX = 0;
        m_nStartY = 0;
    }

    if (Qt::Key_End == event->key())
    {
        m_nStartX = m_nWidth - m_Image.width();
        m_nStartY = m_nHeight - m_Image.height();
    }

    DrawPixmap();
    update();           // 触发重绘事件
}

void PaintWidget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);

    QPainter painter;
    painter.begin(this);
    painter.drawPixmap(QPoint(0, 0), *m_upPixmap);          // 绘制 Pixmap
    painter.end();

    return QWidget::paintEvent(event);
}

void PaintWidget::InitializeWidget()
{
    setAutoFillBackground(true);

    QPalette palette = this->palette();
    palette.setColor(QPalette::Window, Qt::white);
    setPalette(palette);

    setMinimumSize(800, 800);
    setMaximumSize(800, 800);

    m_nWidth = size().width();
    m_nHeight = size().height();

    m_upPixmap = std::make_unique<QPixmap>(m_nWidth, m_nHeight);
    m_upPixmap->fill(Qt::white);

    m_Image.load(":/image.png");

    m_nStartX = 100;
    m_nStartY = 100;
    m_nStep = 20;

    DrawPixmap();
}


// 先将需要绘制的图像绘制在 Pixmap上面,后面再在 paintEvent 函数里面绘制 Pixmap
void PaintWidget::DrawPixmap()
{
    m_upPixmap->fill(Qt::white);
    unique_ptr<QPainter> upPainter = std::make_unique<QPainter>();

    QPen pen(Qt::DotLine);

    for (int i = m_nStep; i < m_nWidth; i += m_nStep)
    {
        upPainter->begin(m_upPixmap.get());
        upPainter->setPen(pen);
        upPainter->drawLine(QPoint(i, 0), QPoint(i, m_nHeight));
        upPainter->end();
    }

    for (int i = m_nStep; i < m_nHeight; i += m_nStep)
    {
        upPainter->begin(m_upPixmap.get());
        upPainter->setPen(pen);
        upPainter->drawLine(QPoint(0, i), QPoint(m_nWidth, i));
        upPainter->end();
    }

    upPainter->begin(m_upPixmap.get());
    upPainter->drawImage(QPoint(m_nStartX, m_nStartY), m_Image);
    upPainter->end();
}

http://www.kler.cn/a/611532.html

相关文章:

  • Qt事件处理(处理鼠标事件、键盘事件、定时器事件、窗口移动和大小变化事件)
  • Postman下载安装(Windows 11 专业版)
  • 【递归与动态规划(DP) C/C++】(1)递归 与 动态规划(DP)
  • 数据结构5(初):续写排序
  • 手搓全自动文章多平台发布系统:2、重要模块的设计
  • 国产化适配 - YashanDB、达梦数据库与MySQL 的兼容性及技术选型对比分析
  • R语言交互项-formula
  • 【C++】STL库_list 的模拟实现
  • 大数据学习栈记——HBase操作(shell java)
  • Couchbase存储引擎Magma和Couchstore
  • Stable Diffusion绘画插件(ControlNet )
  • Eclipse Debug 调试
  • 汇编(六)——汇编语言程序格式及MASM
  • OpenGL绘制文本
  • Vue的实例
  • Three.js 快速入门教程【十八】射线拾取模型——鼠标点击屏幕选中模型或物体
  • 【AI大模型】搭建本地大模型GPT-J:详细步骤及常见问题
  • 计算机视觉中的椭圆带权平均算法全解析
  • 【NLP 44、实践 ⑪ 用Bert模型结构实现自回归语言模型的训练】
  • Docker技术系列文章,第七篇——Docker 在 CI/CD 中的应用