Presenting a tree model with a hierarchical set of comboboxes
-
Task is to provide a view for a simple read-only tree model with the help of *QComboboxes *to let the user choose a specific sub item. As questions about this topic are poping up very often, but rarely being answered, here an simple example. It is open for discussion on how to improve it.
It was a quite tedious task after having started with QDataWidgetMapper, trying to use QStyledItemDelegate and other possible implementations. If you know it, it is basically rather simple and even well documented in the section "Parents of items" in the help file qthelp://org.qt-project.qtwidgets.540/qtwidgets/model-view-programming.html. The trick is to use hierarchicalIndex = treeModel->index(lower, 0, hierarchicalIndex) for each level of hierachy.
An alternative solution is described at "Implementing_QTreeView_in_QComboBox_using_Qt":http://developer.nokia.com/community/wiki/Implementing_QTreeView_in_QComboBox_using_Qt-_Part_2
unfortunately having several drawbacks as for example not working on Mac and not resizing well.Here is the complete code for main.cpp:
@
#include <QObject>
#include <QApplication>
#include <QMainWindow>
#include <QVBoxLayout>
#include <QComboBox>
#include <QStandardItemModel>
#include <QTreeView>// macro to overcome overloading issue - I like it cryptic ;-)
#define SIGTYPE(t, c, f) static_cast<void (c:: *)(t)>(&c::f)// some model parameters
enum {
Trees = 5,
Branches = 4,
Leaves = 3
};// our widget main window
class MainWindow : public QWidget {
Q_OBJECTpublic: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private slots: void on_comboBox_1_currentIndexChanged(int arg1); // select the tree void on_comboBox_2_currentIndexChanged(int arg1); // select the branch void on_comboBox_3_currentIndexChanged(int); // select the leaf private: QStandardItem *addItemAt(QStandardItem *parentItem, QString itemString); QModelIndex hierarchicalIndex(int highest, ...); private: QVBoxLayout *verticalLayout; QComboBox *comboBox_1; QComboBox *comboBox_2; QComboBox *comboBox_3; QTreeView *treeView; QStandardItemModel *treeModel; int tree, branch;
};
MainWindow::MainWindow(QWidget *parent): QWidget(parent) {
tree = 0;
branch = 0;
// compose a simple layout using 3 QComboBoxes and a QTreeView vertically aligned
verticalLayout= new QVBoxLayout(this);comboBox_1 = new QComboBox(this); comboBox_2 = new QComboBox(this); comboBox_3 = new QComboBox(this); treeView = new QTreeView(this); verticalLayout->addWidget(comboBox_1); verticalLayout->addWidget(comboBox_2); verticalLayout->addWidget(comboBox_3); verticalLayout->addWidget(treeView); setLayout(verticalLayout); treeModel = new QStandardItemModel(this); QList<QStandardItem*> itemlist; itemlist.append(treeModel->invisibleRootItem()); itemlist.append(NULL); for (int tree = 0; tree < Trees; tree ++ ) { itemlist[1] = addItemAt(itemlist.at(0), QString("Tree %1").arg(tree)); itemlist.append(NULL); for (int branch = 0; branch < Branches; branch++) { itemlist[2] = addItemAt(itemlist.at(1), QString("Branch %2 of tree %1").arg(tree).arg(branch) ); itemlist.append(NULL); for (int leaf = 0; leaf < Leaves; leaf ++) { itemlist[3] = addItemAt(itemlist.at(2), QString("Leaf %3 of tree %1 (%2)") .arg(tree).arg(branch).arg(leaf) ); } } } treeView->setModel(treeModel); connect(comboBox_1, SIGTYPE(int, QComboBox, currentIndexChanged), this, &MainWindow::on_comboBox_1_currentIndexChanged ); connect(comboBox_2, SIGTYPE(int, QComboBox, currentIndexChanged), this, &MainWindow::on_comboBox_2_currentIndexChanged ); connect(comboBox_3, SIGTYPE(int, QComboBox, currentIndexChanged), this, &MainWindow::on_comboBox_3_currentIndexChanged ); // reverse order to avoid runtime errors comboBox_3->setModel(treeModel); comboBox_2->setModel(treeModel); comboBox_1->setModel(treeModel);
}
MainWindow::~MainWindow() {
}QStandardItem *MainWindow::addItemAt(QStandardItem *parentItem, QString itemString) {
QStandardItem *item = new QStandardItem(itemString);
parentItem->appendRow(item);
return item;
}// scans indices of a tree model (highestLevel, nextLevel, ...-1)
// a negative int signals end of the variadic list
QModelIndex MainWindow::hierarchicalIndex(int highest, ...) {
va_list arguments;
va_start(arguments, highest);
QModelIndex hierarchicalIndex = treeModel->index(highest, 0);
for (;;) {
int lower = va_arg(arguments, int);
if (lower < 0) break;
hierarchicalIndex = treeModel->index(lower, 0, hierarchicalIndex);
}
va_end(arguments);
return hierarchicalIndex;
}void MainWindow::on_comboBox_1_currentIndexChanged(int arg1) {
tree = arg1;
comboBox_2->setRootModelIndex(hierarchicalIndex(tree, -1));
comboBox_2->setCurrentIndex(0); // strangely enough we need to update manually here
}void MainWindow::on_comboBox_2_currentIndexChanged(int arg1) {
branch = arg1;
comboBox_3->setRootModelIndex(hierarchicalIndex(tree, branch, -1));
comboBox_3->setCurrentIndex(0);
}void MainWindow::on_comboBox_3_currentIndexChanged(int /arg1/) {
// here very likely we do our job
}int main(int argc, char *argv[]) {
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}#include "main.moc"
@and the corresponding qmake file HierarchicalComboBox.pro:
@
lessThan(QT_MAJOR_VERSION, 5): message("Requires Qt5")QT += core gui widgets
TARGET = HierarchicalComboBox
TEMPLATE = appSOURCES += main.cpp
@