Хитрости QComboBox + QTreeView

от автора

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


ссылка на оригинал статьи https://habr.com/post/420977/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *