Qt中如何使用wrapper class实现QGraphics组合符号整体选中
问题描述
基于Qt Graphics View框架开发的绘图程序中,QGraphicsScene承载大量QGraphicsItem图元:程序从文件读取直线、圆弧、圆形的坐标数据存入map结构,绘制阶段遍历map将坐标传入Qt对应API完成渲染。
当前存在组合符号无法整体选中的异常:单个符号由4条直线、1段圆弧、1个圆形组合构成时,点击其中任意一条直线仅会选中该直线本身,点击圆弧也仅选中对应圆弧,不符合「点击符号任意部分即可选中整个符号」的预期。
现有建议是通过
wrapper class(包装类)实现需求,但不清楚具体实现逻辑。
现有代码结构
mySymbol 类定义
class mySymbol { struct Line { std::tuple<int, int, int, int> line; }; struct Arc { std::tuple<int, int, int, int, int, int> arc; }; struct Circle { std::tuple<int, int, int> circle; }; struct Rect { std::vector<Line> allLines; std::vector<Arc> allArcs; std::vector<Circle> allCircles; } std::map<std::string, Rect> allShapes; }
DrawShapes.h
class DrawShapes { void createSymbol(map<std::string, Rect>); }
DrawShapes.cpp
void DrawShapes :: createSymbol(map<std::string, Rect> allShapes) { QGraphicsItemGroup * parentGroup = new QGraphicsItemGroup; for (auto iter = allShapes.begin(); iter != allShapes.end(); ++iter) { if (iter->second.allLines.size() > 0) { for (unsigned int i = 0; i < iter->second.allLines.size(); i++) { // 提取直线的4个坐标值first、second、third、fourth myLine* line = new myLine(first,second,third,fourth); line->DrawLine(); parentGroup->addToGroup(line); scene->addItem(static_cast<QGraphicsLineItem*>(line)); } } // 圆弧、圆形的处理逻辑与直线一致 }
myLine.h
class myLine: public QGraphicsLineItem { public: myLine(); myLine(int x1,int y1,int x2 ,int y2,QGraphicsItem *parent = nullptr) : QGraphicsLineItem(x1,y1,x2,y2,parent) {} void DrawLine(); };
myLine.cpp
void myLine::DrawLine() { this->setPen(QPen(QColor("blue"), 1)); this->setFlag(QGraphicsItem::ItemIsSelectable); }
根因分析
现有代码存在3个核心逻辑错误,导致无法实现整体选中:
- 所有子图元(直线、圆弧、圆形)都单独设置了
ItemIsSelectable标志,子图元自身可捕获选中事件,不会将事件传递给上层容器 - 每个子图元被单独调用
scene->addItem()加入场景,虽然调用了addToGroup,但图元归属逻辑错误,QGraphicsItemGroup没有真正成为子图元的父容器 - 仅创建了一个全局的
parentGroup,所有符号的所有图元都被加入同一个组,最终会导致所有内容成为一个不可分割的整体,不符合单个符号独立选中的需求
修复方案
基础版:基于QGraphicsItemGroup实现
1. 调整子图元属性
子图元不需要单独持有选中能力,移除子图元的可选中标志,将选中逻辑统一交给上层符号容器处理。修改myLine::DrawLine:
void myLine::DrawLine() { this->setPen(QPen(QColor("blue"), 1)); // 移除原有的setFlag(QGraphicsItem::ItemIsSelectable)逻辑 this->setFlag(QGraphicsItem::ItemIsSelectable, false); }
圆弧、圆形类做相同修改。
2. 修正组创建与图元挂载逻辑
每个独立符号对应一个独立的QGraphicsItemGroup,禁止所有图元共用同一个全局组;子图元创建时直接将组作为父对象传入,不需要单独将子图元加入场景,组被加入场景时会自动将所有子图元挂载到场景下。
修改后的createSymbol核心逻辑:
void DrawShapes :: createSymbol(map<std::string, Rect> allShapes) { // 移除全局parentGroup定义 for (auto iter = allShapes.begin(); iter != allShapes.end(); ++iter) { // 为每个独立符号创建专属组 QGraphicsItemGroup * symbolGroup = new QGraphicsItemGroup; // 仅给组设置可选中属性,组是选中事件的响应主体 symbolGroup->setFlag(QGraphicsItem::ItemIsSelectable, true); if (iter->second.allLines.size() > 0) { for (unsigned int i = 0; i < iter->second.allLines.size(); i++) { // 提取直线4个坐标值 // 创建直线时直接将symbolGroup作为父对象传入,自动完成组挂载 myLine* line = new myLine(first,second,third,fourth, symbolGroup); line->DrawLine(); // 移除单独调用addToGroup、scene->addItem添加子图元的逻辑 } } // 圆弧、圆形做相同处理,创建时传入symbolGroup作为父对象 // 最后仅需将整个符号组加入场景即可 scene->addItem(symbolGroup); } }
进阶版:自定义包装类实现
如果需要给符号绑定业务属性(比如符号ID、自定义样式、业务逻辑),可以继承QGraphicsItemGroup实现自定义包装类,替代原生的QGraphicsItemGroup:
// 自定义符号包装类 class MySymbolItem : public QGraphicsItemGroup { public: MySymbolItem(const std::string& symbolId, QGraphicsItem* parent = nullptr) : QGraphicsItemGroup(parent), m_symbolId(symbolId) { // 组默认持有可选中属性 this->setFlag(QGraphicsItem::ItemIsSelectable, true); } // 可扩展自定义业务方法,比如获取符号ID、批量修改图元样式等 std::string getSymbolId() const { return m_symbolId; } private: std::string m_symbolId; // 存储符号业务标识 };
创建符号时实例化自定义的MySymbolItem作为容器即可,后续获取选中项时可直接转换为MySymbolItem*类型,读取绑定的业务数据,扩展性比原生组更强。
内容的提问来源于stack exchange,提问作者tushar
相关产品推荐
相关产品推荐

