45、QGraphicsScene 与 QGraphicsView 框架---------绘图

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 提供了多种添加图形项的方法,如 addRectaddEllipseaddText 等,也支持自定义图形项。

示例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);
}
  1. starPath.boundingRect()
    starPath 是一个 QPainterPath 对象,它表示一个绘图路径,可能包含多个形状、线条和曲线。boundingRect() 是 QPainterPath 的一个成员函数,它返回一个 QRectF 对象,表示该路径的边界矩形。这个矩形是一个最小的矩形,完全包围了路径中的所有形状。
  2. 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();
}
相关推荐
「QT(C++)开发工程师」2 小时前
C++17三大实用特性详解:折叠表达式、结构化绑定与constexpr if
jvm·c++
杜子不疼.2 小时前
Python + Ollama 本地跑大模型:零成本打造私有 AI 助手
开发语言·c++·人工智能·python
小此方2 小时前
Re:思考·重建·记录 现代C++ C++11篇 (一) 列表初始化&Initializer_List
开发语言·c++·stl·c++11·现代c++
计算机安禾2 小时前
【数据结构与算法】第29篇:红黑树原理与C语言模拟
c语言·开发语言·数据结构·c++·算法·visual studio
sycmancia2 小时前
QT——计算器核心算法
开发语言·qt·算法
AbandonForce2 小时前
C++ STL list容器模拟实现
开发语言·c++·list
Tanecious.2 小时前
蓝桥杯备赛:Day7- U535982 C-小梦的AB交换
c语言·c++·蓝桥杯
杜子不疼.2 小时前
AutoGen vs CrewAI vs LangGraph:2026年 Agent 框架怎么选?
c++·microsoft
小肝一下4 小时前
每日两道力扣,day5
数据结构·c++·算法·leetcode·职场和发展·hot100