Qt 图形系统中的关键角色
QPainter
- Qt 中的画家,能够绘制各种基础图形
- 拥有绘图所需的画笔 (QPen),画刷 (QBrush),字体 (QFont)
QPainterDevice
- Qt 中的画布,画家 (QPainter) 的绘画板
- 所有的 QWidget 类都继承自 QPainterDevice
小贴士:
1. QPainter 中的所有绘制参数都可以自定义
2. 任意的 QWidget 对象都能够作为画布绘制图形
画家 (QPainter) 所使用的工具角色
QPen
- 用于绘制几何图形的边缘,由颜色,宽度,线风格等参数组成
QBrush
- 用于填充几何图形的调色板,由颜色和填充风格组成
QFont
- 用于文本绘制,由字体属性组成
QPainter 的基本绘图能力
重要规则
只能在 QWidget::paintEvent 中绘制图形
问题
如何动态绘制需要的图形?
工程中的解决方案
1. 根据需要确定参数对象 (绘图类型,点坐标,角度,等)
2. 将参数对象存入数据集合中 (如:链表)
3. 在 paintEvent 函数中遍历数据集合
4. 根据参数对象绘制图形 (update())
动态随机绘图
Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QPushButton>
#include <QList>
enum
{
RECT,
LINE,
ELLIPSE
};
struct PaintParam
{
int type;
Qt::PenStyle pen;
QPoint begin;
QPoint end;
};
class Widget : public QWidget
{
Q_OBJECT
private:
QPushButton m_testBtn;
protected slots:
void onButtonClicked();
protected:
QList<PaintParam> m_paint;
void paintEvent(QPaintEvent* event);
public:
Widget(QWidget* parent = nullptr);
~Widget();
};
#endif // WIDGET_H
Widget.cpp
#include "Widget.h"
#include <QPainter>
#include <QPoint>
Widget::Widget(QWidget* parent)
: QWidget(parent)
{
resize(600, 400);
m_testBtn.setParent(this);
m_testBtn.move(500, 330);
m_testBtn.resize(80, 30);
m_testBtn.setText("button");
connect(&m_testBtn, &QPushButton::clicked, this, &Widget::onButtonClicked);
}
void Widget::paintEvent(QPaintEvent* event)
{
QPainter painter;
painter.begin(this);
for(int i = 0; i < m_paint.count(); i++)
{
int x = (m_paint[i].begin.x() < m_paint[i].end.x() ? m_paint[i].begin.x() : m_paint[i].end.x());
int y = (m_paint[i].begin.y() < m_paint[i].end.y() ? m_paint[i].begin.y() : m_paint[i].end.y());
int w = qAbs(m_paint[i].begin.x() - m_paint[i].end.x());
int h = qAbs(m_paint[i].begin.y() - m_paint[i].end.y());
painter.setPen(m_paint[i].pen);
switch(m_paint[i].type)
{
case LINE:
painter.drawLine(m_paint[i].begin, m_paint[i].end);
break;
case RECT:
painter.drawRect(x, y, w, h);
break;
case ELLIPSE:
painter.drawEllipse(x, y, w, h);
break;
default:
break;
}
}
painter.end();
}
void Widget::onButtonClicked()
{
PaintParam temp;
temp.type = qrand() % 3;
temp.pen = static_cast<Qt::PenStyle>((qrand() % 6) + 1);
temp.begin = QPoint(qrand() % 400, qrand() % 300);
temp.end = QPoint(qrand() % 400, qrand() % 300);
if(m_paint.count() == 5)
{
m_paint.clear();
}
m_paint.append(temp);
update();
}
Widget::~Widget()
{
}
程序运行结果如下所示:
Qt 图形系统中的坐标系
物理坐标系 (设备坐标系)
- 原点 (0, 0) 在左上角的位置,单位:像素 (点)
- x 坐标向右增长,y 坐标向下增长
逻辑坐标系
- 数学模型中的抽象坐标系,单位由具体问题决定
- 坐标轴的增长方向由具体问题决定
一些事实
QPainter 使用逻辑坐标系绘制图形
逻辑坐标系中图形的大小和位置经由转换后绘制于具体设备
默认情况下的逻辑坐标系和物理坐标系完全一致
视口与窗口
视口 (view port)
- 物理坐标系中一个任意指定的矩形
窗口
- 逻辑坐标系下对应到物理坐标系中的相同矩形
深入理解视口和窗口
视口和窗口是不同坐标系中的同一矩形
视口和窗口中的坐标点存在一一映射的关系
视口和窗口能够通过坐标变换而相互转换
视口和窗口的变换方法
视口和窗口的变换方法
定义视口 (setViewport):
左上角坐标,右下角坐标,计算宽度和高度
定义窗口 (setWindow):
左上角坐标,右下角坐标,计算宽度和高度
正弦波形绘图实例
解决方案
1. 定义视口矩形和逻辑坐标系
2. 定义画笔并填充窗口底色
3. 根据实际问题中的波形函数绘图 (drawPoint())
正弦波绘制
Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QList>
struct Point
{
float x;
float y;
};
class Widget : public QWidget
{
Q_OBJECT
protected:
void paintEvent(QPaintEvent *event);
public:
Widget(QWidget *parent = nullptr);
~Widget();
};
#endif // WIDGET_H
Widget.cpp
#include "Widget.h"
#include <QPainter>
#include <QPen>
#include <QtMath>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
}
void Widget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QPen pen;
pen.setColor(Qt::green);
pen.setStyle(Qt::SolidLine);
pen.setWidthF(0.01);
painter.setPen(pen);
painter.setViewport(50, 50, width() - 100, height() - 100);
painter.setWindow(-10, 2, 20, -4); // (-10, 2) (10, -2)
painter.fillRect(-10, 2, 20, -4, Qt::black);
painter.drawLine(QPoint(-10, 0), QPoint(10, 0));
painter.drawLine(QPoint(0, -2), QPoint(0, 2));
for(float x=-10; x<10; x+=0.01)
{
float y = qSin(x);
painter.drawPoint(QPointF(x, y));
}
}
Widget::~Widget()
{
}
窗口用于逻辑坐标系下的图形绘制,视口用于实际物理设备上的图形显示