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();
    //must
    return 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();             //==localPos
    qDebug() << "DiagramView::globalPos:" << event->globalPos(); //==screenPos
    //must
    return 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 1
    QTransform deviceTransform; //in param
    QGraphicsItem *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
    //must
    return QGraphicsView::mousePressEvent(event);
}

//在DiagramItem追加如下测试函数
void DiagramItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {
    qDebug() << "DiagramItem# localpos:" << event->pos() << "scenePos:" << event->scenePos();
    //must
    return 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 version
        QTransform xinv = viewportTransform().inverted();
        return d->scene->items(xinv.mapRect(QRectF(pos.x(), pos.y(), 1, 1)), Qt::IntersectsItemShape, Qt::DescendingOrder, viewportTransform());
    }
    // Use the polygon version
    return 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被推荐用作变换类。限于文章篇幅,此文不再对此展开。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue指令相关

vue中有很多的指令像v-on、v-model、v-bind等是我们开发中常用的 常用指令 v-bind 单向绑定解析表达式 v-model 双向数据绑定 v-for 遍历数组/对象/字符串 v-on 绑定事件监听,可简写为@ v-show 条件渲染(动态控制节点是否存展示) v-if 条件渲染(动态控制节点是否存存在) v…

R 生存分析3:Cox等比例风险回归及等比例风险检验

虽然Kaplan-Meier分析方法目前应用很广&#xff0c;但是该方法存在一下局限: 对于一些连续型变量&#xff0c;必须分类下可以进行生存率对比 是一种单变量分析&#xff0c;无法同时对多组变量进行分析 是一种非参数分析方法&#xff0c;必须有患者个体数据才能进行分析 英国…

鸿蒙开发-UI-交互事件-焦点事件

鸿蒙开发-UI-图形-绘制几何图形 鸿蒙开发-UI-图形-绘制自定义图形 鸿蒙开发-UI-图形-页面内动画 鸿蒙开发-UI-图形-组件内转场动画 鸿蒙开发-UI-图形-弹簧曲线动画 鸿蒙开发-UI-交互事件-通用事件 鸿蒙开发-UI-交互事件-键鼠事件 文章目录 前言 一、基本概念 二、走焦规则 三、…

android_uiautomator元素定位

通过UIAUTOMATOR的text属性定位到元素&#xff0c;并打印文本from appium import webdriver from appium.webdriver.common.appiumby import AppiumBy import time # For W3C actions from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriv…

小程序富文本图片宽度自适应

解决这个问题 创建一个util.js文件,图片的最大宽度设置为100%就行了 function formatRichText(html) {let newContent html.replace(/\<img/gi, <img style"max-width:100%;height:auto;display:block;");return newContent; }module.exports {formatRichT…

2024.3.26

头文件: #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTime> #include <QTimerEvent> #include <QTimer> #include <QtTextToSpeech>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : p…

centos创建svn库步骤

1.切换root用户 1、设置root用户的密码&#xff1a; sudo passwd root 2、切换到root用户权限 su 3、切换回个人用户权限 exit 2.用root用户执行yum install -y subversion 3.创建文件夹mkdir -p /data/svn/repository 4.创建SVN 版本库 5.输入命令&#xff1a; svnadmin creat…

第 1 章.提示词:开启AI智慧之门的钥匙

什么是提示词&#xff1f; 提示词&#xff0c;是引导语言模型的指令&#xff0c;让用户能够驾驭模型的输出&#xff0c;确保生成的文本符合需求。 ChatGPT&#xff0c;这位文字界的艺术大师&#xff0c;以transformer架构为基石&#xff0c;能轻松驾驭海量数据&#xff0c;编织…

Java 与 Go:时间函数

在软件开发中&#xff0c;时间和日期函数是必不可少的组成部分&#xff0c;而 Java 和 Go 是两种备受欢迎的编程语言&#xff0c;它们在时间和日期函数方面有着各自独特的特性。本文将对比 Java 和 Go 在时间和日期函数上的优劣&#xff0c;并探讨它们的用法和适用场景。 Java…

2024品牌私域运营:「去中心化」正在成为企业决胜关键

越来越多的品牌选择以DTC模式与消费者互动和销售。通过与消费者建立紧密联系&#xff0c;不仅可提供更具成本效益的规模扩张方式&#xff0c;还能控制品牌体验、获取宝贵的第一方数据并提升盈利能力。许多企业采取的DTC私域策略以交易为中心的方法往往导致了成本上升和运营复杂…

网络编程(1)写一个简单的UDP网络通信程序【回显服务器】,并且实现一个简单的翻译功能

使用 JAVA 自带的api 目录 一、回显服务器 UdpEchoServer 服务器代码 客户端代码 二、翻译功能 UdpDictServer 在UdpDictServer里重写process方法 一、回显服务器 UdpEchoServer /*** 回显服务器* 写一个简单的UDP的客户端/服务器 通信的程序* 这个程序没有啥业务逻辑&am…

RGB与gray图互转

参考博客 https://www.cnblogs.com/alex-bn-lee/p/15140708.html 显示图片 import cv2 from PIL import Imageimg cv2.imread("./mingyu.png") img cv2.cvtColor(img, cv2.COLOR_BGR2RGB)display(Image.fromarray(img)) RGB 转gray gray cv2.cvtColor(img, cv2…

【从零开始学习Redis | 第八篇】认识Redis底层数据结构(下)

目录 前言&#xff1a; ZipList&#xff1a; Ziplist的特性: QucikList: QuicList特征&#xff1a; SkipList&#xff1a; 跳表特征&#xff1a; RedisObijct&#xff1a; 小心得&#xff1a; 总结&#xff1a; 前言&#xff1a; 在现代软件开发中&#xff0c;数据…

NGINX 反向代码 CORS

我遇到了一个问题就是 Nginx 是作为反向代理服务器部署的&#xff0c;但因为 Nginx 的配置导致 CORS 问题。 在这个时候我们可以对 Nginx 的配置文件进行修改&#xff1a; 在 location 后添加下面的内容&#xff1a; add_header Access-Control-Allow-Origin *; add_header A…

浏览器https受信任证书生成——openssl颁发受信任证书

站点常常由于没有受信任的第三方CA机构颁发证书,使用https访问时,浏览器常常会弹出不安全的提示,为解决该问题,可以使用openssl颁发个人证书来解决该问题。 1openssl安装及使用方式参考:32.9 x509_OpenSSL 中文手册https://www.openssl.net.cn/docs/230.html2.本文章所有生…

快速匹配和编译NXP官方uboot-imx

目录 概述 1 搭建编译环境 2 下载和编译uboot-imx 2.1 下载软件包 2.2 编译代码 3 总结 概述 本文主要讲述如何快速匹配和编译NXP官方uboot-imx。文中总结了生成u-boot文件的整个流程&#xff0c;笔者通过实操的方法&#xff0c;一步步从编译器下载&#xff0c;编译环境…

使用postman调用Vcenter-Api

一、下载postman Postman API Platform 二、Vcenter-APi-文档 Create Session | CIS | vSphere CIS REST APIs 三、如何调用&#xff1f; 一、获取访问凭证 两种方式进行鉴权&#xff0c;这里讲第一种。 二、使用postman调用Api获取凭证 下面就是vmware-api-session-id …

Linux Php 连接 SAP Hana数据库客户端

下载地址 : SAP Development Tools https://tools.hana.ondemand.com/#hanatools 进入hanaclient-2.19.21-linux-x64 无需编译&#xff0c;运行 ./hdbinst 提示没有权限&#xff0c;执行chmod x * 有个子目录里面的也是没有权限&#xff0c;进入那个子目录 执行chmod …

MySQL WHERE 条件查询

我们通常要求在执行 SELECT 查询时&#xff0c;都要带上查询条件。那这一节&#xff0c;我们就来学习一些简单的 WHERE 条件查询。 我们仍然以技术派文章表 article 为例&#xff0c;比如说我们要查找标题为“聊聊分库分表”的文章&#xff0c;可以这么写&#xff1a; SELECT *…

Nginx(Docker 安装的nginx)配置域名SSL证书

1.首先确保Linux环境上已经安装了docker&#xff08;可参考VMware使用和Linux安装Docker_wmware直接部署linux和安装docker后-CSDN博客 2.通过docker 安装nginx&#xff08;可参考Linux 环境安装Nginx—源码和Dokcer-CSDN博客&#xff09; 3.安装SSL证书 3.1 在宿主机中创建…