QGraphicsScene 与 QGraphicsView 框架
QGraphicsScene 和 QGraphicsView 提供了一个高级的二维图形框架,适用于创建复杂的图形界面、动画以及交互式应用。它基于场景-视图模式,允许在场景中添加和管理大量的图形项,然后通过视图进行显示和交互。
基础概念
●QGraphicsScene:管理和组织所有的图形项(QGraphicsItem)。
●QGraphicsView:显示 QGraphicsScene,并提供视图变换(缩放、旋转等)和用户交互功能。
●QGraphicsItem:场景中的基本图形单元,可以是预定义的形状或自定义的图形。
打个比方
QGraphicsItem 相当于是舞台上的人
QGraphicsScene 相当于是舞台
QGraphicsView 相当于我们看待舞台的视角
QGraphicsScene
QGraphicsScene 是一个用于管理和存储图形项(QGraphicsItem)的场景。它负责处理图形项的添加、删除、更新和渲染。
主要功能
●管理图形项: 可以添加、删除和管理多个图形项。
●事件处理: 处理与图形项相关的事件,如鼠标点击、键盘输入等。
●坐标系统: 提供一个坐标系统,可以在其中定位和操作图形项。
常用方法
●addItem(QGraphicsItem *item): 向场景中添加图形项。
●removeItem(QGraphicsItem *item): 从场景中移除图形项。
●clear(): 清空场景中的所有图形项。
●items(): 返回场景中的所有图形项。
QGraphicsView
QGraphicsView 是一个用于显示 QGraphicsScene 的窗口部件。它负责将场景的内容渲染到屏幕上,并提供用户交互。
主要功能
●可视化图形场景: 显示 QGraphicsScene 中的内容。
●视图变换: 支持缩放、平移等视图变换操作。
●事件转发: 将用户输入事件(如鼠标和键盘事件)转发给场景中的图形项。
常用方法
●setScene(QGraphicsScene *scene): 设置要显示的场景。
●fitInView(): 调整视图以适应场景的内容。
●scale(): 缩放视图。
●setRenderHint(): 设置渲染提示,以提高绘制质量。
添加与管理图形项
QGraphicsScene 提供了多种添加图形项的方法,如 addRect、addEllipse、addText 等,也支持自定义图形项。
示例1:创建简单的图形场景
实现下图所示,我们在场景中添加几个item,然后实现拖动和选中它们的效果。

我们声明一个类GraphicsSceneWidget,使其继承自QGraphicsView
cpp
#include <QGraphicsView>
#include <QWidget>
class GraphicsSceneWidget: public QGraphicsView
{
Q_OBJECT
public:
GraphicsSceneWidget(QWidget* parent = nullptr);
~GraphicsSceneWidget();
};
我们可以在GraphicsSceneWidget构造函数内部创建QGraphicsScene,向QGraphicsScene添加一些图形,最后将QGraphicsScene设置到视图里
cpp
GraphicsSceneWidget::GraphicsSceneWidget(QWidget *parent)
{
//创建场景
QGraphicsScene *scene = new QGraphicsScene(this);
//设置场景区域
//scene->setSceneRect(0,0,600,600);
scene->setSceneRect(-1000, -1000, 2000, 2000);
// 添加矩形
QGraphicsRectItem *rect = scene->addRect(50, 50, 100, 100, QPen(Qt::black), QBrush(Qt::yellow));
//设置可移动,可选中
rect->setFlag(QGraphicsItem::ItemIsMovable ,true);
rect->setFlag(QGraphicsItem::ItemIsSelectable ,true);
// 添加椭圆
QGraphicsEllipseItem *ellipse = scene->addEllipse(200, 50, 100, 100, QPen(Qt::blue), QBrush(Qt::green));
//设置可移动,可选中
ellipse->setFlag(QGraphicsItem::ItemIsMovable ,true);
ellipse->setFlag(QGraphicsItem::ItemIsSelectable ,true);
// 添加文本
QGraphicsTextItem *text = scene->addText("Hello, QGraphicsScene!");
text->setFont(QFont("Arial", 16));
text->setDefaultTextColor(Qt::red);
text->setPos(350, 50);
//设置可移动,可选中
text->setFlag(QGraphicsItem::ItemIsMovable ,true);
text->setFlag(QGraphicsItem::ItemIsSelectable ,true);
// 设置场景
setScene(scene);
// 设置视图属性
setRenderHint(QPainter::Antialiasing);
// 设置拖拽模式
setDragMode(QGraphicsView::ScrollHandDrag);
}
关于拖拽模式
NoDrag: 无拖动操作,适用于默认或无交互状态。ScrollHandDrag: 用于滚动视图,允许用户通过拖动来平移内容。RubberBandDrag: 用于选择多个对象或区域,用户通过拖动来绘制选择框。
接下来我们在主函数创建视图,并且展示
cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//创建视图
GraphicsSceneWidget w;
//展示视图
w.show();
return a.exec();
}
示例2:调整视图
我们可以通过调整视图,实现不同方式查看场景,包括旋转,缩放,平移等
如下图所示

我们在MainWindow.ui中为CentralWidget添加一个水平布局,然后将CentralWidget调整为垂直布局,那么水平布局就会填满整个CentralWidget。
左边放入QGraphicsView,右边放入一个widget,widget宽度限制120。
接下来在右侧widget中加入几个按钮并将右侧widget设置为垂直布局。
ui布局如下

属性结构如下:

我们之前自定义了一个视图是GraphicsSceneWidget,GraphicsSceneWidget继承自QGraphicsView.
那么我们可以将QGraphicsView提升为我们自定义的GraphicsSceneWidget类型
右键属性图中的graphicsView点击提升为

弹出窗口后,添加提升类的名称为GraphicsSceneWidget,勾选全局包含。

然后点击添加,再点击提升。可以看到属性图中graphicsView提升为GraphicsSceneWidget类型了。

接下来分别实现几个槽函数
cpp
//实现放大
void MainWindow::on_zoomIn_clicked()
{
//视图放大1.5被
ui->graphicsView->scale(1.5,1.5);
}
//实现缩小
void MainWindow::on_zoomOut_clicked()
{
//视图缩小0.5
ui->graphicsView->scale(0.5,0.5);
}
//顺时针旋转
void MainWindow::on_clockwise_clicked()
{
//顺时针旋转15度
ui->graphicsView->rotate(15);
}
//逆时针旋转
void MainWindow::on_counterClockwise_clicked()
{
//逆时针旋转15度
ui->graphicsView->rotate(-15);
}
//左平移
void MainWindow::on_moveleft_clicked()
{
// 将可视区域的矩形范围的中心点的坐标转换到场景中,得出在场景中的坐标
QPointF currentCenter = ui->graphicsView->mapToScene(
ui->graphicsView->viewport()->rect().center());
// 视口左移
ui->graphicsView->centerOn(currentCenter.x() - 100, currentCenter.y());
}
//右平移
void MainWindow::on_moveRight_clicked()
{
// 将可视区域的矩形范围的中心点的坐标转换到场景中,得出在场景中的坐标
QPointF currentCenter = ui->graphicsView->mapToScene(
ui->graphicsView->viewport()->rect().center());
// 视口右移
ui->graphicsView->centerOn(currentCenter.x() + 100, currentCenter.y());
}
这样我们点击对应的按钮,就可以实现视图的缩放,旋转,平移了。
示例3:自定义图形(拓展内容)
我们可以自定义图形,比如实现如下图的五角星的绘制

创建好项目CustomItem之后,我们添加一个类StarItem,继承自QGraphicsItem
cpp
class StarItem : public QGraphicsItem
{
public:
StarItem(QGraphicsItem *parent = nullptr);
// 重写边界函数
QRectF boundingRect() const override;
// 重写绘制函数
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
protected:
// 重写鼠标事件处理
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
private:
QPainterPath starPath;
};
我们编程中采用的cos和sin等正余弦函数的参数和数学中不同,编程中正余弦参数为弧度,比如180°角度的弧度就是M_PI,就是我们常说的3.1415926.
使用M_PI要包含头文件#include <qmath.h>
那么360°对应的弧度就是M_PI * 360/180 = 2 *M_PI
30°的余弦函数计算公式为cos(M_PI*30/180)
一个五角星有五个角,所以角与角之间应该是360/5=72°
72°对应的弧度就是M_PI*72/180,我们用deg存储
假设我们将五角星的原点设置为(0,0),那么它上面的点,我们定义为第一个点也就是(0,-R)

接下来求顺时针的第二个点

已知第一个点和第二个点之间的角度为72°(两条红线夹角),我们通过计算72°对应的弧度应为deg = M_PI*72/180
我们还知道第二个点到圆心的长度为R,那么可以计算出绿色线的长度为R*sin(deg)
黑色的线的长度为R*cos(deg)
所以第二个点的坐标为(R*sin(deg), -R*cos(deg)),因为坐标原点在五角星中心,所以第二个点的纵坐标为-R*cos(deg)
依次类推,可以推出第三个点的坐标为(R*sin(2*deg),-R*cose(2*deg)), 我们推出五个点的坐标
然后将要绘制的路线提前存起来,路线就是先将画笔移动到第一个点的位置
接下来从第一个点向第三个点连线,红色的线表示绘制的路径

从第三个点向第五个点连线

从第五个点向第二个点连线

从第二个点向第四个点连线

从第四个点回到原点

cpp
StarItem::StarItem(QGraphicsItem *parent)
: QGraphicsItem(parent)
{
double R = 100;
double deg = M_PI*72/180;
QList<QPoint> points = {
QPoint(0,-R),
QPoint(R*sin(deg),-R*cos(deg)),
QPoint(R*sin(2*deg),-R*cos(2*deg)),
QPoint(R*sin(3*deg),-R*cos(3*deg)),
QPoint(R*sin(4*deg),-R*cos(4*deg)),
};
//移动到第一个点
starPath.moveTo(points[0]);
//从第一个点向第三个点连线
starPath.lineTo(points[2]);
//从第三个点向第五个点连线
starPath.lineTo(points[4]);
//从第五个点向第二个点连线
starPath.lineTo(points[1]);
//从第二个点向第四个点连线
starPath.lineTo(points[3]);
//从第四个点回到原点
starPath.closeSubpath();
// 设置图形项在场景的位置
setPos(300, 200);
// 启用图形项的选中状态
setFlag(QGraphicsItem::ItemIsSelectable, true);
setFlag(QGraphicsItem::ItemIsMovable, true);
}
接下来实现item所在的碰撞区域,每一个item都有一个矩形区域表示它的边界,我们重写这个函数
cpp
QRectF StarItem::boundingRect() const
{
// 增加边界以避免裁剪
return starPath.boundingRect().adjusted(-5, -5, 5, 5);
}
- starPath.boundingRect()
starPath 是一个 QPainterPath 对象,它表示一个绘图路径,可能包含多个形状、线条和曲线。boundingRect() 是 QPainterPath 的一个成员函数,它返回一个 QRectF 对象,表示该路径的边界矩形。这个矩形是一个最小的矩形,完全包围了路径中的所有形状。 - adjusted(left, top, right, bottom) adjusted() 是 QRectF 的一个成员函数,用于调整矩形的边界。它接受四个参数,分别表示:
left: 向左调整的距离
top: 向上调整的距离
right: 向右调整的距离
bottom: 向下调整的距离
我们可以重写item的paintEvent函数,达到自定义绘制item的效果
cpp
//重回item样式
void StarItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
// 设置画笔和刷子
//选中状态更改画笔颜色
if (isSelected()) {
painter->setPen(QPen(Qt::red, 2));
} else {
painter->setPen(QPen(Qt::cyan, 2));
}
painter->setBrush(QBrush(Qt::yellow));
// 绘制星形路径
painter->drawPath(starPath);
}
我们同样可以重写鼠标点击和释放的功能,这里没有做特殊处理,只是调用了基类的处理
cpp
void StarItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
// 点击时改变颜色
Q_UNUSED(event);
// 可以在这里添加更多自定义的交互逻辑
QGraphicsItem::mousePressEvent(event);
}
void StarItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
Q_UNUSED(event);
QGraphicsItem::mouseReleaseEvent(event);
}
接下来在main函数中加载这个item
cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//创建场景
QGraphicsScene *scene = new QGraphicsScene();
//设置场景大小
scene->setSceneRect(0, 0, 600, 400);
// 添加自定义星形图形项
StarItem *star = new StarItem();
scene->addItem(star);
// 创建视图
QGraphicsView *view = new QGraphicsView(scene);
view->setRenderHint(QPainter::Antialiasing);
view->setWindowTitle("自定义图形项示例");
view->resize(800, 600);
view->show();
return a.exec();
}