代码之家  ›  专栏  ›  技术社区  ›  GPhilo satyendra

如果选择了该项,为什么QGraphicsItem的子项不再获得鼠标单击?

  •  1
  • GPhilo satyendra  · 技术社区  · 6 年前

    我正在使用 SizeGripItem here (添加一个信号的最小修改)在 QGraphicsRectItem 定义如下:

    标题

    //! RectItem that sends a boxChanged signal whenever it is moved or resized via the handles.
    class QSignalingBoxItem : public QObject, public QGraphicsRectItem
    {
        Q_OBJECT
    
    private:
        void setSelected_(bool selected);
    
    protected:
        QVariant itemChange(GraphicsItemChange change, const QVariant & value);
    
    public:
        QSignalingBoxItem(QRectF rect, QGraphicsItem * parent = nullptr);
        ~QSignalingBoxItem();
    
    signals:
        void boxChanged(QRectF newBox);
    };
    

    .cpp公司

    namespace
    {
        class BoxResizer : public SizeGripItem::Resizer
        {
        public:
            virtual void operator()(QGraphicsItem* item, const QRectF& rect)
            {
                QSignalingBoxItem* rectItem =
                    dynamic_cast<QSignalingBoxItem*>(item);
    
                if (rectItem)
                {
                    rectItem->setRect(rect);
                }
            }
        };
    }
    
    QSignalingBoxItem::QSignalingBoxItem(QRectF rect, QGraphicsItem * parent) : QGraphicsRectItem(parent)
    {
        setFlags(QGraphicsItem::ItemIsMovable | 
                 QGraphicsItem::ItemIsSelectable | 
                 QGraphicsItem::ItemSendsScenePositionChanges);
    }
    
    QVariant QSignalingBoxItem::itemChange(GraphicsItemChange change, const QVariant & value)
    {
        switch (change)
        {
        case QGraphicsItem::ItemSelectedHasChanged:
            setSelected_(value.toBool());
            break;
        case QGraphicsItem::ItemScenePositionHasChanged:
            emit boxChanged(rect());
            break;
        default:
            break;
        }
        return value;
    }
    
    void QSignalingBoxItem::setSelected_(bool selected)
    {
        //TODO: Test that it works as expected
        if (selected)
        {
            auto sz = new SizeGripItem(new BoxResizer, this);
            connect(sz, &SizeGripItem::resized, [&]() {emit boxChanged(rect()); });
        }
        else
        {
            // Get the child
            // If it's a SizeGripItem, delete it.
            if (childItems().length() > 0)
            {
                foreach(QGraphicsItem* item, childItems())
                {
                    auto sz = dynamic_cast<SizeGripItem*>(item);
                    if (sz)
                    {
                        delete sz;
                        break;
                    }
                }
            }
        }
    }
    

    如果我申请 SizeGritem公司 在构造函数中,类按预期工作。但是,我需要句柄是可见的,并且只有在选中该项时才能工作。不过,当我运行上面的代码时,控制柄会显示出来,但是任何对控制柄的单击都会移动整个框(就像我在框的中间单击了一样),而不是调整它的大小。

    用调试器运行,我看到了 itemChange 连电话都没有。 为什么是这个案子?当项目被选中时,我需要如何修改类才能使其工作?

    编辑

    这就是这个类的用法。我有一个 QGraphicsView 与它的私人 m_scene 当我超越 mouse{Press,Move,Release}Event 通过“单击并拖动”生成框。 把班上的其他同学都放在一边,因为班上只有一部分接触到 QSignalingBoxItem s是这三个函数,我要加上它们。如果需要更多,我会根据需要补充。

    void QSampleEditor::mousePressEvent(QMouseEvent * mouseEvent)
    {
        QImageView::mousePressEvent(mouseEvent);
        if (mouseEvent->buttons() == Qt::RightButton && m_currentSample && m_activePixmap && m_activePixmap->isUnderMouse())
        {
            m_dragStart = mapToScene(mouseEvent->pos());
            m_currentBox = new QSignalingBoxItem({ m_dragStart, QSize(0,0) });
            m_scene.addItem(m_currentBox);
            mouseEvent->accept();
        }
    }
    
    void QSampleEditor::mouseMoveEvent(QMouseEvent * mouseEvent)
    {
        QImageView::mouseMoveEvent(mouseEvent);
        if (m_currentBox)
        {
            m_currentBox->setRect(rectFromTwoPoints(m_dragStart, mapToScene(mouseEvent->pos())));
            mouseEvent->accept();
        }
    }
    
    void QSampleEditor::mouseReleaseEvent(QMouseEvent * mouseEvent)
    {
        //TODO: Add checks that the sample is still there.
    
        QImageView::mouseReleaseEvent(mouseEvent);
        if (m_currentBox)
        {
            // Add to the StringSample
            auto new_char = new CharacterSample;
            connect(m_currentBox, &QSignalingBoxItem::boxChanged, new_char, &CharacterSample::boxChanged);
            new_char->boxChanged(m_currentBox->rect());
            m_currentSample->addCharacter(new_char);
            m_currentBox = nullptr;
            mouseEvent->accept();
        }
    }
    
    1 回复  |  直到 6 年前
        1
  •  1
  •   Kuba hasn't forgotten Monica    6 年前

    当一个给定的项选择了它的任何可移动的祖先项时,最年长的被选择的祖先将接收该事件,即使该项的位置与子项重叠。这是在 QGraphicsItem::mouseMoveEvent -未处理移动事件 如果存在可移动祖先 . 最长的可移动选定祖先接收事件并使用它移动自身,但子项忽略它(这里是句柄!).

    关于代码(您的和您正在重用的代码)的一般说明:

    1. 类型名以 Q 为Qt保留。除非将Qt放在命名空间中,否则不应该使用此类名称。

    2. 这个 SizeGripItem 应该标记为没有内容-因为 paint 方法是禁止操作的。

    3. 通过常量引用传递非数值、非指针方法参数 除非 该方法需要一个副本才能在内部修改。

    4. 这个 SizeGritem公司 需要一个 Resizer 或者发出一个信号,而不是两者-这两个选项是互斥的。

      事实上 调整器 是一个Qt 4的痕迹,其中的插槽是冗长的,不可能连接到lambda。在Qt 5中,信号可以连接到任何函子,包括 调整器 类型-因此使 QObject::connect 隐式向后兼容显式使用 调整器 (!).

    可以提出以下解决方案:

    1. 平凡解- 这抑制了问题的主要原因 :使托管项不可移动。手柄就会起作用。它与选择状态无关。是可移动性引起了这个问题。
    2. 使 SizeGritem公司 可移动的。这个 SignalingBoxItem 不能移动。
    3. 重新实施 SizeGripItem::HandleItem mouseMoveEvent 接受相关事件并作出反应。这个 信号箱项目 仍然可以移动。
    4. 拥有 SizeGritem公司 成为its的事件筛选器 HandleItem 并按解决方案2处理相关事件。这个 信号箱项目 仍然可以移动。
    5. 使 SizeGritem公司 成为 信号箱项目 . 这个 信号箱项目 然后就可以移动,不用迫不及待的使用 SizeGritem公司 的手柄。当然没有祖先 信号箱项目 那就可以移动了。

    下面是大约240行的完整示例,实现了解决方案1-3。每个解决方案都在一个条件块中进行了描述,并且示例在启用其中任何一个子集的情况下进行编译。如果未选择任何解决方案,则原始问题仍然存在。启用的解决方案可以在运行时选择。

    首先,让我们从 SizeGripItem :

    // https://github.com/KubaO/stackoverflown/tree/master/questions/graphicsscene-children-51596611
    #include <QtWidgets>
    #include <array>
    #define SOLUTION(s) ((!!(s)) << (s))
    #define HAS_SOLUTION(s) (!!(SOLUTIONS & SOLUTION(s)))
    #define SOLUTIONS (SOLUTION(1) | SOLUTION(2) | SOLUTION(3))
    
    class SizeGripItem : public QGraphicsObject {
       Q_OBJECT
       enum { kMoveInHandle, kInitialPos, kPressPos };
       struct HandleItem : QGraphicsRectItem {
          HandleItem() : QGraphicsRectItem(-4, -4, 8, 8) {
             setBrush(Qt::lightGray);
             setFlags(ItemIsMovable | ItemSendsGeometryChanges);
          }
          SizeGripItem *parent() const { return static_cast<SizeGripItem *>(parentItem()); }
          QVariant itemChange(GraphicsItemChange change, const QVariant &value) override {
             if (change == ItemPositionHasChanged) parent()->handleMoved(this);
             return value;
          }
    #if HAS_SOLUTION(2)
          bool sceneEvent(QEvent *event) override {
             return (data(kMoveInHandle).toBool() && hasSelectedMovableAncestor(this) &&
                     processMove(this, event)) ||
                    QGraphicsRectItem::sceneEvent(event);
          }
    #endif
       };
    #if HAS_SOLUTION(2) || HAS_SOLUTION(3)
       static bool processMove(QGraphicsItem *item, QEvent *ev) {
          auto mev = static_cast<QGraphicsSceneMouseEvent *>(ev);
          if (ev->type() == QEvent::GraphicsSceneMousePress &&
              mev->button() == Qt::LeftButton) {
             item->setData(kInitialPos, item->pos());
             item->setData(kPressPos, item->mapToParent(mev->pos()));
             return true;
          } else if (ev->type() == QEvent::GraphicsSceneMouseMove &&
                     mev->buttons() == Qt::LeftButton) {
             auto delta = item->mapToParent(mev->pos()) - item->data(kPressPos).toPointF();
             item->setPos(item->data(kInitialPos).toPointF() + delta);
             return true;
          }
          return false;
       }
       static bool hasSelectedMovableAncestor(const QGraphicsItem *item) {
          auto *p = item->parentItem();
          return p && ((p->isSelected() && (p->flags() & QGraphicsItem::ItemIsMovable)) ||
                       hasSelectedMovableAncestor(p));
       }
    #endif
       std::array<HandleItem, 4> handles_;
       QRectF rect_;
       void updateHandleItemPositions() {
          static auto get = {&QRectF::topLeft, &QRectF::topRight, &QRectF::bottomLeft,
                             &QRectF::bottomRight};
          for (auto &h : handles_) h.setPos((rect_.*get.begin()[index(&h)])());
       }
       int index(HandleItem *handle) const { return handle - &handles_[0]; }
       void handleMoved(HandleItem *handle) {
          static auto set = {&QRectF::setTopLeft, &QRectF::setTopRight,
                             &QRectF::setBottomLeft, &QRectF::setBottomRight};
          auto rect = rect_;
          (rect.*set.begin()[index(handle)])(handle->pos());
          setRect(mapRectToParent(rect.normalized()));
       }
    
      public:
       SizeGripItem(QGraphicsItem *parent = {}) : QGraphicsObject(parent) {
          for (auto &h : handles_) h.setParentItem(this);
          setFlags(ItemHasNoContents);
       }
       QVariant itemChange(GraphicsItemChange change, const QVariant &value) override {
          if (change == QGraphicsItem::ItemPositionHasChanged) resize();
          return value;
       }
       void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override {}
       QRectF boundingRect() const override { return rect_; }
       void setRect(const QRectF &rect) {
          rect_ = mapRectFromParent(rect);
          resize();
          updateHandleItemPositions();
       }
       void resize() { emit rectChanged(mapRectToParent(rect_), parentItem()); }
       Q_SIGNAL void rectChanged(const QRectF &, QGraphicsItem *);
    #if SOLUTIONS
       void selectSolution(int i) {
    #if HAS_SOLUTION(1)
          setFlag(ItemIsMovable, i == 1);
          setFlag(ItemSendsGeometryChanges, i == 1);
          if (i != 1) {
             auto rect = mapRectToParent(rect_);
             setPos({});  // reset position if we're leaving the movable mode
             setRect(rect);
          }
          i--;
    #endif
          for (auto &h : handles_) {
             int ii = i;
    #if HAS_SOLUTION(2)
             h.setData(kMoveInHandle, ii-- == 1);
    #endif
    #if HAS_SOLUTION(3)
             if (ii == 1)
                h.installSceneEventFilter(this);
             else
                h.removeSceneEventFilter(this);
    #endif
          }
       }
    #endif
    #if HAS_SOLUTION(3)
       bool sceneEventFilter(QGraphicsItem *item, QEvent *ev) override {
          if (hasSelectedMovableAncestor(item)) return processMove(item, ev);
          return false;
       }
    #endif
    };
    

    那么 信号箱项目 :

    class SignalingBoxItem : public QObject, public QGraphicsRectItem {
       Q_OBJECT
       SizeGripItem m_sizeGrip{this};
       QVariant itemChange(GraphicsItemChange change, const QVariant &value) override {
          if (change == QGraphicsItem::ItemSelectedHasChanged)
             m_sizeGrip.setVisible(value.toBool());
          else if (change == QGraphicsItem::ItemScenePositionHasChanged)
             emitRectChanged();
          return value;
       }
       void emitRectChanged() { emit rectChanged(mapRectToScene(rect())); }
       void setRectImpl(const QRectF &rect) {
          QGraphicsRectItem::setRect(rect);
          emitRectChanged();
       }
    
      public:
       SignalingBoxItem(const QRectF &rect = {}, QGraphicsItem *parent = {})
           : QGraphicsRectItem(rect, parent) {
          setFlags(ItemIsMovable | ItemIsSelectable | ItemSendsScenePositionChanges);
          m_sizeGrip.hide();
          connect(&m_sizeGrip, &SizeGripItem::rectChanged, this,
                  &SignalingBoxItem::setRectImpl);
       }
       void setRect(const QRectF &rect) {
          setSelected(false);
          m_sizeGrip.setRect(rect);
          setRectImpl(rect);
       }
       Q_SIGNAL void rectChanged(const QRectF &);  // Rectangle in scene coordinates
    #if SOLUTIONS
       void selectSolution(int index) {
          setFlag(ItemIsMovable, !HAS_SOLUTION(1) || index != 1);
          m_sizeGrip.selectSolution(index);
       }
    #endif
    };
    

    这个 SampleEditor :

    class SampleEditor : public QGraphicsView {
       Q_OBJECT
       bool m_activeDrag = false;
       SignalingBoxItem m_box;
       QPointF m_dragStart;
    
      public:
       SampleEditor(QGraphicsScene *scene) : QGraphicsView(scene) {
          scene->addItem(&m_box);
          connect(&m_box, &SignalingBoxItem::rectChanged, this, &SampleEditor::rectChanged);
       }
       Q_SIGNAL void rectChanged(const QRectF &);
       void mousePressEvent(QMouseEvent *event) override {
          QGraphicsView::mousePressEvent(event);
          if (event->button() == Qt::RightButton) {
             m_dragStart = m_box.mapFromScene(mapToScene(event->pos()));
             m_activeDrag = true;
             m_box.show();
             m_box.setRect({m_dragStart, m_dragStart});
             event->accept();
          }
       }
       void mouseMoveEvent(QMouseEvent *event) override {
          QGraphicsView::mouseMoveEvent(event);
          if (m_activeDrag) {
             m_box.setRect({m_dragStart, m_box.mapFromScene(mapToScene(event->pos()))});
             event->accept();
          }
       }
       void mouseReleaseEvent(QMouseEvent *event) override {
          QGraphicsView::mouseReleaseEvent(event);
          if (m_activeDrag && event->button() == Qt::RightButton) {
             event->accept();
             m_activeDrag = false;
          }
       }
       void resizeEvent(QResizeEvent *event) override {
          QGraphicsView::resizeEvent(event);
          scene()->setSceneRect(contentsRect());
       }
    #if SOLUTIONS
       void selectSolution(int index) { m_box.selectSolution(index); }
    #endif
    };
    

    最后,演示代码:

    int main(int argc, char *argv[]) {
       QApplication a(argc, argv);
       QWidget ui;
       QGridLayout layout{&ui};
       QGraphicsScene scene;
       SampleEditor editor(&scene);
       QComboBox sel;
       QLabel status;
       layout.addWidget(&editor, 0, 0, 1, 2);
       layout.addWidget(&sel, 1, 0);
       layout.addWidget(&status, 1, 1);
       sel.addItems({
          "Original (Movable SignalingBoxItem)",
    #if HAS_SOLUTION(1)
              "Movable SizeGripItem",
    #endif
    #if HAS_SOLUTION(2)
              "Reimplemented HandleItem",
    #endif
    #if HAS_SOLUTION(3)
              "Filtering SizeGripItem",
    #endif
       });
       sel.setCurrentIndex(-1);
    #if SOLUTIONS
       QObject::connect(&sel, QOverload<int>::of(&QComboBox::currentIndexChanged),
                        [&](int index) { editor.selectSolution(index); });
    #endif
       QObject::connect(&editor, &SampleEditor::rectChanged, &status,
                        [&](const QRectF &rect) {
                           QString s;
                           QDebug(&s) << rect;
                           status.setText(s);
                        });
       sel.setCurrentIndex((sel.count() > 1) ? 1 : 0);
       ui.setMinimumSize(640, 480);
       ui.show();
       return a.exec();
    }
    #include "main.moc"