当前位置: 首页 > news >正文

Qt 图形视图 /图形视图框架坐标系统的设计理念和使用方法

文章目录

  • 概述
  • Qt 坐标系统
  • 图形视图的渲染过程
  • Item图形项坐标系
  • Scene场景坐标系
  • View视图坐标系
  • map坐标映射
    • 场景坐标转项坐标
    • 视图坐标转图形项坐标
    • 图形项之间的坐标转换
  • 其他

概述

The Graphics View Coordinate System 图形视图坐标系统是Qt图形视图框架的重要组成部分,按照帮助文档中的划分,其与 The Graphics View Architecture 图形视图架构是在一个层级上的,主要包含图形项坐标系、场景坐标系、视图坐标系、坐标映射等内容。本文将结合帮助文档、Qt编程书籍、Qt示例程序等研习图形视图框架坐标系统的设计理念和使用方法。
在这里插入图片描述
补充,20230501,
编写HelpDoc的人,大概率是极为熟悉该模块的开发者,他们编写文档时的立意是全局化的,很有可能在写第一个章节时,潜移默化的渗透少许第二、甚至第三四五章节的知识点,尽管他们可能已经在尽力避免类似事情的发生,但那很难做到。所以有很多帮助内容,初读时很有跳跃感,比较吃力。此时先读个大概,了解全局,再回头细读,可能是一种可行的方法。

转载请标明原文链接,
https://blog.csdn.net/quguanxin/category_12597847.html

Qt 坐标系统

图形视图框架的坐标系统基于Qt大框架的坐标系统。逻辑坐标系的使用,使绘制代码独立于绘画设备的分辨率和坐标系,开发者可以在逻辑坐标系中编写绘制代码,QPainter 会自动将其映射到实际的设备坐标系。
在Qt框架下,QPainter类实际掌控着Qt坐标系统,并且它与 QPaintDevice 和 QPaintEngine 类共同组成了Qt的绘制系统,而且Qt绘制系统竟然还有一个名字,Arthur 亚瑟,这可能是一个项目代号,具体不得而知。QPainter 用以执行绘制操作,QPaintDevice 是一个二维绘制空间,QPainter就是在该空间进行绘制操作,QPaintEngine为painter在不同设备上的绘制操作提供了interface接口。
QPaintDevice类是所有支持绘制的类的基类,它的绘画能力被 QWidget, QImage, QPixmap, QPicture, and QOpenGLPaintDevice 继承。一个Qt绘制设备的默认坐标系原点在Device左上角,X轴向右增长,Y轴向下增长。在基于像素的设备上,以像素为默认长度单位,在打印设备上,默认单位是一个点(1/72英寸)。QPainter的逻辑坐标到QPaintDevice的物理坐标的映射,由QPainter的变换矩阵、视口和窗口处理。
在这里插入图片描述
图形视图坐标系统以Qt坐标系统为基础,不支持y轴向上增长的反向坐标系(inverted Y-axis coordinate system)。在传统的笛卡尔坐标系中,坐标原点 (0,0) 位于左下角,y轴向上增长,也就是说 y 值越大,坐标点在垂直方向上越高。而在Qt中采用Y轴向下增长,其原因可能是,在绘制图像时,通常图像的第一行像素位于最上方,向下递增,这符合存储图像数据的方式,如此更加自然。

图形视图的渲染过程

在这里插入图片描述
前边提到过,图形视图使用Qt的坐标系统,上述图片段落大意是将图形视图框架的渲染过程代入了 Qt 绘制系统。Qt图形视图框架下,在渲染时,图形视图的场景坐标对应于QPainter的逻辑坐标,视图坐标等同于设备坐标。有关逻辑坐标和设备坐标之间关系的更多内容,在Qt绘制系统的相关文章中讲述。在 ‘绘制’ 这个层次的概念上,Qt图形视图框架并没有脱离QPainter机制,而是在QPainter的基础上进行了扩展和封装,如,自动处理场景的渲染和重绘。

在图形视图中有三个有效effective的坐标系统:图形项坐标系、场景坐标系和视图坐标系。为了简化实现,Qt 图形视图提供了便利函数,允许您在三个坐标系统之间进行映射。

Item图形项坐标系

Item居于live自己的本地坐标系中,可以理解为Item在自己的本地坐标系中运作。图形项坐标系通常以项的中心点 (0,0) 为中心(注意,有的不是哦,后文有提及),这个点也是所有坐标变换的中心。图形项坐标系下的几何图元(Geometric primitives) 通常被称为,点、Item线、Item矩形。
当你创建一个自定义项时,你只需要关注Item本地坐标系即可,场景和视图会帮你执行所有变换。如在 Diagram Scene Example 示例程序中,创建基本流程图形状的代码,是在Item本地坐标系中进行绘制的,根本不用关心场景和视图坐标系的任何。

DiagramItem::DiagramItem(DiagramType diagramType, QMenu *contextMenu, QGraphicsItem *parent) : QGraphicsPolygonItem(parent) {...switch (myDiagramType) {...case Conditional:  //流程图形状,判定框myPolygon << QPointF(-100, 0) << QPointF(0, 100)<< QPointF(100, 0) << QPointF(0, -100)<< QPointF(-100, 0);break;case Step:        //流程图形状,过程框myPolygon << QPointF(-100, -100) << QPointF(100, -100)<< QPointF(100, 100) << QPointF(-100, 100)<< QPointF(-100, -100);break;...}setPolygon(myPolygon);...
}

Qt 图形视图框架在尽力的使用Item本地坐标系坐标交互位置信息,以方便开发者使用。例如,当你 (潜台词,在Item对象中) 接收到鼠标按下或拖拽进入事件时,事件(QGraphicsSceneMouseEvent)对象携带的位置会以Item坐标给出(到图形项对象)。QGraphicsItem::contains() 虚函数用于判断某个点是否在物品内,如果在则返回 true,否则返回 false,该函数的参数为Item坐标系下的坐标。类似地,Item的边界矩形和形状也是以Item坐标表示的。下文以 mousePressEvent 鼠标事件为例,观察该事件在视图、场景和图形项中的运行规律,

//在图形项QGraphicsItem类中
virtual void QGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent *event);
//在场景QGraphicsScene类中
virtual void QGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event);
//是视图QGraphicsView类中
virtual void QGraphicsView::mousePressEvent(QMouseEvent *event);

如上函数同为mousePressEvent原型,QGraphicsView 使用QWidget传统的QMouseEvent 事件类型,而 QGraphicsItem 和 QGraphicsScene 使用 QGraphicsSceneMouseEvent 事件类型,后者可传递两种位置信息,如下,
在这里插入图片描述
相关示例代码,放在了后续 ‘View视图坐标系’ 章节中。通过调试信息可确认,在Item对象中接收到的mouseEvent事件,通过 mouseEvent->pos 函数传递出来的是Item坐标系下的坐标。

父坐标 和 子坐标, (同一个视觉位置点的父坐标和子坐标)

在这里插入图片描述

不得不穿插对英语句子的理解,只是个人理解,原谅我英语没学好,
At item’s position is the coordinate of the item’s center point in its parent’s coordinate system。
结合上图中QGraphicsItem的构造函数,对上文的一个合理的释义,可能是,“(this这个) Item 的位置” 是指 (this) Item 的中心点在其父级(父Item) 坐标系中的坐标,有时也简称为父坐标。从某种意义上来说,若一个Item没有任何父(Item),那么场景将被当做是它的父(Item),顶层Item的位置以场景坐标来表示。整体上,这一小段Doc,目的是传递一个父坐标的概念,即,Item的(pos函数)位置是该Item原点在其父Item坐标系中的坐标。
在这里插入图片描述
理解上边这段话,着实花费了我不少时间。如上English,Child是子图形项的意思,parent是 this Item‘s parant 父项的意思,复数形式的coordinates可能通常代表坐标系、单数coordinate可能通常代表坐标点。与前文那句英文类似,这段话里似乎也藏了许多潜台词,故此晦涩。本段开始就告诉我们,子坐标系是相对于(be relative to)父坐标系的,真不太好理解。接着文中提到,如果子级Item未经过变换,则(潜台词,在窗口中,用户视觉上是同一个点,如下图的红点)子坐标与父级坐标之间的差距,与(the two)Items(前面提到的Child和Parent)在父坐标中的距离(也即父Item和子Item中心点之间的距离)相同。只能结合下文具体的例子继续理解,
在这里插入图片描述
如果一个未经过变换的子Item恰好位于其父项的中心点上,那么两个项的坐标系统将是相同的。然而,如上图中,红色矩形代表的子级Item,其在父级Item坐标系下的位置是 (10, 0),而不是 (0,0) 原点。此时,视觉红点,其在子级Item坐标系下的坐标为(0, 10),同时它在父级坐标系下的坐标是(10, 10)。如此,同样一个红点的子坐标(0,10)和父坐标(10,10)之间的差异便是(10,0),这个(10,0)也正好是子项和父项之间的距离(两个矩形中心点的距离),这个距离是10个单位长度。而文中 the difference between a child coordinate and a parent coordinate,并没有特定的指代某点,应该是指子Item上的任意的一个点,即,从视图上看到的任意一个点,其子项坐标与父项坐标的差值是一样的,都是子项和父项原点之间的距离。

由于项位置和项变换是与父项相对的,子项的坐标系并不不会受到父项坐标系变换的影响,尽管父项的变动隐式的转换了子项。还在上面的例子中,即使父项被旋转和缩放,子项坐标系下的(0,10)点依然将对应父项坐标系下的(10,10)点。然而,相对于场景,子项将跟随父项的变换和位置。如果父项被缩放2倍,那么,子项在场景中的坐标将变成(20,0),子项坐标系下的(10,0),在父项坐标系下对应的位置依然是(20,0),在场景坐标系中对应的点是(40,0)。

行文至此,我们再倒回头来聊聊前面没理解的 “子坐标系是相对于父坐标系的”,其含义是,无论父级图形项如何缩放、旋转等变动,子项在父项做标系下的位置不变,子项坐标系下的点,其对应的父项坐标系下的坐标也还是原来的值,没有变化,这就是所谓的相对性,即(子项任意可视点的)子项坐标系下的坐标与对应的父项坐标系下的坐标都还是原来的坐标。与上述相对性对立的,是子项相对于场景/绘制设备的绝对位置和变换则受父项变换的影响。可以不太精确的定义两个名词,相对坐标系和绝对坐标系。这种父子层级结构让坐标变换更加清晰可控,子项目可以独立于父项的变换来定义自身坐标,但最终的绝对变换位置仍由父项的属性确定。

除了少数例外( 如QGraphicsItem::pos() )之外,QGraphicsItem的函数都在项坐标系中运行,不受该项或其任何父项目的变换的影响。例如,一个项的边界矩形(即boundingRect)始终给出项坐标系下的坐标。

Scene场景坐标系

场景表示所有Item图形项的基础坐标系统。场景坐标系统描述的是所有顶层Item的位置,也是所有从View视图到Scene场景的 QGraphicsSceneMouseEvent 场景型事件的基石。场景中的每个项除了具有Item本地位置和本地边界矩形之外,还有一个场景位置和场景边界矩形 (QGraphicsItem::scenePos(), QGraphicsItem::sceneBoundingRect())。如下所示,
在这里插入图片描述

在这里插入图片描述
场景位置描述了项在场景坐标系中的位置,而项的场景边界矩形则构成了QGraphicsScene 确定场景中哪些区域已更改的基础。场景中的更改通过 QGraphicsScene::changed() 信号传播,参数是一个场景矩形列表。通过上图中对 boundingRect() 和 sceneBoundingRect() 的对比,可以直观的看出两者的区别,本地边界矩形被用以QGraphicsView的重绘过程,而场景边界矩形是混合了本地边界矩形和坐标系变换之后的场景坐标系下的矩形,其主要用以探测出项在场景中的位置变动。

QRectF Arrow::boundingRect() const {qreal extra = (pen().width() + 20) / 2.0;return QRectF(line().p1(), QSizeF(line().p2().x() - line().p1().x(), line().p2().y() - line().p1().y())).normalized().adjusted(-extra, -extra, extra, extra);
}

View视图坐标系

视图坐标系是窗口部件的坐标系。视图坐标系下的每个单元都对应一个像素。这个坐标系统的特殊之处在于,它是相对于 widget部件 或 viewport视口的,不受被观察场景的影响。QGraphicsView 的viewport 视口的左上角坐标永远是(0,0),右下角坐标永远是(视口宽度,视口高度)。

所有鼠标事件和拖放事件最初都是以视图坐标接收的,你需要将这些坐标映射到场景中,以便与Item项交互。也可以这么理解,用户的键鼠事件等最早(originally)是由视图对象捕获的,而场景对象可能是作为事件过滤器被安装到关联的视图对象上的,为此我们进行了如下验证。我们扩展 Diagram Scene Example 示例程序,添加从QGraphicsView派生的GraphicsView类,并分别在DiagramItem、DiagramScene、DiagramView 中新实现或修改 mousePressEvent 虚函数,

//派生图形项中对鼠标按压事件的处理
void DiagramItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {qDebug() << "\r\n--#-- DiagramItem::mousePressEvent --#--";qDebug() << "DiagramItem::pos:" << event->pos();qDebug() << "DiagramItem::scenePos:" << event->scenePos();//mustreturn QGraphicsPolygonItem::mousePressEvent(event);
}
//派生场景中对鼠标按压事件的处理
void DiagramScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) {qDebug() << "\r\n--#-- DiagramScene::mousePressEvent --#--";qDebug() << "DiagramScene::pos:" << mouseEvent->pos();qDebug() << "DiagramScene::scenePos:" << mouseEvent->scenePos();...
}
//派生视图中对鼠标按压事件的处理
void DiagramView::mousePressEvent(QMouseEvent *event) {qDebug() << " \r\n--#-- DiagramView::mousePressEvent --#--";qDebug() << "DiagramView::localPos:" << event->localPos();qDebug() << "DiagramView::windowPos:" << event->windowPos();qDebug() << "DiagramView::screenPos:" << event->screenPos();qDebug() << "DiagramView::pos:" << event->pos();             //==localPosqDebug() << "DiagramView::globalPos:" << event->globalPos(); //==screenPos//mustreturn QGraphicsView::mousePressEvent(event);
}/* //创建一个ShpeItem点击其靠近右下角的某个位置
--#-- DiagramView::mousePressEvent --#--
DiagramView::localPos: QPointF(355,265)
DiagramView::windowPos: QPointF(564,332)
DiagramView::screenPos: QPointF(664,432)
DiagramView::pos: QPoint(355,265)       //==localPos   //以视图左上角为00
DiagramView::globalPos: QPoint(664,432) //==screenPos--#-- DiagramScene::mousePressEvent --#--
DiagramScene::pos: QPointF(0,0)             //只要点击位置在Item内,则始终为00
DiagramScene::scenePos: QPointF(2573,2562)--#-- DiagramItem::mousePressEvent --#--
DiagramItem::pos: QPointF(75,78)           //以Item中心位置为00原点
DiagramItem::scenePos: QPointF(2573,2562)  //与在场景对象中的捕获坐标值是一致的
*/

通过上运行结果,我们验证了Qt鼠标事件在Qt图形视图框架下的传递顺序,基本可推定,视图对象会在最初捕获用户事件,然后可能的转换过程方案有:转为视图对象直接将 QMouseEvent 转为 QGraphicsSceneMouseEvent 事件;场景对象是视图对象的事件过滤器,图形项对象是场景的事件过滤器,由场景和图形项对象各自分别转换,但由于图形项没有从QObject继承,即无法安装过滤器,这种方案的可能性不大。针对上述问题,后续将研究相关源码,并在其他文章中继续讨论。

当使用未经过任何变换(如缩放、旋转等)的视图observing观察场景时,场景上的unit单位长度对应屏幕上的一个像素。也即在没有应用任何变换的情况下,场景坐标与视图坐标是相互对应的。
例如,如果在场景坐标系中有两个点A(10,10) 和 B(20,10),那么它们在未经变换的视图中的像素距离就是10个像素。这种对应的关系使得最开始在场景上布局和定位图形项时很方便,因为你可以直接以像素为单位来思考。若应用了任何变换,比如缩放或旋转,场景坐标与视图坐标之间就不再是简单的1-1对应关系了,这时就需要使用Qt提供的坐标映射函数来在不同坐标系之间转换。

map坐标映射

通常,在处理场景中的图形项时,需要在场景、图形项、视图之间进行必要的坐标或任意矩形的映射。

例如,当你在 QGraphicsView 的视口中单击鼠标时,可以通过调用 QGraphicsView::mapToScene() 将坐标映射到场景,然后调用QGraphicsScene::itemAt() 来查询光标下的图形项对象。如果你想知道一个图形项在视图视口中的位置,可以在图形项对象上调用QGraphicsItem::mapToScene(),然后在视图上调用QGraphicsView::mapFromScene()。如果你想找出视口中椭圆内的所有图形项,可以将椭圆对象的 QPainterPath 传递给 mapToScene() 映射到场景,然后将映射后的路径传递给QGraphicsScene::items()。

在图形项和场景之间,通过调用QGraphicsItem::mapToScene() and QGraphicsItem::mapFromScene() 函数来相互映射坐标和形状。在图形项与其父项之间通过调用QGraphicsItem::mapToParent() and QGraphicsItem::mapFromParent()来进行映射,或者通过调用 QGraphicsItem::mapToItem() 和 QGraphicsItem::mapFromItem() 在(同一个场景下)不同图形项之间进行映射。所有的映射函数都支持点、矩形、多边形和路径,以 mapToScene 为例,在QGraphicsItem类(图左) 和 QGraphicsView类(图右),有以下6种重载,
在这里插入图片描述
下文将对部分场景下的坐标转换过程,通过示例程序进行说明。

场景坐标转项坐标

在整个Qt图形视图坐标系统中,场景坐标处于至关重要的承上启下的位置。且通过上文可知,场景类并没有直接提供坐标映射的接口,这些映射接口仅存在于QGraphicsItem类 和 QGraphicsView类中。那么在场景对象的处理函数中,如何进行坐标的相关运算呢?
在这里插入图片描述
除了上述 itemAt 函数之外,QGraphicsScene 类还提供了 6 个items(…) 的重载函数、selectedItems() 、views() 等函数,获取目标图形项实例对象。一旦通过场景坐标获取到了图形项实例和视图实例,便可以使用QGraphicsItem类 和 QGraphicsView类中的坐标映射接口。

在 Diagram Scene Example 示例程序,场景类的实现中,就使用了items函数的一个版本,

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();...
}

在Diagram Scene Example 示例程序,场景类的实现中,添加如下itemAt函数的使用测试,

void DiagramScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) {
...#if 1QTransform deviceTransform; //in paramQGraphicsItem *tempitem = this->itemAt(mouseEvent->scenePos(), deviceTransform);QPointF ItemLocalPt = tempitem->mapFromScene(mouseEvent->scenePos());qDebug() << "DiagramScene::ItemLocalPt:" << ItemLocalPt;#endif
...
}

如上,通过调用场景类的 itemAt 函数,得到 QGraphicsItem 实例对象指针后,便可使用 QGraphicsItem 的映射函数啦。

视图坐标转图形项坐标

如上,视图中提供(be available)了与图形项类相同的映射函数,用于在视图和场景之间进行映射。要从视图映射到图形项,你首先需要将视图坐标映射到场景坐标系,然后再从场景映射到图形项坐标系。我们扩展 Diagram Scene Example 示例程序,

//从QGraphicsView派生DiagramView,替代原view实例 /实现如下非功能的测试函数
void DiagramView::mousePressEvent(QMouseEvent *event) {//QPoint#pos()==QPointF#localPos()qDebug() << "DiagramView# localPos:" << event->pos() << "windowPos:" << event->windowPos() << "screenPos:" << event->screenPos();
#if 1//在视图中得到鼠标位置的Item本地坐标QPointF itemLocalPos;//视图坐标转场景坐标 //event->pos()==event->localPos()QPointF scenePtFromView = mapToScene(event->pos());//获取鼠标事件发生的位置对应的QGraphicsItem实例QGraphicsItem *item = this->scene()->itemAt(scenePtFromView, QTransform());if (item)itemLocalPos = item->mapFromScene(scenePtFromView);qDebug() << "DiagramView# localpos:" << itemLocalPos << "scenePos:" << scenePtFromView;
#endif//mustreturn QGraphicsView::mousePressEvent(event);
}//在DiagramItem追加如下测试函数
void DiagramItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {qDebug() << "DiagramItem# localpos:" << event->pos() << "scenePos:" << event->scenePos();//mustreturn QGraphicsPolygonItem::mousePressEvent(event);
}/* 运行修改后的示例程序,添加一个ShpeItem,点击其中的位置,运行结果如,
DiagramView# localPos: QPoint(187,171) windowPos: QPointF(396,238) screenPos: QPointF(496,338)
DiagramView# localpos: QPointF(5,47) scenePos: QPointF(2405,2468)
DiagramItem# localpos: QPointF(5,47) scenePos: QPointF(2405,2468)
*/

如上代码,将View视图实例QMouseEvent事件中的本地坐标(以视图Widget左上角为原点),也即视图坐标系下的坐标,通过 QGraphicsView::mapToScene 函数转换为场景坐标系下的坐标,进而找到该场景坐标下的Item实例,之后通过调用Item的mapFromScene函数,将场景坐标映射到Item本地坐标系下。通过对比DiagramView对象和DiagramItem对象内的打印信息,可验证转换效果。

在 Drill Down Example 这个示例程序中,

void View::mouseReleaseEvent(QMouseEvent *event) {if (QGraphicsItem *item = itemAt(event->pos())) {if (ImageItem *image = qgraphicsitem_cast<ImageItem *>(item))showInformation(image);}QGraphicsView::mouseReleaseEvent(event);
}

如上,我们细看 QGraphicsView 类,可知,QGraphicsView 自身与 QGraphicsScene 类一样,同样提供了 itemAt(…) 和 items(…) 函数。但是本质上还是要将视图坐标映射到场景坐标系,然后再从场景映射到图形项坐标系。透过以下 QGraphicsView::items 函数的源码,可以证明这一点,

//
QGraphicsItem *QGraphicsView::itemAt(const QPoint &pos) const {Q_D(const QGraphicsView);if (!d->scene)  return 0;const QList<QGraphicsItem *> itemsAtPos = items(pos);return itemsAtPos.isEmpty() ? 0 : itemsAtPos.first();
}
//
QList<QGraphicsItem *> QGraphicsView::items(const QPoint &pos) const {Q_D(const QGraphicsView);if (!d->scene)return QList<QGraphicsItem *>();// ### Unify these two, and use the items(QPointF) version in// QGraphicsScene instead. The scene items function could use the viewport// transform to map the point to a rect/polygon.if ((d->identityMatrix || d->matrix.type() <= QTransform::TxScale)) {// Use the rect versionQTransform xinv = viewportTransform().inverted();return d->scene->items(xinv.mapRect(QRectF(pos.x(), pos.y(), 1, 1)), Qt::IntersectsItemShape, Qt::DescendingOrder, viewportTransform());}// Use the polygon versionreturn d->scene->items(mapToScene(pos.x(), pos.y(), 1, 1), Qt::IntersectsItemShape, Qt::DescendingOrder, viewportTransform());
}

图形项之间的坐标转换

以 Diagram Scene Example Arrow 类的 updatePosition 函数为例,说明Item图形项之间的坐标映射过程。

void Arrow::updatePosition() {//将图形项myStartItem/myEndItem的原点转换为另一个Arrow图形项的坐标系下QLineF line(mapFromItem(myStartItem, 0, 0), mapFromItem(myEndItem, 0, 0));//绘制myStartItem/myEndItem中心点之间的线段setLine(line);
}

如上,为了绘制两个ShapeItem之间的连接线,将myStartItem项的中心原点映射为线段的起点,将myEndItem项的中心原点映射为线段的终点,构建 QLineF 对象并更新 QGraphicsLineItem 类内部维护的线段成员变量。特别需要注意的是,图形项QGraphicsLineItem的本地坐标系和场景坐标系是重合的,因此,this->mapFromItem(myStartItem, 0, 0) 与 myStartItem->mapToScene(QPointF(0,0)) 是相等的,而 this->mapToScene(QPointF(0,0)) 和 this->scenePos() 等返回的都是(0, 0)场景坐标原点。

其他

前面讲到 itemAt 或 items 函数时,它们都有一个QTransform 对象作为输入参数,在 QGraphicsItem 的 mapFromScene、mapToScene等函数实现、QGraphicsItemPrivate的诸多实现中都用到了 QTransform 成员变量或函数参数。该类在 Qt 大框架的模块划分上属于 Qt GUI 模块,隶属 Qt 绘制系统。
要更彻底掌握 Qt 绘制系统坐标系统 和 Qt 图形视图框架坐标系统的设计思路和使用方法,或者仅是想稍加深入的阅读上述框架的源代码,QTransform 是绕不开的知识点。它用于指定二维坐标系统变换,提供了平移(translate)、缩放(scale)、剪切(shear)、旋转(rotate) 和投影(project) 等基本变换操作,常被用于渲染图形时对坐标系统进行变换。QTransform是一个真正的3x3矩阵,允许进行透视投影变换,与QMatrix相比,它功能更加强大,在Qt中QTransform被推荐用作变换类。限于文章篇幅,此文不再对此展开。

相关文章:

Qt 图形视图 /图形视图框架坐标系统的设计理念和使用方法

文章目录 概述Qt 坐标系统图形视图的渲染过程Item图形项坐标系Scene场景坐标系View视图坐标系map坐标映射场景坐标转项坐标视图坐标转图形项坐标图形项之间的坐标转换 其他 概述 The Graphics View Coordinate System 图形视图坐标系统是Qt图形视图框架的重要组成部分&#xf…...

视频号小店类目资质如何申请?新手看一遍就懂了!

我是电商珠珠 大家在视频号小店后台新增商品的时候&#xff0c;需要先完成类目资质的申请&#xff0c;通过后才可以上架相关商品。 而类目资质分为普通类目和特殊类目&#xff0c;如果你所上架的商品属于开放类目&#xff0c;那么就去按照普通类目资质去申请。 如果是定向准…...

整合SpringSecurity+JWT实现登录认证

一、关于 SpringSecurity 在 Spring Boot 出现之前&#xff0c;SpringSecurity 的使用场景是被另外一个安全管理框架 Shiro 牢牢霸占的&#xff0c;因为相对于 SpringSecurity 来说&#xff0c;SSM 中整合 Shiro 更加轻量级。Spring Boot 出现后&#xff0c;使这一情况情况大有…...

C# Onnx Yolov9 Detect 物体检测

目录 介绍 效果 项目 模型信息 代码 下载 C# Onnx Yolov9 Detect 物体检测 介绍 yolov9 github地址&#xff1a;https://github.com/WongKinYiu/yolov9 Implementation of paper - YOLOv9: Learning What You Want to Learn Using Programmable Gradient Information…...

Flink SQL 基于Update流出现空值无法过滤问题

问题背景 问题描述 基于Flink-CDC &#xff0c;Flink SQL的实时计算作业在运行一段时间后&#xff0c;突然发现插入数据库的计算结果发生部分主键属性发生失败&#xff0c;导致后续计算结果无法插入&#xff0c; 超过失败次数失败的情况问题报错 Caused by: java.sql.BatchUp…...

git-怎样把连续的多个commit合并成一个?

Git怎样把连续的多个commit合并成一个&#xff1f; Git怎样把连续的多个commit合并成一个&#xff1f; 参考URL: https://www.jianshu.com/p/5b4054b5b29e 查看git日志 git log --graph比如下图的commit 历史&#xff0c;想要把bai “Second change” 和 “Third change” 这…...

2024年2月游戏手柄线上电商(京东天猫淘宝)综合热销排行榜

鲸参谋监测的线上电商&#xff08;京东天猫淘宝&#xff09;游戏手柄品牌销售数据已出炉&#xff01;2月游戏手柄销售数据呈现出强劲的增长势头。 根据鲸参谋数据显示&#xff0c;今年2月游戏手柄月销售量累计约43万件&#xff0c;同比去年上涨了78%&#xff1b;销售额累计达1…...

Sass5分钟速通基础语法

前言 近来在项目中使用sass,想着学习一下,但官方写的教程太冗杂,所以就有了本文速通Sass的基础语法 Sass 是 CSS 的一种预编译语言。它提供了 变量&#xff08;variables&#xff09;、嵌套规则&#xff08;nested rules&#xff09;、 混合&#xff08;mixins&#xff09; 等…...

百度蜘蛛池平台在线发外链-原理以及搭建教程

蜘蛛池平台是一款非常实用的SEO优化工具&#xff0c;它可以帮助网站管理员提高网站的排名和流量。百度蜘蛛池原理是基于百度搜索引擎的搜索算法&#xff0c;通过对网页的内容、结构、链接等方面进行分析和评估&#xff0c;从而判断网页的质量和重要性&#xff0c;从而对网页进行…...

Android_ android使用原生蓝牙协议_连接设备以后,给设备发送指令触发数据传输---Android原生开发工作笔记167

之前通过蓝牙连接设备的时候,直接就是连接上蓝牙以后,设备会自动发送数据,有数据的时候,会自动发送,但是,有一个设备就不会,奇怪了很久,设备启动了也连接上了,但是就是没有数据过来. 是因为,这个设备有几种模式是握力球,在设备连接到蓝牙以后,需要,给设备通过蓝牙发送一个指令…...

【Java面试题】操作系统

文章目录 1.进程/线程/协程1.1辨别进程和线程的异同1.2优缺点1.2.1进程1.2.2线程 1.3进程/线程之间通信的方法1.3.1进程之间通信的方法1.3.2线程之间通信的方法 1.4什么是线程上下文切换1.5协程1.5.1协程的定义&#xff1f;1.5.2使用协程的原因&#xff1f;1.5.3协程的优缺点&a…...

SQLite数据库成为内存中数据库(三)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLite使用的临时文件&#xff08;二&#xff09; 下一篇&#xff1a;SQLite中的原子提交&#xff08;四) ​​ SQLite数据库通常存储在单个普通磁盘中文件。但是&#xff0c;在某些情况下&#xff0c;数据库可能…...

多张图片怎么合成一张gif?快来试试这个方法

将多张图片合成一张gif动图是现在常见的图像处理的方式&#xff0c;适合制作一些简单的动态图片。通过使用在线图片合成网站制作的gif动图不仅体积小画面丰富&#xff0c;画质还很清晰。不需要下载任何软件小白也能轻松上手&#xff0c;支持上传jpg、png以及gif格式图片&#x…...

爬取b站音频和视频数据,未合成一个视频

一、首先找到含有音频和视频的url地址 打开一个视频&#xff0c;刷新后&#xff0c;找到这个包&#xff0c;里面有我们所需要的数据 访问这个数据包后&#xff0c;获取字符串数据&#xff0c;用正则提取&#xff0c;再转为json字符串方便提取。 二、获得标题和音频数据后&…...

mysql进阶知识总结

1.存储引擎 1.1MySQL体系结构 1).连接层 最上层是一些客户端和链接服务&#xff0c;包含本地sock通信和大多数基于客户端/服务端工具实现的类似于TCP/IP的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念&#xff0c;为通过认证…...

量化交易入门(二十五)什么是RSI,原理和炒股实操

前面我们了解了KDJ&#xff0c;MACD&#xff0c;MTM三个技术指标&#xff0c;也进行了回测&#xff0c;结果有好有坏&#xff0c;今天我们来学习第四个指标RSI。RSI指标全称是相对强弱指标(Relative Strength Index),是通过比较一段时期内的平均收盘涨数和平均收盘跌数来分析市…...

快速上手Spring Cloud 九:服务间通信与消息队列

快速上手Spring Cloud 一&#xff1a;Spring Cloud 简介 快速上手Spring Cloud 二&#xff1a;核心组件解析 快速上手Spring Cloud 三&#xff1a;API网关深入探索与实战应用 快速上手Spring Cloud 四&#xff1a;微服务治理与安全 快速上手Spring Cloud 五&#xff1a;Spring …...

python——遍历网卡并禁用/启用

一、遍历网卡 注意&#xff1a;只能遍历到启用状态的网卡&#xff0c;如果网卡是禁止状态&#xff0c;则遍历不到&#xff01;&#xff01;&#xff01; import os import time import psutil import loggingdef get_multi_physical_network_card():physical_nic_list []try:…...

初识 51

keil的使用: 具体细节请移步我上一篇博客:创建第一个51文件-CSDN博客 hex -- 汇编语言实现的文件 -- 直接与单片机对接的文件 单片机 -- 一个集成电脑芯片 单片机开发版 -- 基于单片机的集成电路 stc 89 c52RC / RD 系列单片机 命名规则: 89 -- 版本号&#xff1f; C --…...

【回溯与邻里交换】纸牌三角

1.回溯算法 旋转有3种可能&#xff0c;镜像有2种 所以最后次数&#xff1a;counts/3/2 #include<iostream> using namespace std;int num[9]; int counts0; bool bools[9];//默认为false int dfs(int step){if(step9){//索引 if((num[0]num[1]num[2]num[3]num[3]num[4]n…...

微服务(基础篇-004-Feign)

目录 http客户端Feign Feign替代RestTemplate&#xff08;1&#xff09; Feign的介绍&#xff08;1.1&#xff09; 使用Feign的步骤&#xff08;1.2&#xff09; 自定义配置&#xff08;2&#xff09; 配置Feign日志的两种方式&#xff08;2.1&#xff09; Feign使用优化…...

Linux IRC

目录 入侵框架检测 检测流程图 账号安全 查找账号中的危险信息 查看保存的历史命令 检测异常端口 入侵框架检测 1、系统安全检查&#xff08;进程、开放端口、连接、日志&#xff09; 这一块是目前个人该脚本所实现的功能 2、Rootkit 建议使用rootkit专杀工具来检查&#…...

五、Elasticsearch 集成

目录 5.1 Spring Data 框架集成5.1.1 Spring Data 框架介绍5.1.2 Spring Data Elasticsearch 介绍5.1.3 Spring Data Elasticsearch 版本对比5.1.4 集成步骤 5.1 Spring Data 框架集成 5.1.1 Spring Data 框架介绍 Spring Data 是一个用于简化数据库开发的开源框架。其主要目…...

Qt 完成图片的缩放拖动

1. 事件和函数 主要使用事件paintEvent(QPaintEvent *event)和drawTiledPixmap函数实现绘图。 paintEvent事件在改变窗口大小、移动窗口、手动调用update等情形下会被调用。需先了解下绘图该函数的用法。 - QPainter::drawTiledPixmap(int x, int y, int w, int h, const QPi…...

Linux 内核工具 iptables 配置TCP/UDP端口转发(命令参考)

1、配置TCP端口转发 把本机20000/TCP端口转发到7.7.7.7:20000 iptables -t nat -A PREROUTING -p tcp --dport 20000 -j DNAT --to-destination 7.7.7.7:20000 iptables -t nat -A POSTROUTING -j MASQUERADE 2、配置UDP端口转发 把本机20000/UDP端口转发到7.7.7.7:20000 i…...

love 2d Lua 俄罗斯方块超详细教程

源码已经更新在CSDN的码库里&#xff1a; git clone https://gitcode.com/funsion/love2d-game.git 一直在找Lua 能快速便捷实现图形界面的软件&#xff0c;找了一堆&#xff0c;终于发现love2d是小而美的原生lua图形界面实现的方式。 并参考相关教程做了一个更详细的&#x…...

SpringBoot+ElasticSearch实现文档内容抽取、高亮分词、全文检索

需求 产品希望我们这边能够实现用户上传PDF、WORD、TXT之内得文本内容&#xff0c;然后用户可以根据附件名称或文件内容模糊查询文件信息&#xff0c;并可以在线查看文件内容。 一、环境 项目开发环境&#xff1a; 后台管理系统springbootmybatis_plusmysqles 搜索引擎&#…...

利用Redis实现简单的短信登录

在现代应用中&#xff0c;短信登录是一种常见的用户认证方式。它提供了一种便捷的登录方式&#xff0c;同时也增加了账户的安全性。在本文中&#xff0c;我们将介绍如何使用 Redis 实现短信登录的功能&#xff0c;并提供相应的 Java 实现层代码。 1、短信验证码的生成与存储当用…...

在 Linux 中通过 SSH 执行远程命令时,无法自动加载环境变量(已解决)

问题场景 目前我的环境变量都存储在 /etc/profile 文件中&#xff0c;当我通过远程 SSH 执行一些命令时&#xff0c;提示命令找不到&#xff0c;如下所示&#xff1a; 问题出现原因 这里找到了一张出自尚硅谷的图片&#xff0c;很好的解释了该问题&#xff1a; 这是由于 Linu…...

c++使用类的一些注意事项

前言&#xff1a; 本篇内容为前面的补充&#xff0c;介绍了我们使用类时需要注意些什么以及一些编译器的优化&#xff0c;可能有些理解不到位或者错误&#xff0c;请斧正。 目录 前言&#xff1a; 1.再谈构造函数 2.&#xff08;c98&#xff09;隐式类型转换中的编译器的优…...

html门户网站开发源代码/百度软件下载

Web 不论你在 web 上做什么, 都离不开请求和响应, web请求作为某个用户交互的结果由web浏览器发送到web服务器, 在web服务器上, 会生成web响应(或应答)并发回到 web 浏览器. 如果web请求的是静态内容, 比如一个Html文件, 图像或者是存储在web服务器硬盘上的其他内容, web服务器…...

地图类网站开发实战教程/快速排名seo

紫薯芝麻饼松软的手指食物。食材紫薯50克、面粉10克、芝麻酱1勺、鸡蛋1个、油2克标签手指食物、缓解便秘、加餐零食、不爱主食、各种饼类、中式面食、10m难度★★★标签推荐月龄仅针对展示的成品图&#xff0c;可根据宝宝月龄调整食材大小1. 紫薯提前蒸熟压成泥&#xff0c;鸡蛋…...

做网站郑州汉狮/上海城市分站seo

Python3中有六种基本数据类型&#xff1a; Number(数字) String(字符串) List(列表) Tuple(元组) Set(集合) Dict(字典) 他们之前有什么区别?联系?直接上图&#xff1a;按照图示的分类,开始学习吧。(python3) Number(数字类型) Python3 支持 int、float、bool、complex(复数)…...

武汉 大型 网站建设/今日足球赛事推荐

在路由文件需要守卫的path后面加上meta {path: /home,component: home,meta:{requireAuth:true}}在main.js里面加上 //路由守卫 router.beforeEach((to, from, next) > {console.log(to);console.log(from);if (to.meta.requireAuth) { // 判断该路由是否需要登录权限if(J…...

sublime怎么做网站/微信朋友圈推广平台

如何来测试龙门铣床的准确度? 现代社会&#xff0c;机床的使用已经是非常常见了&#xff0c;尤其是近几年推陈出新的出来来好几款高精度的机床。但也正式因为如此&#xff0c;人们对于他的保养问题就有所怠慢了&#xff0c;惹得龙门铣床工作是频频出现问题。下面给大家说说这…...

吴中区住房和城乡建设局网站/网站怎么优化搜索

栈栈,存储货物或供旅客住宿的地方,可引申为仓库数据结构中的栈栈是一组数据的存放方式,特点是先进后出&#xff0c;后进先出&#xff0c;也有人是通俗的说吃吐原理&#xff0c;最后吃的最新吞出来&#xff0c;看看大概的图示代码小案例function one() {function two() {functio…...