Qt 图形视图 /基于Qt示例DiagramScene解读图形视图框架

news2025/1/18 3:26:26

文章目录

  • 概述
  • 从帮助文档看示例程序
    • 了解程序背景/功能
    • 理清程序概要设计
  • 分析图形视图的协同运作机制
    • 如何嵌入到普通Widget程序中?
    • 形状Item和文本Item的插入和删除?
    • 连接线Item与形状Item的如何关联?
    • 如何绘制ShapeItem间的箭头线?
  • 下水实践
  • 小结Qt示例阅读方法

概述

本文基于对 Qt 图形视图框架 DiagramScene 示例程序的研习,快速上手图像视图框架的使用和窥探图形视图框架的运作原理。文中通过帮助文档了解了示例程序的功能背景,分析了其设计结构,然后通过对示例程序的实际操作,感受其提供的功能,并以一个软件设计开发人员的角度来审视,自己会如何设计实现这些可触及的功能,由浅入深,向图形视图框架的大院又迈进一步。

打开 Qt Creator -> 示例 -> 搜索Diagram -> 打开 Diagram Scene Example 工程。在打开示例工程时,通常相关帮助页面会一同弹出来。Qt 优势之一就在于其炸天的帮助文档和示例程序。那些没有文档辅助的工程和代码,可能会让人疯掉,不到迫不得已,不建议研读。

在这里插入图片描述

从帮助文档看示例程序

如下图,Qt 示例程序具有详尽的帮助文档,文档目录与工程文件目录完全一致,可见是逐文件对应进行的讲解。你甚至不必细读,只读个概述的段落大意,便可迅速了解该程序。如果你找不到示例程序帮助的位置了,使用帮助程序的查找搜索功能,注意不是索引哦。
在这里插入图片描述

了解程序背景/功能

该示例程序的Doc中,包含了示例实现的主要功能,设计思路,层次结构,主要代码及其注释。

概述中提到,名为 Diagram Scene 的示例,是一个可以创建流程图的应用程序。程序支持流程图形状和文本的添加,流程图形状可以使用Arrow箭头进行连接。流程图形状、箭头和文本都可以设置不同的颜色,且可以改变文本的字体、样式、添加下划线等。还是建议应该编译运行程序,去实际的使用和感受一下。

Qt Graphics 框架(Graphics View Framework)被设计用来管理和展示自定义的2D图形项。涉及的基础类类型有 QGraphicsItem, QGraphicsScene 和 QGraphicsView 等。QGraphicsScene 场景会管理多个图形项,并提供一个surface 展示面。 QGraphicsView is a widget that is used to render a scene on the screen. QGraphicsView 视图是一个用于在屏幕Screen上渲染场景Scene的部件。 说QGraphicsView 是Widget部件,那是因为 QGraphicsView 的继承关系可以一直追溯到QWidget类。另外通过上述这段话,我们大抵知道了管理图形项的是Scene,而View是负责渲染场景的部件。

示例程序展示了如何创建自定义的图形场景和图形项。详细地(In Particular)展示了,
1、创建自定义图形项
2、控制鼠标事件和图形项移动
3、实现可以管理自定义图形项的图形场景
4、自定义基于painting绘制的图形项(Arrow是基于Painting的)
5、创建可移动和可编辑的文本图形项

文档化名词定义
在Microsoft Visio流程图软件中,基本流程图形状包括了,判定、流程、子流程、数据等形状和指针工具、连接线、文本等工具。本程序是简化版的流程图软件,提供了条件、流程、输入输出等形状、文本、指针、连接线。不太一样的是,本示例应用程序中将文本和形状塞到了一个选项卡中,而没有将文本与指针工具和连接线等归为工具。为了后续章节行文的方便和不造成歧义,我们统一口径,定义下文使用的名词及其含义。流程图形状记做ShapItem,连接线记做ArrowItem,文本记做TextItem。

理清程序概要设计

都说,面向对象程序设计则是一种自底向上的思维方式,但是,面向对象的程序设计也涉及自顶向下的设计方法,开发者要从程序的整体架构和功能需求出发,确定需要的类和对象,以及它们之间的关系和交互。最好先问问,若拿到同样的功能需求后,自己会怎么办,你会设计哪些类,类之间有怎样的关联。示例程序包含如下C++类设计,各类功能概述如下,

MainWindow
派生自 QMainWindow,它创建、布局和展示Widget部件,用以管理窗口部件、场景、视图、图形项之间的交互。它(forward)转发窗口部件的输入到DiagramScene对象,并在TextItem发生变动或者ShapItem、TextItem插入场景时,接收scene信号来更新相关的HMI部件。另外这个类负责删除图形项并handle处理图形项的Z向顺序,即决定项彼此重叠时的绘制drawn顺序。

DiagramItem
派生自 QGraphicsPolygonItem 多边形项。表示一个流程图形状,不包含文本和箭头。即ToolBox-Page1中的条件、流程、输入输出3种基本流程图形状。

DiagramScene
类DiagramScene,继承自 QGraphicsScene 类,其除了(In addition to)具有基类的图形项管理功能外,添加了对自定义项 DiagramItems、Arrows、DiagramTextItems 的管理功能。在这个类中,一个鼠标点击可以有三种不同的响应:鼠标位置的项移动、新项被插入、在ShapeItem项之间连接箭头。鼠标点击触发哪种操作,取决于场景所处的mode模式,其由Mode枚举类型表示,在MainWindow的输入部件的响应函数中使用setMode()函数设置当前的模式。Which action a mouse click has depends on the mode, given by the Mode enum, the scene is in. (英语语法,given by the Mode enum, the scene is in. 是 mode 的并列定语)。The type of DiagramItem, given by the DiagramItem::DiagramType function, to be created when an item is inserted is set with the setItemType() slot.(to be created when an item is inserted 是定语从句,与given… 并列修饰主语The type of DiagramItem)。

DiagramItem
DiagramItem在DiagramScene场景下代表流程图形状。它继承自QGraphicsPolygonItem类,并为每种形状持有一个polygon多边形。DiagramType枚举类型为每个流程图形状定义了一个值。该类持有与其连接的箭头(对象指针)的列表。This is necessary because only the item knows when it is being moved (with the itemChanged() function) at which time the arrows must be updated. 只有ShapeItem自身知道何时它被移动,此时,与之相连的箭头必须要重新绘制。该item类还可以使用image()成员函数在QPixmap上绘制自己。image()函数的设计和使用比较巧妙,该函数在QPixmap上绘制一个几何图形,并在示例程序中使用它,用作MainWindow工具组中各按钮的icon图标。枚举类型Type是类的唯一标识符,它被qgraphicsitem_cast()使用,用来动态强制类型转换图形项。UserType == 65536,是最小的用户自定义图形项的值,定义在超类QGraphicsItem中。

DiagramTextItem
DiagramTextItem类继承自QGraphicsTextItem,并添加了移动可编辑文本项的功能(possibility)。可编辑状态的QGraphicsTextItem被设计为,在固定位置上不可移动,在用户single单击该项时开始编辑。而派生自它的 DiagramTextItem,则通过双击开始编辑,单击仍然可用于与该项交互和移动。With DiagramTextItem the editing starts with a double click leaving single click available to interact with and move it.

Arrow
自定义类Arrow,是用以连接两个DiagramItem的ShapItem。它在其中一个图形项上绘制箭头头部,为了实现这一点,该自定义图形项需要自行绘制该箭头,并重写场景视图用以检查碰撞和选择的方法。该类派生自 QGraphicsLine 类,并在此基础上绘制箭头头部,并随着其所连接的ShapItem的移动而移动。本类中 boundingRect() 和 shape() 函数是对 QGraphicsLineItem 超类对应虚函数的重写,它们被图形场景对象用以检查冲突和选择。

分析图形视图的协同运作机制

了解图形视图机制的基本创建流程和运作流程,是阅读本示例的根本目标之一。在阅读此示例程序前,要对场景、视图、图形项等概念有基础的了解,且通过帮助文档了解示例程序的功能和结构。重点强调下,一定要编译、运行并实际去使用你要探究的示例程序,去感受它的功能,思考并猜测你所触及到的功能在该程序中如何实现的,先提起自己的兴趣来!、

如何嵌入到普通Widget程序中?

在《Qt 图形视图 /Graphics View Framework 详谈图形视图框架》一文中,我们已经意识到,类QGraphicsView 其父辈可追寻到 QWidget类;而按照辈分算的话,QGraphicsScene 得是 QGraphicsView 的二老爷爷,其直接从QObject继承;类 QGraphicsItem 与 QObject 没有任何血缘关系,它是一个根级别的类。由此可推测,能将图形视图功能融入到QWidget程序中的家伙,非QGraphicsView莫属,

MainWindow::MainWindow() {
    ..
    scene = new DiagramScene(itemMenu, this);
	...
    QHBoxLayout *layout = new QHBoxLayout;
    layout->addWidget(toolBox);       //在水平布局中添加用以图形项和背景选择的ToolBox控件
    view = new QGraphicsView(scene);  //创建视图对象/以scene为可视化场景
    layout->addWidget(view);          //将视图添加到布局对象
    QWidget *widget = new QWidget;    //widget 是MainWidget中心窗口
    widget->setLayout(layout);      
    setCentralWidget(widget);        //
    ...
}

上述使用的是QGraphicsView构造函数原型为,
在这里插入图片描述
创建一个视图对象,并设置其可视化场景为 scene 这个场景对象(也可以说是,将视图关联到场景上)。那么,场景,为啥称作可视化场景呢? 可视化场景是指在图形界面中以图形的形式展示的场景,它提供了一个二维的绘图区域,可以在其中添加、编辑和显示图形项。对象scene 是通过 对象view 来进行显示和交互的,可以将其中的图形项以图形的形式可视化在界面上。

另外瞥一眼用户操作的几个关键部件,buttonGroup、pointerTypeGroup、backgroundButtonGroup ,而且前阵子看到这里的时候,还单独为 QButtonGroup 开了一篇文章,确实挺好用的。按钮组 buttonGroup管理的是ToolBox-Page1流程图形状和文本选择按钮们,backgroundButtonGroup管理的是ToolBox-Page2背景样式选择按钮们,pointerTypeGroup管理的是工具栏中鼠标指针和连接线。上述按钮组内的按钮在被check后,其相应操作均为对scene对象进行参数设置。其他工具栏按钮和菜单动作项功能和行为类似。
补记,20231210,
通过细细品读这个示例程序,不仅可以很大程度上提升对图形视图框架的理解,也有很多其他的地方值得学习,如GroupButton的使用、QAction&QToolButton小部件图标创建、 DiagramItem::image() 函数返回与自身同形的QPixmap等,都值得留心学习。

形状Item和文本Item的插入和删除?

在软件实操中,插入一个流程图图形项或流程图文本项,只需要先在ToolBox选项卡中Check相应的按钮,然后在绘图区域(view对象代表的Widget)中,使用鼠标左键在任意位置执行单点击操作即可,如果之前设置了填充色,也会在插入时生效。被插入的项,可以任意移动其在 ‘视图中的位置’ ,更准确地说应该是在场景中的位置,可以使用Delte删除或使用工具栏的删除按钮删除。

插入操作,
通过对代码的基本分析可看到,插入操作的信号是从场景类的itemInserted发射的。DiagramScene场景定义了4种操作项的模式,

    enum Mode { InsertItem, InsertLine, InsertText, MoveItem };

step,设置项操作模式,
当我们在buttonGroup中点击了某按钮时,如果是ID为InsertTextButton的按钮则secen模式被设置为DiagramScene::InsertText,如果是判定框、流程框、输出输出框对应的按钮,则secen模式被设置为DiagramScene::InsertItem。如果我们点击的是pointerTypeGroup组中的鼠标按钮或连接线按钮,则secen模式将分别被设置为DiagramScene::MoveItem 和 DiagramScene::InsertLine 操作类型。

step,在场景对象中实时检测鼠标事件,

void DiagramScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) {
    if (mouseEvent->button() != Qt::LeftButton)
        return;

    DiagramItem *item;
    switch (myMode) {
        case InsertItem:
            item = new DiagramItem(myItemType, myItemMenu);
            item->setBrush(myItemColor);
            addItem(item);
            item->setPos(mouseEvent->scenePos());
            emit itemInserted(item);
            break;
    ...

如上所示,在鼠标按压事件过滤函数中,如果检测到鼠标左键按下,且当前设置的图形项操作模式为InsertItem的话,则创建一个流程图形状项,并将其添加到场景对象中,设置图形项对象在场景中的位置。最后发出已流程图形状项插入信号,该信号在MainWindow中响应如下,将鼠标效果强设置为鼠标指针类型,进而将scene对象从InsertItem模式中退出,切换到MoveItem模式;取消buttonGroup的当前选中。插入文本项和连接线项的流程与上述插入流程图基本形状项是一致的,不再赘述。

删除操作,

void MainWindow::deleteItem() {
    foreach (QGraphicsItem *item, scene->selectedItems()) {
        if (item->type() == Arrow::Type) {
            scene->removeItem(item);
            Arrow *arrow = qgraphicsitem_cast<Arrow *>(item);
            arrow->startItem()->removeArrow(arrow);
            arrow->endItem()->removeArrow(arrow);
            delete item;  //要注意的是scene的removeItem操作并不释放
        }
    }
	
	//由于按住Ctrl键时可以选中多个项,且选中顺序无法保证,因此不能在一个for循环中删除多个类型
    foreach (QGraphicsItem *item, scene->selectedItems()) {
         if (item->type() == DiagramItem::Type)
             qgraphicsitem_cast<DiagramItem *>(item)->removeArrows();
         scene->removeItem(item);
         delete item;
     }
}

要注意,图形视图框架允许多个项同时被选中,且有选中顺序,因此不能在一个for循环中删除多个类型。要先删除连接线,再删除图形项和文本项。删除连接线Item时,要对关联的图形Item进行操作,同样地,在删除某个图形Item时,也要捎带着删除关联的连接线Item。

连接线Item与形状Item的如何关联?

上一节我开始对ArrowItem和ShapeItem的关联关系产生了好奇,为了搞明白这件事情,得关注DiagramScene中连接线的插入操作。

void DiagramScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)  {
		...
            case InsertLine:
            line = new QGraphicsLineItem(QLineF(mouseEvent->scenePos(), mouseEvent->scenePos()));
            line->setPen(QPen(myLineColor, 2));
            addItem(line);
            break;
        ...
----------------------------------------------------------------------------------------------------
void DiagramScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) {
    if (line != 0 && myMode == InsertLine) {
        QList<QGraphicsItem *> startItems = items(line->line().p1());
        if (startItems.count() && startItems.first() == line)
            startItems.removeFirst();
        QList<QGraphicsItem *> endItems = items(line->line().p2());
        if (endItems.count() && endItems.first() == line)
            endItems.removeFirst();

        removeItem(line);
        delete line;

        if (startItems.count() > 0 && endItems.count() > 0 &&
            startItems.first()->type() == DiagramItem::Type &&
            endItems.first()->type() == DiagramItem::Type &&
            startItems.first() != endItems.first()) {
            DiagramItem *startItem = qgraphicsitem_cast<DiagramItem *>(startItems.first());
            DiagramItem *endItem = qgraphicsitem_cast<DiagramItem *>(endItems.first());
            Arrow *arrow = new Arrow(startItem, endItem);
            arrow->setColor(myLineColor);
            startItem->addArrow(arrow);
            endItem->addArrow(arrow);
            arrow->setZValue(-1000.0);
            addItem(arrow);
            arrow->updatePosition();
        }
    }
	//
    line = 0;   QGraphicsScene::mouseReleaseEvent(mouseEvent);
}

连接线Item的插入,是在鼠标按压和抬起事件的协作下完成的。

在按压事件中,
只是简单的创建了 连接线Item,并将其添加到了Scene中。特别需要注意的是,这里创建的并不是我们自定义的 Arrow 对象,而是 使用Arrow的超类 QGraphicsLineItem 进行的创建。这里,line成员变量指向的 QGraphicsLineItem 对象,本质上是一个临时变量,line 的定义仅仅是为了能在释放事件函数中继续使用它。而且在释放事件处理中 line直接被delete掉了。

在释放事件中, 先来看如何求取line对应的起始项和结束项列,
执行插入操作时,在DiagramScene::mouseReleaseEvent函数的下图位置设置断点进行调试,
在这里插入图片描述
通过在Locals和Expressions调试窗口中变量的对比可以发现,startItems 包含了line对象自己(绿色框标注)和包含line->line().p1()点的形状Item对象。进一步的研究 startItems 求取语句可知,
在这里插入图片描述
而其中关键函数,QGraphicsScene::items 存在多个重载版本,这里使用的是,
在这里插入图片描述
上述程序中,iterms函数返回 pos 位置的可视的项对象,使用 默认的Qt::IntersectsItemShape模式,表示将返回那些shape形状与pos相交的所有Item对象。这里有个概念需要补充下,Items whose exact shape,具有xx特点精确shpe的图形项,怎么理解呢,我们从enum Qt::ItemSelectionMode的帮助中可以跳转到shpe的一个解释,
在这里插入图片描述
连接了shape的概念后,我们变可以明白示例程序中items(line->line().p1())的返回列表,如果line的起点是两个Item的叠加,则此时items将返回包含line的3个Item元素。通过后续代码可以看出来,无论startItems和endItems中包含多少个元素,在实际创建Arrow对象时,使用的都是first元素,这与items函数的Qt::DescendingOrder参数设置有关,此处不再细究。

只有在鼠标被释放时,才能确定起点和终点,才能判断这条连接线是否有效(即两端都连接到DiagramItem对象),如果连接无效,就不需要创建Arrow对象。示例程序中,巧妙的使用 line 对象作为引子来实现了这一功能。另外,Arrow的p1和p2也不同于line对象,后者的起点和充电直接来自于用户操作,而Arrow兑现的起点和终点是实时计算出来的,在Arrow::paint绘制过程中实现。

解下来的流程就好理解多了,将arrow对象指针分别在起点和终点ShapeItem对象中记录下来,设置arrow对象的Z向叠顺序(stacking order)值,以确保Arrow对象会被渲染在其他图形项目的下方,这样Arrow就不会遮挡住其他重要的可视元素。将arrow项对象添加到场景中。

void Arrow::updatePosition() {
    //QLineF line(this->mapFromItem(myStartItem, 0, 0), this->mapFromItem(myEndItem, 0, 0));
    QLineF line(mapFromItem(myStartItem, 0, 0), mapFromItem(myEndItem, 0, 0));
    setLine(line);
}

那么 updatePosition 函数这是在干啥?为了从现象级搞明白这个问题,封掉该函数的实现,重新运行测试,发现此时连接线无法完成绘制。实际上,在创建Arrow时,它自身的线条位置还未被设置,需要通过updatePosition()计算并指定。
在这里插入图片描述
帮助文档中,对updatePosition函数实现的讲解如上。 在IDE搜索其调用位置,可看到其会在Arrow对象创建后、关联的ShapeItem有大小或位置等变动时被调用。函数实现中使用mapFromItem函数功能如下,
在这里插入图片描述
结合帮助文档,每个Item都有自己的本地坐标系统,上述代码实现的功能是,将myStartItem坐标系下的Point(0,0),也即中心点(务必注意,图形项坐标系的原点是中心点,而不是左上角点),也可以称作参考原点,转化到 this item == arrow slef 坐标系下的某点,用左线段起点。20231212 初次阅读到这里时,误以为myStartItem坐标系下的Point(0,0)是左上点,因此始终不能理解line的构造思路,耽误了很久。如果能理解到那时ShapeItem的中心点,那么就好理解多了,Arrow的超类QGraphicsLineItem,其私有实现中持有一个线段,这个线段的起点和终点分别是两端ShapeItem的中心位置,再由于ArrowItem的Z值非常小,ShapeItem会对其进行部分覆盖,效果就有了。进一步,我们可以修改ArrowItem的Z值,看看效果…
在这里插入图片描述

如何绘制ShapeItem间的箭头线?

在这里插入图片描述
连接两个流程图形状的功能是我在所有问题中比较感兴趣的一个。实际操作过程,点击工具栏中的连接线按钮,然后通过鼠标左键选中一个图形,保持按住,移动鼠标到另一个图形的任意位置后松开即可。而且随着图形项的拖拽,连接线也自动跟随变动。
通过前边的文档,我们知道这里的连接线功能由Arrow类实现,而且,Arrow的基类就是绘制线段的。当注释掉Arrow类中对于paint函数的重写,发现,连接线依然可以画出来,只不过该连接线是不带箭头的一个线段,由此分析,可确定,Arrow::paint函数主要是用以绘制线段的箭头形状,

QPainterPath Arrow::shape() const
{
    QPainterPath path = QGraphicsLineItem::shape();
    path.addPolygon(arrowHead);
    return path;
}
//! [2]

//! [3]
void Arrow::updatePosition()
{
    QLineF line(mapFromItem(myStartItem, 0, 0), mapFromItem(myEndItem, 0, 0));
    setLine(line);
}
//! [3]

//! [4]
void Arrow::paint(QPainter *painter, const QStyleOptionGraphicsItem *,  QWidget *)
{
    if (myStartItem->collidesWithItem(myEndItem))
        return;  //两个ShapeItem有重叠时不进行带箭头连接线的绘制

    QPen myPen = pen();
    myPen.setColor(myColor);
    qreal arrowSize = 20;    //设置三角形箭头的尺寸
    painter->setPen(myPen);
    painter->setBrush(myColor);

    //点坐标为float型的线段,center其含义是StartItem中心点到EndItem中心点
    QLineF centerLine(myStartItem->pos(), myEndItem->pos());
    //always:(-100,-100)、(100,-100)、(100,100)、(-100,100)、(-100,-100) //即最初创建它时设置的多边形
    QPolygonF endPolygon = myEndItem->polygon();
    //p1多边形某条边的起点
    QPointF p1 = endPolygon.first() + myEndItem->pos();
    qDebug() << "endPolygon.first:" << endPolygon.first() << ", " << "myEndItem->pos:" << myEndItem->pos();
    
    QPointF p2;             //p2多边形某条边的终点
    QPointF intersectPoint; //最终的计算结果 /多变形某条边与连接线centerLine之间的交叉点
    QLineF polyLine;        //多边形的某条边线段
    //遍历多边形的全部边线段
    for (int i = 1; i < endPolygon.count(); ++i) {
        p2 = endPolygon.at(i) + myEndItem->pos();
        polyLine = QLineF(p1, p2);
        //判定两条线段是否相交并计算交点值
        QLineF::IntersectType intersectType = polyLine.intersect(centerLine, &intersectPoint);
        if (intersectType == QLineF::BoundedIntersection)
            break;
        p1 = p2;
    }
    
    //更新覆盖Arrow插入操作时设置的线段
    setLine(QLineF(intersectPoint, myStartItem->pos()));

    //反正切函数 /计算直线自身的角度值
    double angle = std::atan2(-line().dy(), line().dx());
    //得到三角箭头尾部的点1
    QPointF arrowP1 = line().p1() + QPointF(sin(angle + M_PI / 3) * arrowSize, cos(angle + M_PI / 3) * arrowSize);
    //得到三角箭头尾部的点2
    QPointF arrowP2 = line().p1() + QPointF(sin(angle + M_PI - M_PI / 3) * arrowSize, cos(angle + M_PI - M_PI / 3) * arrowSize);
    arrowHead.clear();
    //构成箭头三角形的3个点
    arrowHead << line().p1() << arrowP1 << arrowP2;
    painter->drawLine(line());          //绘制新线段
    painter->drawPolygon(arrowHead);    //绘制箭头三角形

    //如果箭头被选中,绘制用于展示选中效果的虚线
    if (isSelected()) {
        painter->setPen(QPen(myColor, 1, Qt::DashLine));
        QLineF myLine = line();
        myLine.translate(0, 4.0);   //平移线条以绘制上方的虚线
        painter->drawLine(myLine);
        myLine.translate(0,-8.0);
        painter->drawLine(myLine);  //平移线条以绘制下方的虚线
    }
}

在DiagramItem构造函数中,风格ShapeItem类型创建PainterPath,使用的是各自本地坐标系统中坐标值,路径点压入path的顺序,也即polygon函数获取的多边形点的顺序。如上我们从红色判定框到蓝色输入输出框之间绘制箭头线的过程中,我们调试测试piant函数,其中大部分信息已经直接注释在了源码中,如下,我们再简单补充几点,
在这里插入图片描述
由于我们应用程序中创建的所有ShapItem都没有parent,因此paint函数中pos函数返回的坐标都是在Scene坐标系下的。由于Item是一个区域,因此要描述Item在场景下的位置,则必须找一个Item的点来代表Item整体,这个点就是Item的原点(its origin),也是Item本地坐标系下的 (0, 0) 点,通常它在Item的中心位置。如下图所示的绿色原点,
在这里插入图片描述
当需要求取两条 QLineF 线的交点的时候,构建centerLine和polyLine线段使用的点都是在场景坐标系下的浮点型坐标值。centerLine中心线,是StartItem中心原点到EndItem中心原点的线段,polyLine代表的是EndItem的4条边线段,通过intersect函数求取两线的交点,作为待绘制三角箭头的头部顶点。如是,完成了一个带箭头的连接线的绘制。

下水实践

鉴于本文的篇幅已经足够长,这部分内容将在新CSDN博文中展开。在前文的阅读分析过程中,通过增加打印信息、借助IDE调试窗口分析执行数据、临时改变代码执行逻辑观察执行现象等手段,也有效的提升了对示例程序本身和图形视图框架的理解。通过对DiagramScene示例程序的解读,不光是加深了对图形视图框架的理解,也get到不少其他的 Qt GUI编程小技巧,限于篇幅,不再展开。

小结Qt示例阅读方法

本文的第一笔记录是在2022年5月份写下的,拖拖拉拉到现在,已经过去1年半了,终于还是基本完成了文章的整理。在早期我曾想着对着示例的帮助文档,一点点的读下去,可是并不理想,我总被卡主,理解不了。后来,我采用了如下方法,
先粗读帮助文档,了解应用程序功能和背景。然后我结合一些结构化程序设计中,概要设计流程和详细设计的方法和流程,把需求抛给自己,以设计者的视角,思考应用程序中表现出来的功能,并写写自己的思路。一定要尝试写写,这里不追求结果,其目的在于激发思考过程,哪怕只是简单的提出几个问题,如 ,场景视图如何与传统QWidget程序柔和在一起?图形项本地坐标系的原点在哪里?线段是如何绘制的? 箭头是如何绘制的? 总之,提起自己的兴趣来,尽量摆脱 ‘读别人的程序’ 产生的不适感。

如下,是现阶段我能总结的一个如何解读示例程序的步骤,可能并不有效和高效,仅供参考,

1、确保您有程序的文档或说明文件。仔细阅读文档,了解程序的目的、功能和使用方法。
2、下载或拷贝示例程序,配置其编译和运行环境,确保便程序可以正常编译和运行。
3、了解程序结构。浏览示例程序的源代码,了解它的结构和组织方式。查看主要文件和目录,以及它们之间的关系。
4、运行程序,切身体验其展现出来的功能。更重要的是,思考如何实现你所触及的功能。
5、针对某个具体的功能,进行调试分析,借助IDE或打印,观察其执行流和数据流,理解其实现逻辑。
6、研究细节。针对某个具体的功能,结合执行流和数据流,对源码进行剖析。这个阶段将开始纵向深入,会较多地触及示例程序所属的Qt框架的机制、类、接口等,认真研习相关帮助文档,理解其本质。在阅读示例前,我们可能已有些基础的理论知识储备,现在在实践的基础上,得以对某些具体的理论点进行较深入的学习领会,使得我们走出了,理论-实践-理论交叉前行的步伐。
7、修改和定制。尝试更改一些参数或添加新功能,以更好地理解程序的工作原理。
8、学习和扩展。通过阅读示例程序的源代码和文档,学习它的实现细节和使用的技术。根据您的兴趣和需求,尝试扩展示例程序,添加新功能或进行改进。通常,我们在Get到新知识的同时,也会产生新问题需求。如此,我们便有了新的要走的路,如,在本示例的解读完成后,将开始对图形视图框架下坐标系系统的探究。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1518869.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Centos strema 9 环境部署Glusterfs9

本文档只是创建复制卷&#xff0c;分布式卷&#xff0c;分布式复制卷&#xff0c;纠删卷 操作系统 内核 角色 Ip地址 说明 CentOS Stream 9 x86_64 5.14.0-427.el9.x86_64 客户端 client 192.168.80.119 挂载存储业务机器 CentOS Stream 9 x86_64 5.14.0-427.el9.x8…

Git的介绍

导出项目依赖 # 以后项目给别人需要导出项目依赖&#xff0c;放在项目路径下&#xff0c;以后在运行项目前&#xff0c;先安装依赖 一般约定俗成都叫 requirements.txt,但是会有别的&#xff1a;req.txt | dev.txt # 两种方式&#xff1a; 1、虚拟环境所有装的第三方&…

分享6款非常炫酷的前端动画特效(附在线演示)

分享6款非常不错的项目动画特效 其中有three.js特效、canvas特效、CSS动画、SVG特效等等 下方效果图可能不是特别的生动 那么你可以点击在线预览进行查看相应的动画特效 同时也是可以下载该资源的 Three.js 3D游戏场景动画特效 基于Three.js的HTML5 3D动画&#xff0c;这个动…

【学习】感受野

感受野&#xff08;receptive field&#xff09;是指在神经网络中&#xff0c;某一层输出的特征图上的一个像素点对应输入图像的区域大小。在深度神经网络中&#xff0c;随着网络层数的增加&#xff0c;特征图的感受野也会逐渐增大。这是因为每一层的卷积操作都会扩大感受野。 …

python面向对象的三大特性:封装,继承,多态

1、面向对象有哪些特性 三种&#xff1a;封装性、继承性、多态性 2、Python中的封装 在Python代码中&#xff0c;封装有两层含义&#xff1a; ① 把现实世界中的主体中的属性和方法书写到类的里面的操作即为封装 ② 封装可以为属性和方法添加为私有权限&#xff0c;不能直…

设计模式二三事(含基础使用示例)

设计模式是众多软件开发人员经过长时间的试错和应用总结出来的&#xff0c;解决特定问题的一系列方案。现行的部分教材在介绍设计模式时&#xff0c;有些会因为案例脱离实际应用场景而令人费解&#xff0c;有些又会因为场景简单而显得有些小题大做。 本文会结合在美团金融服务…

苹果Find My App用处多多,产品认准伦茨科技ST17H6x芯片

苹果发布AirTag发布以来&#xff0c;大家都更加注重物品的防丢&#xff0c;苹果的 Find My 就可以查找 iPhone、Mac、AirPods、Apple Watch&#xff0c;如今的Find My已经不单单可以查找苹果的设备&#xff0c;随着第三方设备的加入&#xff0c;将丰富Find My Network的版图。产…

【Git】error: bad signature 0xb86f1e1 和 bfatal: index file corrupt

一、问题 之前都好好的&#xff0c;今天执行 git add .的时候突然报错 报错原因翻译成中文&#xff1a;索引文件损坏 二、解决方法 方法1&#xff1a; 删除.git隐藏文件夹中的index文件 然后执行 git reset 重新生成index文件 git reset 方法2&#xff1a; 重新从远程克隆…

用户和组及权限管理

用户至少属于一个组,在创建时如果不指定组,将会创建同名的组 用户只能有一个基本组(主组),但可以隶属于多个附加组 如果一个组作为某用户的基本组,此组将不能被删除 UID: 用户标识 GID: 组的标识 root管理员的uid及gid 都为0 用户的配置文件: 1./etc/passwd test:x:1000:1000…

文献速递:深度学习乳腺癌诊断---基于深度学习的图像分析预测乳腺癌中HE染色组织病理学图像的PD-L1状态

Title 题目 Deep learning-based image analysis predicts PD-L1 status from H&E-stained histopathol ogy images in breast cancer 基于深度学习的图像分析预测乳腺癌中H&E染色组织病理学图像的PD-L1状态 01 文献速递介绍 编程死亡配体-1&#xff08;PD-L1&…

代码随想录算法训练营三刷day24 | 回溯算法 之 理论基础 77. 组合

三刷day24 理论基础77. 组合递归函数的返回值以及参数回溯函数终止条件单层搜索的过程 理论基础 回溯法解决的问题都可以抽象为树形结构。 因为回溯法解决的都是在集合中递归查找子集&#xff0c;集合的大小就构成了树的宽度&#xff0c;递归的深度&#xff0c;都构成的树的深…

网络安全,硬防迪云

要减少被攻击的频率&#xff0c;游戏开发者可以采取以下措施&#xff1a; 1. 强化安全措施&#xff1a;确保游戏服务器和用户数据的安全性&#xff0c;加密网络传输&#xff0c;防止黑客攻击和数据泄露。 2. 更新和修复漏洞&#xff1a;定期检查游戏代码和服务器&#xff0c;…

css3 实现html样式蛇形布局

文章目录 1. 实现效果2. 实现代码 1. 实现效果 2. 实现代码 <template><div class"body"><div class"title">CSS3实现蛇形布局</div><div class"list"><div class"item" v-for"(item, index) …

如何使用第三方接入淘宝商品详情(主图,详情图)

1、找到可用的API接口&#xff1a;首先&#xff0c;需要找到支持查询商品信息的API接口。这些信息通常可以在电商平台的官方文档或开发者门户网站上找到。 2、注册并获取API密钥&#xff1a;在使用API接口之前&#xff0c;需要注册并获取API密钥。API密钥是识别身份的唯一标识符…

区块链技术中的共识机制算法:以权益证明(PoS)为例

引言&#xff1a; 在区块链技术的演进过程中&#xff0c;共识机制算法扮演着至关重要的角色。除了广为人知的工作量证明&#xff08;PoW&#xff09;外&#xff0c;权益证明&#xff08;Proof of Stake&#xff0c;PoS&#xff09;也是近年来备受关注的一种共识算法。 …

C# 读取多条数据记录导出到 Word 标签模板

目录 应用需求 实现步骤 范例运行环境 配置Office DCOM 实现代码 组件库引入 ​编辑 核心代码 小结 应用需求 将数据库数据表中的数据输出并打印&#xff0c;WORD 是一个良好的载体&#xff0c; 在应用项目里&#xff0c;许多情况下我们会使用数据记录结合 WORD 标签模…

Halcon OCR文字识别

1、OCR文字识别 FontFile : Universal_0-9_NoRej dev_update_window (off) read_image (bottle, bottle2) get_image_size (bottle, Width, Height) dev_open_window (0, 0, Width, Height, black, WindowHandle) set_display_font (WindowHandle, 16, mono, true, false) dev…

妇女节专访|勇敢踏入未知领域,她的 Web3 奇妙之旅

Web3 的出现席卷着数字世界的剧烈变革&#xff0c;让每个人与互联网和数字资产互动的方式产生了深刻的变化。Web3 所强调的去中心化特征&#xff0c;使其成为人们对理想未来世界的一个缩影。而作为一个以技术为核心的新兴领域&#xff0c;Web3 也难以避免传统认知中男性占主导地…

信息检索(十一):Nonparametric Decoding for Generative Retrieval

Nonparametric Decoding for Generative Retrieval 摘要1. 引言2. 相关工作3. 非参数解码3.1 关键优势3.2 Base Np3.3 异步 Np3.4 对比 Np3.5 聚类 4. 实验设置4.1 基线4.2 数据集和评价指标4.3 构建CE 的细节 5. 实验结果5.1 普通解码 vs Np 解码5.2 非参数解码的优点5.3 什么…

Win11安装Plsql140报错2503

一、安装异常 二、解决办法 出现上述问题&#xff0c;主要是因为msi包安装的权限问题&#xff0c;使用管理员权限安装即解决 。cmd控制台以管理员身份打开WINR&#xff09;->(SHIFTCTRLRNTER)&#xff0c;进入到msi安装包目录下&#xff0c;以管理员身份安装即可&#xff1…