На практике, иногда бывает необходимость показывать в QComboBox древовидную структуру данных.
Стандартным компонентом в Qt для такой структуры данных является QTreeView, более того,
QComboBox умеет отображать этот компонент внутри себя, но как всегда, в документации существуют небольшие пробелы, ведь нужно не только отображать дерево, но и устанавливать текущим, выбранный пользователем элемент.
Давайте разберём как правильно это делать
Во первых, создадим сам компонент, который будет отображать данные, для этого наследуемся от QComboBox и наделяем его нужными нам свойствами.
Объявим в закрытой части класса переменную m_view класса QTreeView, которая будет отображать дерево в QComboBox, переопределим 2 функции, которые отвечают за поведение компонента, при раскрытии и закрытии:
Так же добавим функцию hideColumn(int n), которая будет скрывать нужные вам колонки в QTreeView, так как если ваша модель состоит из нескольких колонок, QComboBox покажет их все (стандартный компонент использует список), что будет выглядеть очень некрасиво
treecombobox.h
treecombobox.cpp
В конструкторе, мы устанавливаем у дерева нужный нам вид, чтобы оно выглядело «встроенным» в QComboBox, убираем заголовки, скрываем элементы раскрытия и устанавливаем его как элемент отображения.
Вся хитрость для правильной установки выбранного пользователем элемента в QComboBox, заключается в функциях showPopup() и hidePopup().
Так как QComboBox работает с «плоским» модельным представлением, он не может установить правильный индекс, выбранного пользователем элемента в древовидных моделях, так как они используют индекс относительно родительского элемента, для этого:
корневым элементом — мы устанавливаем корневым индексом недействительный индекс модели, чтобы QComboBox отобразил все элементы модели.
корневым элементом — мы устанавливаем индекс родителя выбранного полльзователем элемента модели, а затем уже относительно родительского элемента, устанавливаем выбранный пользовательский элемент по индексу.
Используется это всё примерно так:

Стандартным компонентом в Qt для такой структуры данных является QTreeView, более того,
QComboBox умеет отображать этот компонент внутри себя, но как всегда, в документации существуют небольшие пробелы, ведь нужно не только отображать дерево, но и устанавливать текущим, выбранный пользователем элемент.
Давайте разберём как правильно это делать
Во первых, создадим сам компонент, который будет отображать данные, для этого наследуемся от QComboBox и наделяем его нужными нам свойствами.
Объявим в закрытой части класса переменную m_view класса QTreeView, которая будет отображать дерево в QComboBox, переопределим 2 функции, которые отвечают за поведение компонента, при раскрытии и закрытии:
- void showPopup() override; — выполняется, когда пользователь раскрывает список
- void hidePopup() override; — выполняется, когда пользователь выбрал элемент, кликнув по нему
Так же добавим функцию hideColumn(int n), которая будет скрывать нужные вам колонки в QTreeView, так как если ваша модель состоит из нескольких колонок, QComboBox покажет их все (стандартный компонент использует список), что будет выглядеть очень некрасиво
treecombobox.h
#ifndef TREECOMBOBOX_H
#define TREECOMBOBOX_H
#include <QtWidgets/QComboBox>
#include <QtWidgets/QTreeView>
class TreeComboBox final : public QComboBox
{
public:
TreeComboBox();
void showPopup() override;
void hidePopup() override;
void hideColumn(int n);
void expandAll();
void selectIndex(const QModelIndex &index);
private:
QTreeView *m_view = nullptr;
};
#endif //TREECOMBOBOX_H
treecombobox.cpp
TreeComboBox::TreeComboBox()
{
m_view = new QTreeView;
m_view->setFrameShape(QFrame::NoFrame);
m_view->setEditTriggers(QTreeView::NoEditTriggers);
m_view->setAlternatingRowColors(true);
m_view->setSelectionBehavior(QTreeView::SelectRows);
m_view->setRootIsDecorated(false);
m_view->setWordWrap(true);
m_view->setAllColumnsShowFocus(true);
m_view->setItemsExpandable(false);
setView(m_view);
m_view->header()->setVisible(false);
}
void TreeComboBox::hideColumn(int n)
{
m_view->hideColumn(n);
}
void TreeComboBox::expandAll()
{
m_view->expandAll();
}
void TreeComboBox::selectIndex(const QModelIndex &index)
{
setRootModelIndex(index.parent());
setCurrentIndex(index.row());
m_view->setCurrentIndex( index );
}
void TreeComboBox::showPopup()
{
setRootModelIndex(QModelIndex());
QComboBox::showPopup();
}
void TreeComboBox::hidePopup()
{
setRootModelIndex(m_view->currentIndex().parent());
setCurrentIndex( m_view->currentIndex().row());
QComboBox::hidePopup();
}
В конструкторе, мы устанавливаем у дерева нужный нам вид, чтобы оно выглядело «встроенным» в QComboBox, убираем заголовки, скрываем элементы раскрытия и устанавливаем его как элемент отображения.
Вся хитрость для правильной установки выбранного пользователем элемента в QComboBox, заключается в функциях showPopup() и hidePopup().
Так как QComboBox работает с «плоским» модельным представлением, он не может установить правильный индекс, выбранного пользователем элемента в древовидных моделях, так как они используют индекс относительно родительского элемента, для этого:
showPopup()
корневым элементом — мы устанавливаем корневым индексом недействительный индекс модели, чтобы QComboBox отобразил все элементы модели.
hidePopup()
корневым элементом — мы устанавливаем индекс родителя выбранного полльзователем элемента модели, а затем уже относительно родительского элемента, устанавливаем выбранный пользовательский элемент по индексу.
Используется это всё примерно так:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
QStandardItemModel model;
QStandardItem *parentItem = model.invisibleRootItem();
for (int i = 0; i < 4; ++i) {
QStandardItem *item = new QStandardItem(QString("item %0").arg(i));
parentItem->appendRow(item);
parentItem = item;
}
TreeComboBox t;
t.setModel(&model);
t.expandAll();
auto lay = new QVBoxLayout;
lay->addWidget( &t);
w.setLayout(lay);
w.show();
return a.exec();
}
