Хитрости QComboBox + QTreeView

в 14:56, , рубрики: qt, Qt 5

На практике, иногда бывает необходимость, показывать в QComboBox древовидную структуру данных.

Стандартным компонентом в Qt для такой структуры данных является QTreeView, более того,
QComboBox умеет отображать этот компонент внутри себя, но как всегда, в документации существуют небольшие пробелы, ведь нужно не только отображать дерево, но и устанавливать текущим, выбранный пользователем элемент.

Давайте разберём как правильно это делать

Во первых создадим сам компонент, который будет отображать данные, для этого наследуемся от QComboBox наделяем его нужными нам свойствами.

Объявим в закрытой части класса переменную m_view, класса QTreeView, которая будет отображать дерево в QComboBox, переопределим 2 функции, которые отвечают за поведение компонента, при раскрытии и закрытии:

  • void showPopup() override; — выполняется, когда пользователь раскрывает список
  • void hidePopup() override; — выполняется, когда пользователь выбрал элемент кликнув по нему

Так же добавим функцию hideColumn(int n), которая будет скрывать, нужные вам, колонки в QTreeView, так как если ваша модель состоит из нескольких колонок, combobox — покажет их все (стандартный компонент использует список), что будет выглядеть очень некрасиво

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();
}

Автор: sborisov

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js