Solved Combobox with a TreeView: which model?
-
Hello,
I'm trying to accomplish something like these two posts:
- http://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html
- https://stackoverflow.com/questions/27172160/how-to-implement-a-tree-based-qcombobox
Ie. creating a combobox with a treeview (2) and importing and representing data as in (1).
However when the user click on a child, in the treeview, I want the combobox to display all the text from the root until that child. So if for example we have this data:Parent1 Child1 Child2 Child2.1
If the user clicks Child2.1, the combobox's text should display something like this:
Parent1 - Child2 - Child2.1
The problem is: I'm completely lost between Models & Views. Can you provide me with a few shortcuts on how to implement this? How QComboBox gets the text that is clicked on the view?
Thanks
-
Did you already implement the tree? for me that's the hard part.
Did you manage to achieve a combobox that lets you click on Child2.1?
-
void wdgMyTree::on_treeWidget_itemClicked(QTreeWidgetItem *item, int column) { std::vector<QString> txt; std::queue<QTreeWidgetItem*> Q; Q.push(item); while(!Q.empty()){ QTreeWidgetItem *now = Q.front(); Q.pop(); if (now->parent() != NULL) { qDebug() << now->text(column); txt.push_back(now->text(column)); Q.push(now->parent()); } } QString returned_string = ""; for(int i = txt.size() - 1; i >= 0; i--){ returned_string += txt.at(i); } emit(returned_string); }
-
@VRonin yes. Using (2) - on my original post, you can achieve that.
Quick demo:
Using this code to build the tree's data:
QStandardItem *root = new QStandardItem("Item1"); QStandardItem *l1 = new QStandardItem("Leaf1"); QStandardItem *l2 = new QStandardItem("Leaf2"); root->appendRow(l1); l1->appendRow(l2);; root->appendRow(l1); QStandardItemModel *model = new QStandardItemModel(); model->appendRow(root); ui->combo->setModel(model);
@Taz7422 whats the difference of using QTreeWidget instead of QTreeView on QComboBox?
Also, when using it as (2), can't catch the itemClicked() / clicked() signal since by doing setView() the QComboBox takes ownership of the tree widget. -
Maybe this can't definitely be done with QComboBox + QTreeView?
I created a custom TreeModel based on (1) and with the data() method returning a string with all item's and its parents' text (as described above) I managed to get the ComboBox displaying something like:Parent1 - Child2 - Child2.1
But by modifying this method on the Model, the view on the Tree also gets modified (not just on the ComboBox)
I'm now considering just having two separate widgets, a button and a tree widget. The button when pressed opens the tree widget and when an item in the tree is pressed it sets the text on the button.
Any feedback on this is greatly appreciated.
-
Sorry for the delay.
Had a look at the sources of QComboBox and unfortunately the model index dies inside the private part of the class so you have 2 solutions, none of them ideal:
- include the private API, and reimplement
QComboBoxPrivate::emitActivated
to emit a signal including the index- this breaks binary compatibility (and hence LGPL on certain platforms), as well as not giving you any assurance Qt won't change parts of the API at the next update)
- hack it via a direct connection in the view
- this is untested but if it works it should be good enough as long as you have a non-editable QComboBox (i.e. you don't allow typing directly in the combobox) and you don't have multiple columns
for the second solution:
#include <QComboBox> #include <QPersistentModelIndex> #include <QAbstractItemView> #include <QItemSelectionModel> #include <QAbstractItemModel> #include <QStylePainter> class TreeCombo : public QComboBox { Q_OBJECT Q_DISABLE_COPY(TreeCombo) public: TreeCombo(QWidget* parent=Q_NULLPTR) : QComboBox(parent) { connectView(); connectModel(); } void setView(QAbstractItemView *itemView){ QComboBox::setView(itemView); connectView(); } void setModel(QAbstractItemModel *model){ QComboBox::setModel(model); connectModel(); } protected: Q_SLOT void updateModelIndex(const QModelIndex& idx){ if(idx.isValid()) m_index=idx; } Q_SLOT void updateComboString(){ m_comboString.clear(); for(QModelIndex idx = m_index;idx.isValid();idx=idx.parent()){ if(!m_comboString.isEmpty()) m_comboString.prepend(" - "); m_comboString.prepend(idx.data().toString()); } update(); } virtual void paintEvent(QPaintEvent* event) Q_DECL_OVERRIDE { Q_UNUSED(event); QStylePainter painter(this); painter.setPen(palette().color(QPalette::Text)); QStyleOptionComboBox opt; initStyleOption(&opt); opt.currentText = m_comboString; painter.drawComplexControl(QStyle::CC_ComboBox, opt); painter.drawControl(QStyle::CE_ComboBoxLabel, opt); } private: void connectView(){ connect(view()->selectionModel(),&QItemSelectionModel::currentChanged,this,&TreeCombo::updateModelIndex); } void connectModel(){ connect(model(),&QAbstractItemModel::dataChanged,this,&TreeCombo::updateComboString); connect(model(),&QAbstractItemModel::rowsRemoved,this,&TreeCombo::updateComboString); connect(model(),&QAbstractItemModel::rowsInserted,this,&TreeCombo::updateComboString); connect(model(),&QAbstractItemModel::rowsMoved,this,&TreeCombo::updateComboString); } QPersistentModelIndex m_index; QString m_comboString; };
- include the private API, and reimplement
-
@VRonin thanks for the help!
I ended up implementing it with a QPushButton and a QTreeWidget (with Qt::Popup) and by connecting signals&slots between them. This is how it looks:
It serves the purpose!
-
This post is deleted!