在项目中为了便于对组合后的图元进行管理,一般会继承 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 的位置及大小,内部的矩形与圆形已经偏离了原位置。

图一 组合前的位置

图二 组合后的位置
发生这个问题的原因是:==不能在 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](https://github.com/lsyeei/dashboard "Compelling Data Designer") 中 dashboard/BIDesigner/bigraphicsview.cpp 的处理过程。该项目用于数据的可视化设计,软件采用可扩展架构,支持扩展图形插件、数据接口。项目仍在开发中,目前已设计完成基本图形、多属性配置、动画等功能。

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

  1. MyEclipse修改项目名称后,部署到tomcat问题

    问题描述: 修改项目名称后,部署到tomcat server,部署出来的文件夹名还是旧的名称. 解决方案: 光把项目重命名是不够的,还要修改一下Myeclipse里面的配置. 工程名->右键-& ...

  2. [eclipse]改项目名称后tomcat连接问题解决方法

    背景:在我们使用eclipse进行项目开发时,有时候会需要修改项目名称,当改动项目名称后发现tomcat启动访问出现问题,使用新的项目名称不可行,使用旧的项目名称却可以.修改web.xml里面的dis ...

  3. MyEclipse修改项目名称后,部署到 tomcat问题

    问题描述: 修改项目名称后,部署到tomcat问题 解决方案: 项目->属性->myelcipse->web下,修 改web context root就可! 要在eclipse里面改 ...

  4. C#项目打包后安装的桌面快捷方式图标怎么设置成自己想要的图标

    #项目打包后安装的桌面快捷方式图标怎么设置成自己想要的图标 2012-08-25 09:11匿名 | 浏览 3286 次  C#编程 C#项目用vs2005自带的工具打包后安装的桌面快捷方式图标怎么设 ...

  5. MyEclipse修改项目名称后,部署到tomcat问题。

    1.问题描述: 修改项目名称后,部署到tomcat server,部署出来的文件夹名还是旧的名称. 2.解决方案: 光把项目重命名是不够的,还要修改一下Myeclipse里面的配置. a). 工程名- ...

  6. vue2.0 项目build后资源文件报错404的解决方案

    当vue项目build后,我们会看到css.js报错404的问题: 那我们就去找错误原因吧. 首先,查看build后的dist文件目录 可以看出,js.css在index.html的同级目录下: 然后 ...

  7. Springboot 项目启动后执行某些自定义代码

    Springboot 项目启动后执行某些自定义代码 Springboot给我们提供了两种"开机启动"某些方法的方式:ApplicationRunner和CommandLineRun ...

  8. 解决使用maven的java web项目导入后出现的有关问题 -cannot be read or is not a valid ZIP file

    解决使用maven的java web项目导入后出现的有关问题 -cannot be read or is not a valid ZIP file   错误问题:虽然查找repository目录下是有 ...

  9. springboot 学习之路 9 (项目启动后就执行特定方法)

    目录:[持续更新.....] spring 部分常用注解 spring boot 学习之路1(简单入门) spring boot 学习之路2(注解介绍) spring boot 学习之路3( 集成my ...

  10. vue+webpack项目打包后背景图片加载不出来问题解决

    在做VUE +的WebPack脚手架项目打包完成后,在IIS服务器上运行发现项目中的背景图片加载不出来检查项目代码发现是因为CSS文件中,背景图片引用的路径问题;后来通过修改配置文件,问题终于解决了, ...

随机推荐

  1. 基于FPGA的按键提示音设计

    1. 设计要求 按键按下,蜂鸣器要"叮"一声. 2. 设计分析 该设计方案采用野火征途MiNi FPGA开发板(intel-Cyclone IV -EP4CE10F17C8)实现, ...

  2. K8s Pod 资源访问控制策略

    访问控制概述 Kubernetes作为一个分布式集群的管理工具,保证集群的安全性是其一个重要的任务.所谓的安全性其实就是保证对Kubernetes的各种客户端进行认证和鉴权操作. 客户端 在Kuber ...

  3. 【iOS - 月总结】开发中遇到的小知识点(2020.02-2020.03)

    由于懒癌,好久不更了,也是有一段时间没有遇到一些值得记录的问题,今天终于有空闲,就把这两个月遇到的问题总结一下. 1.xcode11中,APPdelegate没有window属性,在运行一些需要使用w ...

  4. 深入理解JVM内存分配机制:大对象处理、年龄判定与空间担保

    ---------------- 先赞后看 效果翻倍 点个关注不迷路 ------------------- 掌握Java对象在堆内存中的生命周期管理艺术 前言 Java虚拟机(JVM)的内存管理机制 ...

  5. Markdown语法入门一:标题,列表,表格与字体

    使用编辑器:obsidian 1. 标题 标题由大到小分为六级,格式为: # + 空格 +标题内容 " # "的数量越少,等级越高 一级标题:#+空格+标题内容 二级标题:##+空 ...

  6. 设计方案:283-基于XILINX K7 XC7K325T的PCIe_CameraLink图像模拟源

    ​ 一.板卡概述        本图像模拟源板卡基于Xilinx公司的FPGAXC7K325T-2FFG900 芯片,pin_to_pin兼容FPGAXC7K410T-2FFG900 .主要的功能是实 ...

  7. Bert基础教程-第一章节

    .markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...

  8. 小trick

    基本算法 求无序数组(不可排序)的前驱和后继(比当前值小) Sol1 考虑离线使用 stack 来进行维护. 顺序遍历的时候,求的是 \(i\) 之前能否到达 \(i\),不能就标记数组 right[ ...

  9. [Alibaba微服务技术入门]_Nacos集群部署说明_第7讲

    Nacos集群部署说明 官方网址:https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html 集群部署适合的环境:生产使用 部署集群环境要求: ...

  10. 基于AOA算术优化的KNN数据聚类算法matlab仿真

    1.程序功能描述 基于AOA算术优化的KNN数据聚类算法matlab仿真.通过AOA优化算法,搜索最优的几个特征数据,进行KNN聚类,同时对比不同个数特征下的KNN聚类精度. 2.测试软件版本以及运行 ...