嵌套 QGraphicsScenes 中的选择排除

Selection exclusion in nested QGraphicsScenes

我有一个包含项目的 QGraphicsScene(我们称之为母场景),其中一些还包含 QGraphicsScene(我们称之为子场景)。

观察:女儿戏和妈妈戏的select离子互不打扰。这意味着,如果我 select 母亲场景中的一个项目,我可以 select 女儿场景中的项目,而母亲场景中的项目将保持 selected.

我的预期结果: 我希望我在一个场景中的 selection 清除任何其他场景中的 selection。

[为清楚起见编辑] 当我 select 场景中的一个项目时,我想取消 select 其他场景中的 selected 项目,而不是有几个专注于不同的场景。

我的动机:我想这样做是因为当我使用键盘快捷键时,我不知道Qt会选择哪个场景。当我只有一个女儿时,我选择了母亲(我更喜欢女儿)。

我目前的解决方案: 在子场景容器项中,mousePressEvent 清除了母场景的selection。我发现这个解决方案非常丑陋,我想知道是否有人确实知道一个更好的解决方案,它会使用一些内部 Qt 功能。现在看来是一个糟糕的DIY解决方案,会带来很多问题。

提前致谢!

[编辑:最小示例] 在这个例子中,我们可以同时 select 两个嵌套元素。我宁愿在我的整个场景中一次只有一个 selection。

#include <QApplication>
#include <QGraphicsView>

#include <QGraphicsScene>
#include <QMainWindow>
#include <QGraphicsItem>
#include <QGraphicsLinearLayout>
#include <QGraphicsWidget>
#include <QGraphicsProxyWidget>

// Item that gets red contour when selected
class SimpleItem : public QGraphicsItem
{
public :
    SimpleItem():QGraphicsItem()
    {
        setFlag(QGraphicsItem::ItemIsSelectable, true);
    }

    QRectF boundingRect() const override { return QRectF(-20, -20, 40, 40);}

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
    {
        painter->setPen(Qt::black);
        if(isSelected())
            painter->setPen(Qt::red);
        painter->setBrush(Qt::gray);
        painter->drawRect(boundingRect());
    }
};

// Item that contains a QGraphicsScene in a layout
// This item gets also a red contour when selected
class SceneItem : public QGraphicsWidget
{
public :
    SceneItem():QGraphicsWidget()
    {
        setFlag(QGraphicsItem::ItemIsSelectable, true);
        setFocusPolicy(Qt::ClickFocus);

        // Create the inner scene
        QGraphicsLinearLayout * layout = new QGraphicsLinearLayout;
        setLayout(layout);
        QGraphicsScene * scene = new QGraphicsScene;
        QGraphicsView * view = new QGraphicsView(scene);
        QGraphicsProxyWidget * proxy = new QGraphicsProxyWidget;
        layout->addItem(proxy);
        proxy->setWidget(view);

        // Add a simple item
        SimpleItem * simpleItem = new SimpleItem;
        scene->addItem(simpleItem);
    }

    QRectF boundingRect() const override { return QRectF(0, 0, 100, 100);}

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
    {
        painter->setPen(Qt::black);
        if(isSelected())
            painter->setPen(Qt::red);
        painter->setBrush(Qt::lightGray);
        painter->drawRect(boundingRect());
    }
};

// Main
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QMainWindow w;

    QGraphicsScene * scene = new QGraphicsScene;
    QGraphicsView * view = new QGraphicsView(scene);
    view->setDragMode(QGraphicsView::RubberBandDrag);
    w.setCentralWidget(view);

    SceneItem * item = new SceneItem;
    scene->addItem(item);

    w.show();

    return a.exec();
}

免责声明

我个人不建议在 QGraphicsScene 中嵌入小部件:

The key to the high performance of Graphics View is reducing how much is painted each frame. QGraphicsWidget and QGraphicsProxyWidget together are huge performance killers because they can not be rendered in a efficient way.

此引文的出处以及有关该主题的更多信息可在此博客中找到 post:

Should you still be using QGraphicsView?

解决方案

如果我必须不惜一切代价使用 OP 使用的确切方法,我的解决方案是:

  1. 使用 QGraphicsScene::selectionChanged 对选择更改做出反应并
  2. 使用QGraphicsScene::selectedItems检查是否选择了一个项目。

例子

这是 OP 提供的 MVCE,由我修改以演示如何实施建议的解决方案:

#include <QApplication>
#include <QMainWindow>
#include <QGraphicsLinearLayout>
#include <QGraphicsProxyWidget>
#include <QGraphicsView>
#include <QPainter>

class SimpleItem : public QGraphicsItem
{
public :
    explicit SimpleItem(QGraphicsItem *parent = nullptr) :
        QGraphicsItem(parent) { setFlag(ItemIsSelectable, true); }

    QRectF boundingRect() const override { return QRectF(-20, -20, 40, 40); }
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *,
               QWidget *) override {
        painter->setPen(isSelected() ? Qt::red : Qt::black);
        painter->setBrush(Qt::gray);
        painter->drawRect(boundingRect());
    }
};

class SceneItem : public QGraphicsWidget
{
    QGraphicsScene *m_scene;
public:
    explicit SceneItem(QGraphicsItem *parent = nullptr) :
        QGraphicsWidget(parent),
        m_scene(new QGraphicsScene(this)) {
        auto *layout = new QGraphicsLinearLayout;
        auto *view = new QGraphicsView(m_scene);
        auto *proxy = new QGraphicsProxyWidget(this);
        auto *simpleItem = new SimpleItem(this);

        m_scene->addItem(simpleItem);
        proxy->setWidget(view);
        layout->addItem(proxy);

        setLayout(layout);
        setFocusPolicy(Qt::ClickFocus);
        setFlag(QGraphicsItem::ItemIsSelectable, true);
    }

    QGraphicsScene *scene() const { return m_scene; }
    QRectF boundingRect() const override { return QRectF(0, 0, 100, 100);}
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *,
               QWidget *) override {
        painter->setPen(isSelected() ? Qt::red : Qt::black);
        painter->setBrush(Qt::lightGray);
        painter->drawRect(boundingRect());
    }
    int type() const override { return UserType; }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QMainWindow w;
    auto *scene = new QGraphicsScene;
    auto *view = new QGraphicsView(scene);
    auto *item = new SceneItem;

    scene->addItem(item);
    view->setDragMode(QGraphicsView::RubberBandDrag);

    QObject::connect(item->scene(), &QGraphicsScene::selectionChanged, [item, scene](){
        if (!item->scene()->selectedItems().isEmpty())
            scene->clearSelection();
    });

    QObject::connect(scene, &QGraphicsScene::selectionChanged, [scene](){
        if (scene->selectedItems().isEmpty())
            return;

        for (auto *item : scene->items())
            if (item->type() == QGraphicsItem::UserType)
                static_cast<SceneItem *>(item)->scene()->clearSelection();
    });

    w.setCentralWidget(view);
    w.show();

    return a.exec();
}