Qt自定义QGraphicsItemGroup子类qgraphicsitem_cast转换失败问题
我尝试使用line、arc、circle图元绘制AND、NAND等数字门电路符号,开发中使用QGraphicsItemGroup对构成单个符号的子图元做分组管理:单个符号包含3条直线、2段圆弧、1个圆形时,我会将所有子图元添加到QGraphicsItemGroup实例中,再将分组对象添加到scene场景中。
自定义分组类头文件定义如下:
// myGroupItem.h class myGroupItem: public QGraphicsItemGroup { virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); }
myGroupItem.cpp实现代码如下:
void myGroupItem ::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); { auto copied_option = *option; copied_option.state &= ~QStyle::State_Selected; auto selected = option->state & QStyle::State_Selected; QGraphicsItemGroup::paint(painter, &copied_option, widget); if (selected) { foreach (QGraphicsItem* item, _scene->selectedItems()) { myGroupItem* inst = qgraphicsitem_cast<myGroupItem*>(item); if (!inst) continue; QList<QGraphicsItem*> childItems = inst->childItems(); foreach (QGraphicsItem* chItem, childItems) { if (myPoly* poly = dynamic_cast<myPoly*>(chItem)) { painter->save(); painter->setPen(QPen(option->palette.windowText(), 0, Qt::SolidLine)); painter->drawPath(poly->shape()); painter->restore(); } } else { if (myLine* line = dynamic_cast<myLine*>(chItem)) { painter->save(); painter->setPen(QPen(option->palette.windowText(), 0, Qt::SolidLine)); painter->drawPath(line->shape()); painter->restore(); } else { if (myEllipse* ell = dynamic_cast<myEllipse*>(chItem)) { painter->save(); painter->setPen(QPen(option->palette.windowText(), 0, Qt::SolidLine)); painter->drawPath(ell->shape()); painter->restore(); } } } } } } }
其余自定义图元头文件定义:
// myLine.h class myLine : public QGraphicsLineItem { } // myEllipse.h class myEllipse : public QGraphicsEllipseItem { }
门电路符号构建逻辑:
class BuildDesign() { myGroupItem* groupItem = new myGroupItem(); // 初始化所有直线图元 myLine* line = new myLine(co-ordinate of lines); groupItem->addToGroup(line); // 添加圆弧图元到分组 groupItem->addToGroup(arc); // 添加圆形图元到分组 groupItem->addToGroup(ellipse); scene->addItem(groupItem); }
按照上述逻辑可正常生成门电路符号,但选中符号执行后续处理时出现异常,选中相关代码如下:
// 检测鼠标点击位置是否存在图元,存在则调用高亮函数,不存在则刷新场景 void BuildDesign::SelectObject(QEvent* event) { QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event); QPointF mousePoint = _view->mapToScene(mouseEvent->pos()); QGraphicsItem* itemUnderClick = _scene->itemAt(mousePoint, QTransform()); if (!itemUnderClick) RefreshScene(); else HighlightSelectedObject(itemUnderClick); } void BuildDesign::HighlightSelectedObject(QGraphicsItem* itemUnderClick) { myGroupItem* gItem = qgraphicsitem_cast<myGroupItem*>(itemUnderClick); if (gItem) { gItem->setSelected(true); // 该段代码始终无法执行 } }
调试时发现鼠标点击获取到的itemUnderClick有有效内存地址,但用qgraphicsitem_cast转myGroupItem*时始终返回空地址0x0,导致选中逻辑无法触发,后续遍历场景选中项列表也拿不到目标分组对象,阻塞后续开发。
两个核心问题导致转换失败:
QGraphicsItemGroup默认不直接响应鼠标命中,scene->itemAt()点击组区域时,返回的是最上层的组内子图元(myLine/myEllipse/圆弧对象),根本不是myGroupItem实例,自然转换失败。- 自定义
QGraphicsItem子类没有实现type()接口、没有定义自定义类型ID,qgraphicsitem_cast无法识别自定义类型,即使拿到组对象也可能转换失败。
1. 兼容子图元点击选中分组
点击命中子图元时,向上遍历父对象链找到所属的myGroupItem实例,替换HighlightSelectedObject逻辑:
void BuildDesign::HighlightSelectedObject(QGraphicsItem* itemUnderClick) { QGraphicsItem* current = itemUnderClick; myGroupItem* targetGroup = nullptr; // 向上查找所属的自定义分组 while(current) { targetGroup = qgraphicsitem_cast<myGroupItem*>(current); if(targetGroup) break; current = current->parentItem(); } if (targetGroup) { // 选中前可根据需求决定是否清空其他选中状态 _scene->clearSelection(); targetGroup->setSelected(true); } }
同时在myGroupItem构造函数中开启选中支持:
myGroupItem::myGroupItem() { setFlag(QGraphicsItem::ItemIsSelectable, true); }
2. 为自定义图元类添加类型支持
每个自定义QGraphicsItem子类都需要在类定义中声明类型枚举、重写type()方法,才能保证qgraphicsitem_cast正常工作,以myGroupItem为例:
// myGroupItem.h class myGroupItem: public QGraphicsItemGroup { public: // 自定义类型ID,取值需大于QGraphicsItem::UserType enum { Type = UserType + 1 }; int type() const override { return Type; } virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); }
myLine、myEllipse、myPoly等自定义子类按相同规则添加即可,Type值依次递增不重复。
3. 修复paint函数语法错误
当前贴出的paint函数中遍历子项的大括号匹配错误,else分支和myPoly判断的层级不对应,会直接编译失败,调整大括号对应关系即可。
内容的提问来源于stack exchange,提问作者tushar

