Qt提供了强大的2D绘图功能,主要通过QPainter
类和相关工具类实现。
核心内容
基本绘图系统
Qt的绘图系统基于:
-
QPainter - 执行绘图操作的主要类
-
QPaintDevice - 绘图设备的抽象基类(如QWidget, QImage, QPixmap等)
-
QPaintEngine - 提供不同设备的具体实现
基本绘图步骤
在paint事件中绘图(对于QWidget派生类):
void MyWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this); // 创建QPainter对象,指定绘图设备
// 设置画笔和画刷
painter.setPen(Qt::blue);
painter.setBrush(Qt::yellow);
// 绘制图形
painter.drawRect(10, 10, 100, 100);
painter.drawEllipse(50, 50, 100, 100);
// 绘制文本
painter.drawText(10, 150, "Hello Qt!");
}
///绘制饼图
void drawPieChart(QPainter &painter, const QRect &rect, const QMap<QString, double> &data)
{
painter.save();
painter.setRenderHint(QPainter::Antialiasing);
double total = 0.0;
for(auto value : data.values()) {
total += value;
}
int startAngle = 0;
int colorIndex = 0;
const QList<QColor> colors = {Qt::red, Qt::blue, Qt::green, Qt::yellow, Qt::cyan};
for(auto it = data.begin(); it != data.end(); ++it) {
double percentage = it.value() / total;
int spanAngle = static_cast<int>(percentage * 360 * 16);
painter.setBrush(colors[colorIndex++ % colors.size()]);
painter.drawPie(rect, startAngle, spanAngle);
startAngle += spanAngle;
}
painter.restore();
}
///绘制柱状图
void drawBarChart(QPainter &painter, const QRect &area, const QMap<QString, double> &data)
{
painter.save();
painter.setRenderHint(QPainter::Antialiasing);
int barWidth = area.width() / (data.size() * 2);
int x = area.left() + barWidth/2;
double maxValue = *std::max_element(data.begin(), data.end());
QFontMetrics fm(painter.font());
int textHeight = fm.height();
for(auto it = data.begin(); it != data.end(); ++it) {
int barHeight = static_cast<int>((it.value() / maxValue) * area.height());
QRect bar(x, area.bottom() - barHeight, barWidth, barHeight);
painter.setBrush(QColor::fromHsv(360 * x / area.width(), 255, 200));
painter.drawRect(bar);
// 绘制标签
painter.drawText(x - barWidth/2, area.bottom() + textHeight, it.key());
x += barWidth * 2;
}
painter.restore();
}
常用绘图操作
画笔(QPen)控制
QPen pen;
pen.setColor(QColor(255, 0, 0)); // 红色
pen.setWidth(3); // 3像素宽
pen.setStyle(Qt::DashLine); // 虚线样式
pen.setCapStyle(Qt::RoundCap); // 线端圆角
pen.setJoinStyle(Qt::RoundJoin); // 连接点圆角
painter.setPen(pen);
画刷(QBrush)控制
QBrush brush;
brush.setColor(Qt::green); // 绿色填充
brush.setStyle(Qt::Dense4Pattern); // 填充样式
painter.setBrush(brush);
// 渐变填充
QLinearGradient gradient(0, 0, 100, 100);
gradient.setColorAt(0, Qt::white);
gradient.setColorAt(1, Qt::black);
painter.setBrush(QBrush(gradient));
基本图形绘制
// 直线
painter.drawLine(QPoint(10, 10), QPoint(100, 100));
// 矩形
painter.drawRect(QRect(10, 10, 100, 50));
// 圆角矩形
painter.drawRoundedRect(QRect(10, 70, 100, 50), 10, 10);
// 椭圆/圆
painter.drawEllipse(QPoint(150, 50), 50, 30);
// 多边形
QPolygon polygon;
polygon << QPoint(10, 10) << QPoint(100, 10) << QPoint(100, 100);
painter.drawPolygon(polygon);
// 路径
QPainterPath path;
path.moveTo(20, 20);
path.lineTo(100, 20);
path.cubicTo(150, 20, 150, 70, 100, 70);
painter.drawPath(path);
文本绘制
painter.setFont(QFont("Arial", 12));
painter.drawText(10, 100, "Qt绘图示例");
// 带格式的文本
QRect textRect(10, 120, 200, 100);
painter.drawText(textRect, Qt::AlignCenter | Qt::TextWordWrap,
"这是一段可以自动换行的长文本...");
图像绘制
QPixmap pixmap(":/images/example.png");
painter.drawPixmap(10, 10, pixmap);
// 缩放绘制
painter.drawPixmap(QRect(50, 50, 100, 100), pixmap);
坐标系统与变换
Qt提供了灵活的坐标系统控制:
// 平移
painter.translate(100, 100);
// 缩放
painter.scale(2.0, 1.5);
// 旋转
painter.rotate(45);
// 保存和恢复状态
painter.save(); // 保存当前状态
painter.translate(50, 50);
// 绘制操作...
painter.restore(); // 恢复到保存的状态
高级特性
双缓冲绘图
void MyWidget::paintEvent(QPaintEvent *)
{
QPixmap pixmap(size());
pixmap.fill(Qt::white);
QPainter bufferPainter(&pixmap);
// 在pixmap上绘制...
QPainter painter(this);
painter.drawPixmap(0, 0, pixmap);
}
自定义图形项(QGraphicsItem)
class MyItem : public QGraphicsItem {
public:
QRectF boundingRect() const override {
return QRectF(-50, -50, 100, 100);
}
void paint(QPainter *painter,
const QStyleOptionGraphicsItem *,
QWidget *) override {
painter->setBrush(Qt::green);
painter->drawEllipse(-50, -50, 100, 100);
}
};
图形视图框架(Graphics View Framework)
对于复杂场景,可以使用QGraphicsView/QGraphicsScene框架:
QGraphicsScene *scene = new QGraphicsScene(this);
QGraphicsView *view = new QGraphicsView(scene);
// 添加图形项
scene->addEllipse(0, 0, 100, 100);
scene->addText("Graphics View 示例");
// 显示视图
view->show();
性能优化技巧
-
尽量减少paintEvent中的计算,减少绘图区域:
-
使用双缓冲减少闪烁
-
对于静态内容,考虑缓存到QPixmap
-
使用QGraphicsView框架处理大量图形项
-
合理使用QPainter状态保存/恢复
双缓冲减少Qt绘图闪烁
双缓冲技术是解决绘图闪烁问题的有效方法,其核心思想是在内存中完成所有绘图操作,然后一次性将结果绘制到屏幕上。
基本原理
-
传统绘图:直接在屏幕上绘制,每次绘制都可见,导致闪烁
-
双缓冲:
-
在内存中创建一个与屏幕绘制区域大小相同的缓冲图像
-
所有绘图操作先在缓冲图像上完成
-
最后将缓冲图像一次性绘制到屏幕上
-
Qt中的双缓冲实现
方法一:手动双缓冲(推荐)
void MyWidget::paintEvent(QPaintEvent *event)
{
// 1. 创建与窗口大小相同的缓冲图像
QPixmap pixmap(this->size());
pixmap.fill(Qt::transparent); // 或填充背景色
// 2. 在缓冲图像上绘图
QPainter bufferPainter(&pixmap);
// 设置抗锯齿
bufferPainter.setRenderHint(QPainter::Antialiasing, true);
// 在这里执行所有绘图操作
drawContent(bufferPainter);
// 3. 将缓冲图像绘制到窗口
QPainter painter(this);
painter.drawPixmap(0, 0, pixmap);
}
void MyWidget::drawContent(QPainter &painter)
{
// 实际的绘图代码
painter.setPen(Qt::blue);
painter.setBrush(Qt::yellow);
painter.drawRect(10, 10, 100, 100);
painter.drawEllipse(50, 50, 100, 100);
}
方法二:使用Qt自动双缓冲
Qt为QWidget提供了自动双缓冲属性:
MyWidget::MyWidget(QWidget *parent) : QWidget(parent)
{
// 启用自动双缓冲
setAttribute(Qt::WA_StaticContents); // 对于静态内容
setAttribute(Qt::WA_OpaquePaintEvent); // 不自动填充背景
}
注意:自动双缓冲在某些情况下可能不如手动双缓冲灵活。
双缓冲的最佳实践
-
缓冲图像大小管理:
void MyWidget::resizeEvent(QResizeEvent *event) { // 窗口大小改变时重新创建缓冲图像 m_buffer = QPixmap(size()); update(); // 触发重绘 }
-
部分更新优化:
void MyWidget::paintEvent(QPaintEvent *event) { if(m_buffer.isNull()) { m_buffer = QPixmap(size()); m_buffer.fill(Qt::white); } QPainter bufferPainter(&m_buffer); // 只更新需要重绘的区域 if(!event->region().isEmpty()) { bufferPainter.setClipRegion(event->region()); } // 绘图操作... QPainter painter(this); painter.drawPixmap(0, 0, m_buffer); }
-
复杂图形的缓存:
对于不经常变化的复杂图形,可以预先绘制到缓冲中:void MyWidget::updateComplexGraphics() { QPainter painter(&m_buffer); // 绘制复杂图形... update(); // 只触发屏幕更新 }
手动双缓冲完整示例
下面是一个完整的Qt手动双缓冲示例,展示如何通过双缓冲技术实现平滑的无闪烁绘图。
示例1:基本双缓冲实现
#include <QWidget>
#include <QPaintEvent>
#include <QPainter>
#include <QTimer>
class DoubleBufferWidget : public QWidget
{
Q_OBJECT
public:
DoubleBufferWidget(QWidget *parent = nullptr) : QWidget(parent)
{
// 设置固定大小
setFixedSize(400, 300);
// 初始化动画计时器
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, [this](){
m_angle += 1; // 每次增加1度
if(m_angle >= 360) m_angle = 0;
update(); // 触发重绘
});
timer->start(16); // ~60FPS
}
protected:
void paintEvent(QPaintEvent *event) override
{
// 1. 创建与窗口大小相同的缓冲图像
QPixmap buffer(size());
buffer.fill(Qt::white); // 填充白色背景
// 2. 在缓冲图像上绘图
QPainter bufferPainter(&buffer);
bufferPainter.setRenderHint(QPainter::Antialiasing, true);
// 绘制内容
drawRotatingRect(bufferPainter);
// 3. 将缓冲图像绘制到窗口
QPainter windowPainter(this);
windowPainter.drawPixmap(0, 0, buffer);
}
void resizeEvent(QResizeEvent *event) override
{
// 窗口大小改变时强制重绘
update();
}
private:
void drawRotatingRect(QPainter &painter)
{
// 设置中心点
QPoint center(width() / 2, height() / 2);
// 保存当前状态
painter.save();
// 移动到中心点并旋转
painter.translate(center);
painter.rotate(m_angle);
// 绘制旋转的矩形
painter.setPen(QPen(Qt::blue, 2));
painter.setBrush(Qt::green);
painter.drawRect(-50, -50, 100, 100);
// 恢复状态
painter.restore();
// 绘制中心点
painter.setPen(Qt::red);
painter.drawEllipse(center, 3, 3);
// 显示帧率信息
painter.drawText(10, 20, QString("双缓冲示例 - 角度: %1°").arg(m_angle));
}
int m_angle = 0;
};
示例2:更复杂的双缓冲实现(带部分更新)
#include <QWidget>
#include <QPaintEvent>
#include <QPainter>
#include <QMouseEvent>
#include <QVector>
#include <QPoint>
class DrawingWidget : public QWidget
{
Q_OBJECT
public:
DrawingWidget(QWidget *parent = nullptr) : QWidget(parent)
{
setAttribute(Qt::WA_StaticContents); // 优化静态内容
setMouseTracking(true); // 启用鼠标跟踪
// 初始化缓冲为透明
m_buffer = QPixmap(size());
m_buffer.fill(Qt::transparent);
}
protected:
void paintEvent(QPaintEvent *event) override
{
// 创建临时绘制器
QPainter painter(this);
// 如果缓冲大小与窗口大小不匹配,调整缓冲大小
if(m_buffer.size() != size()) {
QPixmap newBuffer(size());
newBuffer.fill(Qt::white);
QPainter tempPainter(&newBuffer);
tempPainter.drawPixmap(0, 0, m_buffer);
m_buffer = newBuffer;
}
// 绘制缓冲到窗口
painter.drawPixmap(0, 0, m_buffer);
// 如果有临时绘图(如橡皮筋效果)
if(m_isDrawing) {
painter.setPen(QPen(Qt::black, 2, Qt::DashLine));
painter.drawLine(m_startPoint, m_endPoint);
}
}
void mousePressEvent(QMouseEvent *event) override
{
if(event->button() == Qt::LeftButton) {
m_startPoint = event->pos();
m_endPoint = event->pos();
m_isDrawing = true;
}
}
void mouseMoveEvent(QMouseEvent *event) override
{
if(m_isDrawing) {
m_endPoint = event->pos();
update(); // 更新橡皮筋线
}
}
void mouseReleaseEvent(QMouseEvent *event) override
{
if(event->button() == Qt::LeftButton && m_isDrawing) {
m_endPoint = event->pos();
m_isDrawing = false;
// 在缓冲上绘制最终线条
QPainter bufferPainter(&m_buffer);
bufferPainter.setPen(QPen(Qt::black, 2));
bufferPainter.drawLine(m_startPoint, m_endPoint);
update();
}
}
void resizeEvent(QResizeEvent *event) override
{
// 保存原有内容
QPixmap newBuffer(size());
newBuffer.fill(Qt::white);
QPainter painter(&newBuffer);
painter.drawPixmap(0, 0, m_buffer);
m_buffer = newBuffer;
QWidget::resizeEvent(event);
}
private:
QPixmap m_buffer; // 双缓冲图像
QPoint m_startPoint; // 绘图起点
QPoint m_endPoint; // 绘图终点
bool m_isDrawing = false; // 是否正在绘图
};
示例3:带背景和前景的双缓冲
#include <QWidget>
#include <QPaintEvent>
#include <QPainter>
#include <QTimer>
#include <QDateTime>
class ClockWidget : public QWidget
{
Q_OBJECT
public:
ClockWidget(QWidget *parent = nullptr) : QWidget(parent)
{
setFixedSize(300, 300);
// 背景缓冲只需要生成一次
m_background = QPixmap(size());
drawBackground(m_background);
// 定时器每秒更新一次
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, [this](){
update(); // 只更新前景
});
timer->start(1000);
}
protected:
void paintEvent(QPaintEvent *event) override
{
// 1. 创建前景缓冲
QPixmap foreground(size());
foreground.fill(Qt::transparent);
// 2. 在前景缓冲上绘制动态内容
QPainter foregroundPainter(&foreground);
foregroundPainter.setRenderHint(QPainter::Antialiasing, true);
drawClockHands(foregroundPainter);
// 3. 将背景和前景合成到窗口
QPainter windowPainter(this);
windowPainter.drawPixmap(0, 0, m_background);
windowPainter.drawPixmap(0, 0, foreground);
}
private:
void drawBackground(QPixmap &pixmap)
{
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing, true);
// 绘制渐变背景
QRadialGradient gradient(width()/2, height()/2, width()/2);
gradient.setColorAt(0, Qt::white);
gradient.setColorAt(1, QColor(200, 200, 255));
painter.fillRect(rect(), gradient);
// 绘制时钟刻度
painter.setPen(QPen(Qt::black, 2));
painter.translate(width()/2, height()/2);
for(int i = 0; i < 12; ++i) {
painter.drawLine(0, -120, 0, -100);
painter.rotate(30.0);
}
}
void drawClockHands(QPainter &painter)
{
QTime time = QTime::currentTime();
painter.translate(width()/2, height()/2);
// 时针
painter.save();
painter.setPen(QPen(Qt::black, 4));
painter.rotate(30.0 * (time.hour() + time.minute() / 60.0));
painter.drawLine(0, 0, 0, -60);
painter.restore();
// 分针
painter.save();
painter.setPen(QPen(Qt::black, 2));
painter.rotate(6.0 * time.minute());
painter.drawLine(0, 0, 0, -80);
painter.restore();
// 秒针
painter.save();
painter.setPen(QPen(Qt::red, 1));
painter.rotate(6.0 * time.second());
painter.drawLine(0, 0, 0, -90);
painter.restore();
}
QPixmap m_background; // 静态背景缓冲
};
关键点总结
-
缓冲创建:在
paintEvent
中创建与窗口大小匹配的缓冲图像 -
绘图分离:所有绘图操作先在缓冲上完成
-
最终绘制:最后将缓冲图像一次性绘制到窗口
-
性能优化:
-
静态内容可以预先绘制到单独缓冲中
-
动态内容在每次重绘时更新
-
使用
setRenderHint(QPainter::Antialiasing, true)
提高绘制质量
-
-
大小变化处理:在
resizeEvent
中适当调整缓冲大小
这些示例展示了手动双缓冲的不同应用场景,你可以根据需要选择或组合这些技术来实现无闪烁的平滑绘图效果。
双缓冲的适用场景
-
频繁更新的动画
-
复杂的图形绘制
-
需要平滑过渡的效果
-
绘图操作耗时的场景
注意事项
-
内存使用:双缓冲会占用额外的内存,对于大尺寸窗口要特别注意
-
性能权衡:简单的绘图可能不需要双缓冲
-
与OpenGL结合:在QOpenGLWidget中,双缓冲由OpenGL本身处理
通过合理使用双缓冲技术,可以显著提高Qt绘图程序的视觉质量和用户体验。