Create something like the Widget Box as in the Qt Designer

October 13, 2013

Widget Box

The Widget Box as<br>seen in Qt Designer
The Widget Box as
seen in Qt Designer

There is something called the Widget Box in the Qt Designer. It contains a list of widgets separated by categories. Each category button can be clicked in order to expand and collapse the list below the button.

You might want something like the Widget Box for one reason or another, to display your own collapsible list of items.

Tree Widget

In my approach in recreating the Widget Box I used a Tree Widget because it already has the basic idea of expanding and collapsing items in the list to display and hide its child widgets. The gist of turning the Tree Widget into a Widget Box is adding buttons as top level items and adding a frame with a layout as the child of those top level items. The button should then expand and collapse its own Tree Widget Item when clicked to display and hide the contents of a category. The button will require a custom class inheriting the QPushButton class in order to expand and collapse the Tree Widget Items.

You might want to disable root decoration and set indentation to 0 on the Tree Widget to turn it into a flat list. The custom button type is named QtCategoryButton in this snippet.

MyApplication::MyApplication(QWidget *parent, Qt::WFlags flags)
    : QMainWindow(parent, flags)
{
    ui.setupUi(this);

    ui.treeWidget->setRootIsDecorated(false);
    ui.treeWidget->setIndentation(0);

    // First category
    {
        QTreeWidgetItem* pCategory = new QTreeWidgetItem();
        ui.treeWidget->addTopLevelItem(pCategory);
        ui.treeWidget->setItemWidget(pCategory, 0,
            new QtCategoryButton("Category 1", ui.treeWidget, pCategory));

        QFrame* pFrame = new QFrame(ui.treeWidget);
        QBoxLayout* pLayout = new QVBoxLayout(pFrame);
        pLayout->addWidget(new QPushButton("Btn1"));
        pLayout->addWidget(new QPushButton("Btn2"));

        QTreeWidgetItem* pContainer = new QTreeWidgetItem();
        pContainer->setDisabled(true);
        pCategory->addChild(pContainer);
        ui.treeWidget->setItemWidget(pContainer, 0, pFrame);
    }

    // Second category
    {
        QTreeWidgetItem* pCategory = new QTreeWidgetItem();
        ui.treeWidget->addTopLevelItem(pCategory);
        ui.treeWidget->setItemWidget(pCategory, 0,
            new QtCategoryButton("Category 2", ui.treeWidget, pCategory));

        QFrame* pFrame = new QFrame(ui.treeWidget);
        QBoxLayout* pLayout = new QHBoxLayout(pFrame);
        pLayout->addWidget(new QPushButton("Btn1"));
        pLayout->addWidget(new QPushButton("Btn2"));

        QTreeWidgetItem* pContainer = new QTreeWidgetItem();
        pContainer->setDisabled(true);
        pCategory->addChild(pContainer);
        ui.treeWidget->setItemWidget(pContainer, 0, pFrame);
    }
}

Custom button

The custom button is a quite simple class, all it does it catch the pressed() signal and expands or collapses the Tree Widget Item it is bound to.

// QtCategoryButton.h
class QtCategoryButton : public QPushButton
{
    Q_OBJECT
public:
    QtCategoryButton(const QString& a_Text, QTreeWidget* a_pParent,
        QTreeWidgetItem* a_pItem);

private slots:
    void ButtonPressed();

private:
    QTreeWidgetItem* m_pItem;
};

 

// QtCategoryButton.cpp
QtCategoryButton::QtCategoryButton( const QString& a_Text,
        QTreeWidget* a_pParent, QTreeWidgetItem* a_pItem )
    : QPushButton(a_Text, a_pParent)
    , m_pItem(a_pItem)
{
    connect(this, SIGNAL(pressed()), this, SLOT(ButtonPressed()));
}

void QtCategoryButton::ButtonPressed()
{
    m_pItem->setExpanded( !m_pItem->isExpanded() );
}

Finally

The Custom Widget Box
The Custom Widget Box

If everything went well you should get something like this. You can of course use any other widgets, not just buttons.

Tags

= [, , ]
Comments
anon - March 2, 2014

Thanks. Will adapt for pyqt.

Thanks. It's very helpful to me.

Really good example! Thanks for sharing!

thanks a lot you saved my day

Wow! Thnx buddy!

Aleksey - March 7, 2016

Good example, thanks. However how to make it look like in Qt designer with same root decoration (triangle) in the left? Or replace root button with a line for example like this:

+ ------- Category 1 -------------
widgets
widgets
+ ------- Category 2 -------------
widgets
widgets

etc. thanks!

Aleksey - March 14, 2016

Created sample widget and plugin for Qt Designer, put it on GitHub if somebody else need this and want to improve: https://github.com/akontsevich/WidgetBox. It is ready for using however some points good to have improved:

  • Style category button (or panel) closer to Qt Creator (Designer) look or like to LibreOffice Writer Properties Tool Box on the right side

  • Improve pages behaviour in Qt Designer similar to QToolBox: now Designer does not change currentPageIndex PROPERTY and focus on page or category click for some reason

  • Improve pages geometry resizing to content and their layout on design time and on run-time: QTreeWidget automatically increases itemWidget size height (in runtime, not design time - it remains terrible there) if contents is bigger than widget (page) height, however if size (sizeHint) is bigger - it does not decrease size to necessary minimum.

Hope for community help on these improvements.

Stacey Lanno - May 3, 2016

This is jut a test on page

Faris Priadi - December 7, 2016

That's awesome ! It helps me a lot. Thanks.

CodingCat - April 20, 2018

Thanks for the great concept! I have reimplemented it for PyQt5:

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class SectionExpandButton(QPushButton):
    """a QPushbutton that can expand or collapse its section
    """
    def __init__(self, item, text = "", parent = None):
        super().__init__(text, parent)
        self.section = item
        self.clicked.connect(self.on_clicked)
        
    def on_clicked(self):
        """toggle expand/collapse of section by clicking
        """
        if self.section.isExpanded():
            self.section.setExpanded(False)
        else:
            self.section.setExpanded(True)
            

class CollapsibleDialog(QDialog):
    """a dialog to which collapsible sections can be added;
    reimplement define_sections() to define sections and
        add them as (title, widget) tuples to self.sections
    """
    def __init__(self):
        super().__init__()
        self.tree = QTreeWidget()
        self.tree.setHeaderHidden(True)
        layout = QVBoxLayout()
        layout.addWidget(self.tree)
        self.setLayout(layout)
        self.tree.setIndentation(0)
        
        self.sections = []
        self.define_sections()
        self.add_sections()

    def add_sections(self):
        """adds a collapsible sections for every 
        (title, widget) tuple in self.sections
        """
        for (title, widget) in self.sections:
            button1 = self.add_button(title)
            section1 = self.add_widget(button1, widget)
            button1.addChild(section1)

    def define_sections(self):
        """reimplement this to define all your sections
        and add them as (title, widget) tuples to self.sections
        """
        widget = QFrame(self.tree)
        layout = QHBoxLayout(widget)
        layout.addWidget(QLabel("Bla"))
        layout.addWidget(QLabel("Blubb"))
        title = "Section 1"
        self.sections.append((title, widget))

    def add_button(self, title):
        """creates a QTreeWidgetItem containing a button 
        to expand or collapse its section
        """
        item = QTreeWidgetItem()
        self.tree.addTopLevelItem(item)
        self.tree.setItemWidget(item, 0, SectionExpandButton(item, text = title))
        return item

    def add_widget(self, button, widget):
        """creates a QWidgetItem containing the widget,
        as child of the button-QWidgetItem
        """
        section = QTreeWidgetItem(button)
        section.setDisabled(True)
        self.tree.setItemWidget(section, 0, widget)
        return section
        

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = CollapsibleDialog()
    window.show()
    sys.exit(app.exec_())

Ignacio Wolcott - May 5, 2019

Great post. I am going through some of these issues as well..

CodingCat - January 9, 2020 > Replying to CodingCat

Oh, that messed up the indentation. Sorry. I have put the code on StackOverflow to copy and use:
https://stackoverflow.com/a/49941230/1413513

Leave a comment

// Your email address will not be published.