3

This question may or may not relate to QGraphicsItemGroup - I have never seen this behavior before though....
Briefly: I am deselecting an item, yet the action doesn't take place unless I call the read-only scene().selectedItems() - even if I don't use it.

Details:

I have a custom QGraphicsScene class, that has to perform a lot of operations on the selectedItems().

If items are in a group, they should not be used in any operations - only the group should be used.

So I create my addToGroup() method to perform what I need:

void addToGroup(QList<QGraphicsItem *> children) {
    foreach(QGraphicsItem* child, children)
    {
        child->setSelected(false);
        QGraphicsItemGroup::addToGroup(child);
    }
}

Unfortunately, the items refuse to get deselected !

And then, I added debug messages after each line - and found that adding debug messages changes the outcome !

void addToGroup(QList<QGraphicsItem *> children) {
    foreach(QGraphicsItem* child, children)
    {
        child->setSelected(false);
        scene()->selectedItems();        // this makes it work !
        QGraphicsItemGroup::addToGroup(child);
    }
}

Calling scene()->selectedItems(); - which should be read--only - makes the items actually get deselected !

Please allow me to make sense of this !

Full sample code:

#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QDebug>

class Item {
public:
    Item(int id) {
        m_id = id;
    }
private:
    int m_id;
};

class RectItem: public QGraphicsRectItem, public Item
{
public:
    RectItem(int id) : Item(id) {
        setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsFocusable);
        setRect(QRectF(0,0,100,100));
        setPos(QPointF(10+110*id,10));
        setSelected(true);
    }
};

class GroupItem: public QGraphicsItemGroup, public Item
{
public:

    GroupItem(int id) : Item(id) {
        setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsFocusable | QGraphicsItem::ItemIsSelectable);
    }
    void addToGroup(QList<QGraphicsItem *> children) {
        foreach(QGraphicsItem* child, children)
        {
            child->setSelected(false);
            //scene()->selectedItems().size();
            QGraphicsItemGroup::addToGroup(child);
        }
    }
};

class MyScene: public QGraphicsScene
{
public:
    MyScene() {}
    void group() {
        GroupItem* g = new GroupItem(items().size());
        addItem(g);
        g->addToGroup(selectedItems());
        g->setSelected(true);
    }
};

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MyScene s;
    QGraphicsView view(&s);
    s.setSceneRect(0, 0, 230, 120);
    view.show();
    RectItem* r0 = new RectItem(0);
    s.addItem(r0);
    RectItem* r1 = new RectItem(1);
    s.addItem(r1);
    s.group();
    qDebug() << "Should only have 1 (group) selected out of 3\nTotal items:" << s.items().size() << "; Selected items:" << s.selectedItems().size();
    return app.exec();
}
Thalia
  • 13,637
  • 22
  • 96
  • 190
  • Well [`QGraphicsScene::selectedItems()`](http://code.woboq.org/qt5/qtbase/src/widgets/graphicsview/qgraphicsscene.cpp.html#_ZNK14QGraphicsScene13selectedItemsEv) isn't exactly read only, as you can see from the source code. But what is strange is that in [`QGraphicsItem::setSelected`](http://code.woboq.org/qt5/qtbase/src/widgets/graphicsview/qgraphicsitem.cpp.html#_ZN13QGraphicsItem11setSelectedEb) there is a comment: *QGraphicsScene::selectedItems() lazily pulls out all items that are no longer selected.* – thuga Oct 14 '15 at 06:35
  • @thuga What does "lazily" mean ... (that comment wouldn't explain items *staying selected* even after i set selected to false). Still the function ` QList QGraphicsScene::selectedItems() const` so it should not *modify* anything outside itself... There was another weird behavior. On my ungroup function, not shown in question, I set items as selected - and if set after removing from group, they would show as selected while the `scene().selectedItems()` would show empty ! (or maybe was just lazy) – Thalia Oct 14 '15 at 13:15
  • I don't know, this is bizarre to me. It doesn't call `QGraphicsScene::selectedItems` in `QGraphicsItem::setSelected`. It just mentions that the item will be removed from the selected list in `QGraphicsScene::selectedItems`. Maybe it will be called from somewhere else. This might be worth asking from the Qt devs. – thuga Oct 15 '15 at 09:37

1 Answers1

2

Good observation regarding scene().selectedItems(). There is actually one more inconsistency with QGraphicsItem item selected state when such item is added to QGraphicsItemGroup.

It appears that the internal private set selectedItems accumulates items when a QGraphicsItem is selected. However, if such item is unselected it is not removed from that set.

QGraphicsItem is removed from that set only when the function QGraphicsScene::selectedItems() is called and the member function QGraphicsItem::isSelected() of that item returns false.

In your case at first the items are added to the private scene selected set. Then the selected flag of those items is changed and the group object is added to that scene in selected state. So, there are three objects in the set. The trick is that now isSelected() is true for all items, since isSelected() for items in the group always returns the same result as the group isSelected() independently of items selected state. Thus, all three items appear in the list selectedItems().

If scene().selectedItems() is called after child->setSelected(false) the scene selected set is recreated and the child is removed. That gives final result with only one items in selectedItems().

The function QGraphicsScene::selectedItems() is quite heavy since it is always generates new private set. It is bad to call it inside foreach loop just to remove child items. The same goal can be achieved also by changing order of calls in your group() function:

void group() {
    GroupItem* g = new GroupItem(items().size());
    // at first add items to group
    g->addToGroup(selectedItems());
    // add group to scene
    addItem(g);
    g->setSelected(true);
}

That gives only one item in selectedItems(). It happens because when child items are added to the group that is not in the scene those items are removed from the scene. So, they are also removed from the selected set. Then three unselected items are added to the scene, but g->setSelected(true) adds only group to the selected set.

However, if in the above example you call addItem(g) after setSelected(true) it will give again all three items in selectedItems():

void group() {
    GroupItem* g = new GroupItem(items().size());
    g->addToGroup(selectedItems());
    g->setSelected(true);
    addItem(g);
}

That happens since addItem(g) adds simultaneously three items to the scene. All three items have isSelected() return value true.


Another inconsistency is related to the internal item selected state. It appears that if an item is added to the group the state of that item is frozen in the state that was before adding to that group. It means that if you add selected item to the group it is always displayed as selected (with dashed bounding rectangle) even if the group is not selected. Such view cannot be changed by calling setSelected(false) for that item, since now that function changes only selected state of the group. The item can be displayed as selected even if its isSelected() returns false.


It looks that both those inconsistencies are not intended by the design and it makes sense to report them on the Qt bug tracker.

Orest Hera
  • 6,706
  • 2
  • 21
  • 35
  • Thank you, I was starting to guess all that myself... There is one problem with the group not being added to the scene a the moment of creation - I am making certain calculations based on the group item's `scene()` (which I could use the members `scene()` instead I suppose...). To avoid calling that heavy `selectedItems()` function, I created a list of selected items and changed their selected status before sending them to he group, seems simpler. – Thalia Oct 26 '15 at 15:46
  • @Thalia Yes, your solution is also effective. If you do a copy of selected items list you can try to use `QGraphicsScene::clearSelection()` instead of manual `child->setSelected(false)` for each `child`. It should also clear internal `selectedItems` set. – Orest Hera Oct 26 '15 at 16:05