How do I prevent a user from expanding a QTreeWidgetItem?

49 Views Asked by At

I have a QTreeWidgetItem that I want to disable under certain circumstances to prevent users from using incompatible options. I'm working with Qt 5.15.2.

QTreeWidgetItem has a function, QTreeWidgetItem::setDisabled(bool disabled), but sadly this only seems to grey it out - it does not prevent a user from clicking the arrow and expanding or un-expanding the tree item.

There is of course QTreeWidgetItem::setExpanded(bool expand), but that only sets it once - it doesn't prevent the user from expanding.

Is there some way to completely disable the tree item, so a user cannot open it? I tried QTreeWidgetItem::setFlags(Qt::ItemFlags flags) with flags set to 0, so every option should be disabled, but this did not make any difference.

The only remaining way is to disable each of the children, but the only trouble is that those children have other cases where they need to be disabled and enabled, so simply re-enabling them all would be wrong - some of those children would still need to be off. I suppose you could store the previous state of all the children, but that's cumbersome and prone to error.

2

There are 2 best solutions below

0
musicamante On

One possibility is to set the Qt::ItemNeverHasChildren flag for the parent item, along with the current ones.

Note that that flag is intended for optimization purposes only, but it should be fine to use it within a QTreeWidget.

Just ensure that the item is not expanded, set the flag along with the existing flags, and ensure that you update the viewport.

I can't really write in C++, so I wouldn't risk posting bad code, but this is how I would do it in PyQt/PySide, which should be quite self explanatory:

def setItemExpandable(item, expand):
    if expand:
        item.setFlags(item.flags() & ~Qt.ItemNeverHasChildren)
    else:
        if item.isExpanded():
            item.setExpanded(False)
        item.setFlags(item.flags() | Qt.ItemNeverHasChildren)

    if item.treeWidget():
        item.treeWidget().viewport().update()

You could even implement it in a custom QTreeWidgetItem subclass, and always use that as a prototype for the tree widget.

Be aware that, if the item is already part of the view, setExpanded(False) will obviously trigger the collapsed and itemCollapsed signals, possibly causing some level of recursion in a custom implementation that uses those signals.

0
A.R.M On

You can handle mouse press events, as a straightforward method. Here are two ways to do that:

Using an event filter

#include <QApplication>
#include <QTreeWidget>
#include <QTreeWidgetItem>

class TreeWidgetItemDisabler : public QObject
{
protected:
    bool eventFilter(QObject *watched, QEvent *event) override
    {
        if(event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick)
        {
            QTreeWidgetItem *item = static_cast<QTreeWidget*>(watched->parent())->itemAt(static_cast<QMouseEvent*>(event)->pos());

            //this is just an example condition
            //you can customize this based on what items you don't want expanded
            //usint their position, data, object name, etc.
            if (item && item->isDisabled())
            {
                //return true to filter out the event
                return true;
            }
        }

        return QObject::eventFilter(watched, event);
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QTreeWidget treeWidget;
    TreeWidgetItemDisabler treeWidgetDisabler;
    //IMPORTANT: the viewport receives mouse events, not the tree widget
    treeWidget.viewport()->installEventFilter(&treeWidgetDisabler);

    QTreeWidgetItem parentItem(&treeWidget, QStringList("Parent Item"));
    QTreeWidgetItem childItem(&parentItem, QStringList("Child Item"));

    QTreeWidgetItem parentItem1(&treeWidget, QStringList("Parent Item1"));
    QTreeWidgetItem childItem1(&parentItem1, QStringList("Child Item1"));

    QTreeWidgetItem parentItem2(&treeWidget, QStringList("Parent Item2"));
    QTreeWidgetItem childItem2(&parentItem2, QStringList("Child Item 2"));

    //as an example, I disabled the item I don't want expanded
    //you may tweak that based on your conditions
    parentItem.setDisabled(true);

    treeWidget.show();

    return a.exec();
}

Using mousePressEvent

#include <QApplication>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QMouseEvent>

class TreeWidget : public QTreeWidget
{
public:
    TreeWidget(QWidget *parent = nullptr) : QTreeWidget(parent) {}

protected:
    void mousePressEvent(QMouseEvent *event) override
    {
        QTreeWidgetItem *item = itemAt(event->pos());

        if (item && item->isDisabled())
        {
            //accept the event to to indicate that you want to handle the event yourself
            //then do nothing and just return
            event->accept();
            return;
        }

        QTreeWidget::mousePressEvent(event);
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    TreeWidget treeWidget;

    QTreeWidgetItem parentItem(&treeWidget, QStringList("Parent Item"));
    QTreeWidgetItem childItem(&parentItem, QStringList("Child Item"));

    QTreeWidgetItem parentItem1(&treeWidget, QStringList("Parent Item1"));
    QTreeWidgetItem childItem1(&parentItem1, QStringList("Child Item1"));

    QTreeWidgetItem parentItem2(&treeWidget, QStringList("Parent Item2"));
    QTreeWidgetItem childItem2(&parentItem2, QStringList("Child Item 2"));

    //as an example, I disabled the item I don't want expanded
    //you may tweak that based on your conditions
    parentItem.setDisabled(true);

    treeWidget.show();

    return a.exec();
}

Unexpandable QTreeWidgetItem demonstration GIF

You may also use a flag, isExpandable for example, to enable/disable expanding an item.