Стандартным компонентом в 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;
};
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();
}
Комментарии (10)
Sazonov
23.08.2018 10:18Спасибо, отлично получилось. Вы меня вдохновили, чтобы тоже что-нибудь про Qt написать из своего опыта :)
sborisov Автор
23.08.2018 10:27А сколько таится нюансов в древовидных моделях…
Обычно я использовал QStandardItemModel, но понадобилась очень сложная и развесистая модель — написал свою. Стандартная Qt-шная — довольно медленная, и навязывает свою архитектуру, что может оказаться очень неудобным в определённых моментах, информации по ним тоже кот наплакал, если табличные модели расписаны в документации «от и до», то древовидные почти не раскрыты.
Но там материала думаю на несколько статей хватитSazonov
23.08.2018 10:34Древовидные достаточно сложно унифицировать. Поэтому про них и мало написано. Тем более, что некоторые варианты использования сложно визуализировать, например, когда есть дети у ненулевого столбца и т.п.
Главное — это понять что Qt-шная модель, это не модель данных в общепринятом значении, а, скорее часть логики представления. Лучше даже сказать — адаптер.sborisov Автор
23.08.2018 10:42Пришлось собирать модельку из «частей» из документации и из книги Бланше, а также дописывать много своих методов
GoldKeeper
23.08.2018 13:02Поэтому про них и мало написано.
Вот в этой книге довольно подробно и много.
Qt. Профессиональное программирование. Разработка кроссплатформенных приложений на С++
www.ozon.ru/context/detail/id/6364884
4Draive
24.08.2018 10:32Возможность хорошая, но чем она лучше чем просто комбобокс с пробелами (и спецсимволом на свой вкус для стрелочки)? И не надо возиться с новым классом и пересчётами индекса.
Заголовок спойлера
Tantrido
Хорошая статья, но… хотелось бы увидеть код
showPopup()
иhidePopup()
, а также скриншот того, что получилось. Не понятно также как будут работать методыint currentIndex() const
и
void setCurrentIndex(int index)
QCombobox
-a, как они будут связаны сQModelIndex
представления и модели?sborisov Автор
Код функций приведён в листинге treecombobox.cpp
Они работают, но главная проблема в том, что QComboBox возвращает индекс строки относительно «корня», он не понимает древовидную модель, поэтому над setCurrentIndex, лучше сделать обёртку, которая будет заниматься установкой нужного индекса.
Получение данных из модели делается стандартными средствами модели, примерно так:
Tantrido
Спасибо!