Pull to refresh

Хитрости QComboBox + QTreeView

Reading time3 min
Views8.9K
На практике, иногда бывает необходимость показывать в 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();
}

Tags:
Hubs:
+11
Comments12

Articles