news 2026/4/16 14:41:07

关于 QGraphicsItemGroup 内部项目发生变化后group重新定位的问题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
关于 QGraphicsItemGroup 内部项目发生变化后group重新定位的问题

在项目中为了便于对组合后的图元进行管理,一般会继承 QGraphicsItemGroup 实现自己的 group 类,这样可以方便的借用 QGraphicsItemGroup 对内部图元进行管理,但同时也受到了 QGraphicsItemGroup 实现的约束。例如:QGraphicsItemGroup 对象的默认原点坐标为{0,0};对鼠标键盘的消息默认由 QGraphicsItemGroup 处理,内部图元控件不会处理等。这里主要讨论使用 addToGroup() 或 removeFromGroup() 时 group 的 bound 会发生变化,此时需要调整 group 的坐标及尺寸。因为我们可能一次向 group 添加/移除一个控件,也可能添加、移除多个,group 的坐标及尺寸最好在添加/移除后进行调整。但是为了提高代码的内聚性,我们更希望在 group 内部图元发生变化后由 group 自动调整位置及大小。继承 QGraphicsItemGroup 后重载 itemChange() 方法,当内部图元发生变化时可以通过 change == QGraphicsItem::ItemChildAddedChange 监听添加图元的信号、change == QGraphicsItem::ItemChildRemovedChange 监听移除图元的信号。如果在 itemChange() 方法中处理 group 的坐标及尺寸就会发生不可思议的问题:明明位置与尺寸都计算正确,但是内部图元的位置却发生莫名的偏移。通过监控内部图元的坐标发现计算的坐标完全正确,但是显示位置就是不对。如下图,图一是组合前的位置,图二是组合后的位置,组合后显示的选择框就是重新调整后的 group 的位置及大小,内部的矩形与圆形已经偏离了原位置。

image

图一 组合前的位置

image

图二 组合后的位置

发生这个问题的原因是:不能在 itemChange() 方法内处理 group 的位置及坐标,因为此时addToGroup() 或 removeFromGroup() 的代码还未执行完毕。看一下 addToGroup() 的源码:

// 文件位置 qt-everywhere-src-6.7.3\qtbase\src\widgets\graphicsview\qgraphicsitem.cpp

void QGraphicsItemGroup::addToGroup(QGraphicsItem *item)

{

Q_D(QGraphicsItemGroup);

if (!item) {

qWarning("QGraphicsItemGroup::addToGroup: cannot add null item");

return;

}

if (item == this) {

qWarning("QGraphicsItemGroup::addToGroup: cannot add a group to itself");

return;

}

// COMBINE

bool ok;

QTransform itemTransform = item->itemTransform(this, &ok);

if (!ok) {

qWarning("QGraphicsItemGroup::addToGroup: could not find a valid transformation from item to group coordinates");

return;

}

QTransform newItemTransform(itemTransform);

item->setPos(mapFromItem(item, 0, 0));

// 设置父项目时会触发 itemChange() 方法

item->setParentItem(this);

// removing position from translation component of the new transform

if (!item->pos().isNull())

newItemTransform *= QTransform::fromTranslate(-item->x(), -item->y());

// removing additional transformations properties applied with itemTransform()

QPointF origin = item->transformOriginPoint();

QMatrix4x4 m;

QList<QGraphicsTransform*> transformList = item->transformations();

for (int i = 0; i < transformList.size(); ++i)

transformList.at(i)->applyTo(&m);

newItemTransform *= m.toTransform().inverted();

newItemTransform.translate(origin.x(), origin.y());

newItemTransform.rotate(-item->rotation());

newItemTransform.scale(1/item->scale(), 1/item->scale());

newItemTransform.translate(-origin.x(), -origin.y());

// ### Expensive, we could maybe use dirtySceneTransform bit for optimization

item->setTransform(newItemTransform);

item->d_func()->setIsMemberOfGroup(true);

prepareGeometryChange();

d->itemsBoundingRect |= itemTransform.mapRect(item->boundingRect() | item->childrenBoundingRect());

update();

}

void QGraphicsItem::setParentItem(QGraphicsItem *newParent)

{

if (newParent == this) {

qWarning("QGraphicsItem::setParentItem: cannot assign %p as a parent of itself", this);

return;

}

if (newParent == d_ptr->parent)

return;

const QVariant newParentVariant(itemChange(QGraphicsItem::ItemParentChange,

QVariant::fromValue<QGraphicsItem *>(newParent)));

newParent = qvariant_cast<QGraphicsItem *>(newParentVariant);

if (newParent == d_ptr->parent)

return;

const QVariant thisPointerVariant(QVariant::fromValue<QGraphicsItem *>(this));

// setParentItemHelper 内部触发 itemChange() 方法

d_ptr->setParentItemHelper(newParent, &newParentVariant, &thisPointerVariant);

}

void QGraphicsItemPrivate::setParentItemHelper(QGraphicsItem *newParent, const QVariant *newParentVariant, const QVariant *thisPointerVariant)

{

Q_Q(QGraphicsItem);

if (newParent == parent)

return;

...

if (parent) {

// Remove from current parent

parent->d_ptr->removeChild(q);

if (thisPointerVariant)

parent->itemChange(QGraphicsItem::ItemChildRemovedChange, thisPointerVariant);

}

...

// Deliver post-change notification

if (newParentVariant)

q->itemChange(QGraphicsItem::ItemParentHasChanged, *newParentVariant);

if (isObject)

emit static_cast<QGraphicsObject *>(q)->parentChanged();

}

通过源码可以发现,如果在 itemChange() 内部处理 group 的坐标及尺寸,确实会出现很多问题,因为此时 addToGroup() 还未执行 transform 变换。

要解决此问题,就必须等待addToGroup() 执行完成再去计算坐标及尺寸。可以在 itemChange() 发射一个信号,采用异步处理该信号,将处理过程推迟到下一个事件循环。这样就能够完美解决问题。

具体代码可以参考项目 Compelling Data Designer 中 dashboard/BIDesigner/graphicsitemgroup.cpp 的处理过程。该项目用于数据的可视化设计,软件采用可扩展架构,支持扩展图形插件、数据接口。项目仍在开发中,目前已设计完成基本图形、多属性配置、动画等功能。

image

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/14 9:10:08

FFmpeg开发笔记(八十九)基于FFmpeg的直播视频录制工具StreamCap

着推拉流技术的发展&#xff0c;现在网络直播应用很普及了&#xff0c;打开许多App都能看到各式各样的直播节目&#xff0c;包括抖音、快手、虎牙、斗鱼、B站、小红书、YY、映客等等。有关视频推拉流的FFmpeg实现参见《FFmpeg开发实战&#xff1a;从零基础到短视频上线》一书的…

作者头像 李华
网站建设 2026/4/15 16:16:12

现代UI组件库与设计工具的无缝集成实战指南

现代UI组件库与设计工具的无缝集成实战指南 【免费下载链接】ant-design An enterprise-class UI design language and React UI library 项目地址: https://gitcode.com/gh_mirrors/ant/ant-design 在当今快速迭代的产品开发环境中&#xff0c;设计与开发团队之间的协作…

作者头像 李华
网站建设 2026/4/16 19:48:48

SpringAI基于内存存储向量

一、环境信息 1、参看如下&#xff1a; SpringAI入门学习 二、测试使用 1、创建SimpleVectorStore public interface VectorStore extends DocumentWriter {default String getName() {return this.getClass().getSimpleName();}void add(List<Document> documents)…

作者头像 李华
网站建设 2026/4/16 16:06:11

Matlab/Simulink 三相四桥臂逆变器仿真模型探索

matlab/simulink三相四桥臂逆变器仿真模型 采用电压外环&#xff0c;电流内环控制策略&#xff0c;交流侧可以接不平衡负载&#xff0c;在负载不平衡的情况下依然保持输出电压对称。 直流侧电压为750V&#xff0c;可以自己定义&#xff0c;交流侧为380/220v&#xff0c;平衡负载…

作者头像 李华
网站建设 2026/4/16 16:14:46

探索一维声子晶体结构带隙仿真模型

一维声子晶体结构带隙仿真模型在材料科学与物理学的交叉领域&#xff0c;声子晶体是一个极具吸引力的研究对象。今天咱们就来深入聊聊一维声子晶体结构带隙的仿真模型。 什么是一维声子晶体 简单来说&#xff0c;声子晶体是一种具有周期性结构的人工复合材料&#xff0c;其声学…

作者头像 李华
网站建设 2026/4/16 15:04:36

【计算机毕业设计案例】基于springboot+微信小程序的选修课管理系统的设计与实现“课程查询-在线选课-课表管理-成绩追踪”(程序+文档+讲解+定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华