Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Disable toplevel item of QTreeWidget



  • Hi everyone and happy new year to you all !!

    In my project i have a QTreeWidget filled with QTreeWidgetItems, each element of my tree has a checkbox (not the widget i just use the checkboxes of QTreeWidgetItem). And each toplevel item has one or several children.

    My question is can i disable a toplevel item without disabling it's children ? I want the toplevel item to be grayed out and non-clickable while still being able the see it's children and check/uncheck them.

    So far i can't seem to do that simply, adding a widget inside my QTreeWidgetItem and disabling that instead of the QTreeWidgetItem would work but seem like a hassle just for that. The other way would be to "simulate" the disable by catching keyboards/mouse events and changing the visuals manually but that also seems like a lot of work for not much.

    Thanks in advance for the reply :)


  • Lifetime Qt Champion



  • First, thanks for the reply !
    I've already parsed the doc you linked times and times again and, unless i'm missing something, what you suggested was the first thing i tried, here's the constructor for my test project:

        this->setWindowTitle("QTreeWidget test");
    
        QTreeWidget* treeA = new QTreeWidget(this);
        treeA->setHeaderHidden(true);
    
        QTreeWidgetItem* parentItemA = new QTreeWidgetItem(treeA, QStringList(QString("parent 1")));
        QTreeWidgetItem* childItemA1 = new QTreeWidgetItem(parentItemA, QStringList(QString("child 1")));
        QTreeWidgetItem* childItemA2 = new QTreeWidgetItem(parentItemA, QStringList(QString("child 2")));
        QTreeWidgetItem* childItemA3 = new QTreeWidgetItem(childItemA1, QStringList(QString("child 3")));
        QTreeWidgetItem* childItemA4 = new QTreeWidgetItem(childItemA1, QStringList(QString("child 4")));
    
        QHBoxLayout* layout = new QHBoxLayout(this);
        layout->addWidget(treeA);
    
        childItemA1->setFlags(childItemA1->flags() & ~Qt::ItemIsEnabled);
    
        childItemA3->setFlags(childItemA3->flags() | Qt::ItemIsEnabled);
    

    It's simply a QTreeWidget filled with some QTreeWidgetItems. Here's the output :
    8139d436-c14b-42ec-ac5f-01d7f8a69c4f-image.png

    Child 1 is disabled but child 3 and 4 also are. I even manually re-set child 3's flag to IsEnabled to no avail.

    At this point i'm not sure if what i'm looking for is actually possible. It's not really important since i still managed to get the results i wanted but i would've preferred a cleaner solution.


  • Lifetime Qt Champion

    @Mummoc

    Hmm I must recall wrong and flags actually affect children also - to be "helpful" since in many cases it makes sense
    that if the parent is disabled, children are also.
    Sorry for the goose chase then :)

    Yep it does.

    void QTreeWidgetItem::setFlags(Qt::ItemFlags flags)
    {
        const bool enable = (flags & Qt::ItemIsEnabled);
        const bool changedState = bool(itemFlags & Qt::ItemIsEnabled) != enable;
        const bool changedExplicit = d->disabled != !enable;
        d->disabled = !enable;
        if (enable && par && !(par->itemFlags & Qt::ItemIsEnabled)) // inherit from parent
            itemFlags = flags & ~Qt::ItemIsEnabled;
        else // this item is explicitly disabled or has no parent
            itemFlags = flags;
        if (changedState && changedExplicit) { // if the propagate the change to the children
            QStack<QTreeWidgetItem*> parents;
            parents.push(this);
            while (!parents.isEmpty()) {
                QTreeWidgetItem *parent = parents.pop();
                for (int i = 0; i < parent->children.count(); ++i) {
                    QTreeWidgetItem *child = parent->children.at(i);
                    if (!child->d->disabled) { // if not explicitly disabled
                        parents.push(child);
                        if (enable)
                            child->itemFlags = child->itemFlags | Qt::ItemIsEnabled;
                        else
                            child->itemFlags = child->itemFlags & ~Qt::ItemIsEnabled;
                        child->itemChanged(); // ### we may want to optimize this
                    }
                }
            }
        }
        itemChanged();
    }
    


  • @Mummoc
    Does it behave similarly if you try Qt::ItemIsSelectable in place of Qt::ItemIsEnabled?


  • Lifetime Qt Champion

    @JonB
    well
    childItemA1->setFlags(childItemA1->flags() & ~Qt::ItemIsSelectable);
    wont propagate but it dont look disabled.



  • @mrjj
    True, but it's not selectable, which is at least what the OP asked for:

    I want the toplevel item to be grayed out and non-clickable


  • Lifetime Qt Champion

    QTreeWidget(Item) is a convenience model. If it does not suit for the usecase then you have to implement a model on your own.



  • @Christian-Ehrlicher said in Disable toplevel item of QTreeWidget:

    QTreeWidget(Item) is a convenience model. If it does not suit for the usecase then you have to implement a model on your own.

    Yeah i feel that's what i'll have to do eventually. I recently picked up a project without much prior Qt experience, amongst many refactor i removed the QCheckBoxes inside the QTreeWidgetItems (to facilitate keyboard use with the tree since i didn't want to bother myself with keyboard events back then) and now i'll feel like i'll have to retrace back, oh well.

    @mrjj

    True, but it's not selectable, which is at least what the OP asked for:

    My original issue was only a visual one since i already caught mouse and keyboard events to do what i want.

    All in all thanks for your replies, what i wanted is in fact not possible so i'll have to find workarounds. Marking the post as solved.



  • @Mummoc
    Yes, I believe what we are saying is: disabling a QTreeWidgetItem means disabling its descendants, which seems pretty reasonable. It is therefore not possible to have descendants which are themselves enabled.

    That's why I suggested all you can use would be selectability (Qt::ItemIsSelectable) as that is allowed to vary such that a parent may be unselectable itself while a descendant is still selectable.



  • @JonB

    Yes, I believe what we are saying is: disabling a QTreeWidgetItem means disabling its descendants, which seems pretty reasonable. It is therefore not possible to have descendants which are themselves enabled.

    Yes this seems pretty reasonable and expected as a behavior.

    That's why I suggested all you can use would be selectability (Qt::ItemIsSelectable) as that is allowed to vary such that a parent may be unselectable itself while a descendant is still selectable.

    I was already playing with this flag (and overriding mouse and keyboards events) for various reasons so my original problem was solely a visual one, so to simulate the graying i used QTreeWidgetItem->setForeground() with a gray color.
    So far it does what i need (the checkbox of my QTreeWidgetItem isn't grayed out but this detail is so minor that i'm not bothering with that). So i'm calling it quit now.


Log in to reply