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();
}