What should I do to use treeView childs to select a subset of data to show in a TableView?
-
@JonB What about this?
connect(treeView->selectionModel()->selectedRows(), &QItemSelectionModel::selectionChanged, dataTable, &MainWindow::treeChild_clicked);
And treeChild_clicked is my Slot function for the QTableView, declared like this:
void MainWindow::treeChild_clicked(const QItemSelection &selected, const QItemSelection &deselected) {}
But I still don't get how to define the selected and deselected QItemSelection variables.
-
@Vil_JL said in What should I do to use treeView childs to select a subset of data to show in a TableView?:
What about this?
treeView->selectionModel()->selectedRows() returns a QList which does not have selectionChanged signal - why do you call selectedRows()?
"But I still don't get how to define the selected and deselected QItemSelection variables" - what do you mean? The signal will pass both to the slot.
-
This is a simple technique that I find effective.
All data goes in a single model. This makes it easier to synchronize across multiple views. Child data that should only appear in the table view can be hidden by a number of techniques. The example below makes these nodes a child of the second column. Filter models and disabling user expansion of rows also work.
QStandardItemModel model; for (int i=0; i < 3; i++) { QList<QStandardItem *> items{ new QStandardItem(QString::number(i)), new QStandardItem } ; QStandardItem *child = new QStandardItem(QString("Child of %1").arg(QString::number(i))); items[1]->appendRow(child); model.appendRow(items); } QTreeView tree; tree.setModel(&model); tree.hideColumn(1); tree.show(); QTableView table; table.setModel(&model); QItemSelectionModel *sm = tree.selectionModel(); QObject::connect(sm, &QItemSelectionModel::currentChanged, &table, [&table](const QModelIndex ¤t) { table.setRootIndex(current.siblingAtColumn(1)); }); table.show();
-
@Vil_JL said in What should I do to use treeView childs to select a subset of data to show in a TableView?:
connect(treeView->selectionModel()->selectedRows(), &QItemSelectionModel::selectionChanged, dataTable, &MainWindow::treeChild_clicked);
Almost right, but
selectedRows()
(aQModelIndexList
) does not have aQItemSelectionModel::selectionChanged
method. That is a member of theQItemSelectionModel
itself, so:connect(treeView->selectionModel(), &QItemSelectionModel::selectionChanged, dataTable, &MainWindow::treeChild_selectionChanged);
The receiving slot function should then look like
void MainWindow::treeChild_selectionChanged(const QItemSelection & selected, const QItemSelection & deselected) { }
so it matches the signal parameters. You do not "define" these, the signal passes them to you,
In that function you could use the passed-in
selected
(anddeselected
if you want that too), or you can just re-queryQTreeView::selectedIndexes()
.That should get you going? I cannot speak for @jeremy_k's suggestion, that's up to you. But note that he uses and connects the same signal, albeit his slot is a lambda rather than another function in
MainWindow
. That may be "advanced usage" for you at this stage. -
@JonB said in What should I do to use treeView childs to select a subset of data to show in a TableView?:
connect(treeView->selectionModel(), &QItemSelectionModel::selectionChanged,
dataTable, &MainWindow::treeChild_selectionChanged);@JonB the connect is giving me an error of C2665: 'QObject::connect': no overloaded function could convert all the argument types. I think it comes from the treeView->selectionModel().
Also for the slot function, I still having doubts about how to use the &selected parameter correctly. I saw that I can get a list of indexes from it, could that help to populate the tableview?
void MainWindow::treeChild_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { QModelIndexList currentIndexes = selected.indexes(); foreach(const QModelIndex &index, currentIndexes) { //... } }
Thank you again for your help.
-
@jsulm said in What should I do to use treeView childs to select a subset of data to show in a TableView?:
why do you call selectedRows()?
@jsulm that was a mistake, I was trying to follow another suggestion.
What do you think of my last post? I'm lost with those QItemSelection parameters, and the connect is showing an error after following JonB suggestion.
Thank you for answering.
-
@Vil_JL
I imagine the error you got was because I wrotedataTable
where I should have writtenthis
(theMainWindow
).I think you should start with defining (or changing your mind) about what the right-hand pane should be/show. Your left-hand pane is a tree view. At present you allow multiple selection there. And your right-hand pane is a table view, allowing for multiple rows. Is that indeed what you want? Or, do you really want a standard/common "allow one item in tree view to be selected at left and show detail on that single item at right"? (Which could be done neatly with a
QDataWidgetMapper
especially if you want to allow editing but maybe you do not.) If it's not, and you do want multiple selection and multiple rows at right in a table view that's fine but want to be sure. Let's get that clear before proceeding. -
@JonB What I want to do is simply what I shown, to select one element in an tree to show me in a table the values it has stored in that tree element (or use the tree element as a key to look for the data), and to be able to edit those values (I think I already sorted that out with a delegate).
I choose a model/view framework because my understanding is that is the best way to achieve it. I thought it would be simpler to do, I didn't expect that would be so complicated to develop. 😅
@JonB said in What should I do to use treeView childs to select a subset of data to show in a TableView?:
I imagine the error you got was because I wrote dataTable where I should have written this (the MainWindow).
Yes, thank you, that solved that error. Now I have to figure out the function for the slot.
Thank you again for answering.
-
@Vil_JL said in What should I do to use treeView childs to select a subset of data to show in a TableView?:
to select one element in an tree to show me in a table the values it has stored in that tree element (or use the tree element as a key to look for the data), and to be able to edit those values (I think I already sorted that out with a delegate).
Since it's "one element" you might have been better to use the
QDataWidgetMapper
which would have produced a bunch of label-editwidget pairs in lines in the right-hand pane, instead of a single row in a generic tableview with columns for each value. But that's up to you and we will stick with the table view.You have chosen to create to keep and create the models/maps for the tree part (
namesMap
) and the table part (dataMap
) separate from each other. That means we cannot somehow use a selection in the tree view/model to directly access an element in the table view/model. We could have done that by index (from the selection) had they been in the same model, but we can't with separate models.I don't have time to write the whole thing for you. What you will need to code is:
-
You only want to select a single row in the tree. I think
QTreeView
or its selection model allows you to specify "only allow single selection". You might want to add that. If you don't then your code should check for multiple rows selected and perhaps decide to have the table view show detail on only the first one selected. -
You only want to be able to select a leaf in the tree, not a node. Either find a setting for that if there is one, or make your code ignore a selection if it is a node not a leaf, or make the table view show "nothing" if what is selected is not a leaf.
-
At this point you have a single leaf selected, in
namesMap
. You want to display information about that fromdataMap
. -
Because you cannot use indexes into
namesMap
to directly access elements indataMap
, you need to access (from the current selection) the right element innamesMap
to retrieve the string of the "Child" item (e.g.Child 1-2
) you will need to look up indataMap
. -
At this point you have e.g.
Child 1-2
string. You want to display that in the table view. Your problem is that yourtableModel
has all the rows fromdataMap
. But you're only interested in one. You have about 3 choices:
-
Don't pre-populate
tableModel
with all the rows. Create a new table model or clear out an existing one. When a leaf is selected in the tree populate the table model with just the matching single row fromdataMap
. -
If you do have all the rows already in the table model/view, would you be prepared to do something like just highlight the selected one and/or "dim" the unselected ones?
-
To do it properly (and this is what I would recommend/experienced users would do) where the table model has all the rows but you only want one to be shown you need to filter the model to pick out just the one you want to show in the table view. Qt's QSortFilterProxyModel Class allows for that. You have to interpose that between the underlying source model (
tableModel
) and the table view, like:
QStandardItemModel tableModel; QSortFilterProxyModel tableFilterModel; QTableView tableView; tableFilterModel.setSourceModel(&tableModel); tableView.setModel(&tableFilterModel);
Then, armed with the string
Child 1-2
from the selected leaf in the tree model you can usetableFilterModel.setFilterFixedString("Child 1-2")
, and that will make the table view show only that row. -
-
Some of the confusion may be due to the use of the terminology such as "select", which for QItemSelectionModel is 0 or more items. "current", which means 0 or 1 item, sounds like a better match for the intent.
Reposting my suggestion, this time with all of the boiler plate:
#include <QApplication> #include <QStandardItemModel> #include <QTreeView> #include <QTableView> #include <QString> #include <QItemSelectionModel> #include <QSplitter> int main(int argc, char *argv[]) { QApplication a(argc, argv); QStandardItemModel model; // Populate the model according to the data. QModelIndex firstIndex; for (int i=0; i < 3; i++) { QStandardItem *parent{ new QStandardItem(QString("Parent %1").arg(i))}; QList<QStandardItem *> intermediate{ new QStandardItem(QString("Child %1").arg(i)), new QStandardItem}; QStandardItem *child = new QStandardItem(QString("Data of child %1").arg(i)); // Only children of column 0 are displayed by QTreeView. Children of other rows are hidden. intermediate[1]->appendRow(child); parent->appendRow(intermediate); model.appendRow(parent); if (!firstIndex.isValid()) firstIndex = model.indexFromItem(intermediate[1]); } QTreeView tree; tree.setModel(&model); tree.expandAll(); QTableView table; table.setModel(&model); // Setting the root index of a view hides all items except those rooted at that index. table.setRootIndex(firstIndex); QItemSelectionModel *sm = tree.selectionModel(); QObject::connect(sm, &QItemSelectionModel::currentChanged, &table, [&table](const QModelIndex ¤t) { // Reveal children rooted at the second column of the current (usually clicked) row, if the column is valid QModelIndex rootIndex = current.siblingAtColumn(1); if (rootIndex.isValid()) table.setRootIndex(rootIndex); } ); QSplitter splitter; splitter.addWidget(&tree); splitter.addWidget(&table); splitter.show(); return a.exec(); }
The program above produces this display:
Clicking on "Child 0" displays "Data of child 0". Clicking on a parent node does not update the table display. This behavior can be fine tuned. Note that the items are editable, and edits are saved to the model.
-
Thank you very much, @JonB. It took me a while but I finally achieved what I was trying to do, and your suggestions helped me a lot.
@JonB said in What should I do to use treeView childs to select a subset of data to show in a TableView?:
You only want to select a single row in the tree. I think QTreeView or its selection model allows you to specify "only allow single selection". You might want to add that. If you don't then your code should check for multiple rows selected and perhaps decide to have the table view show detail on only the first one selected.
You only want to be able to select a leaf in the tree, not a node. Either find a setting for that if there is one, or make your code ignore a selection if it is a node not a leaf, or make the table view show "nothing" if what is selected is not a leaf.
Here I coded my slot function to get the selected item on the tree:
// To populate the Tableview with data through the treeView: QItemSelectionModel *selectionModel = treeView->selectionModel(); // selection changes shall trigger a slot connect(selectionModel, &QItemSelectionModel::selectionChanged, this, &MainWindow::treeChild_selection); } // end of MainWindow void MainWindow::treeChild_selection(const QItemSelection &/*selected*/, const QItemSelection &/*deselected*/) { // to get the text of the selected item const QModelIndex index = treeView->selectionModel()->currentIndex(); QString selectedLeaf = index.data(Qt::DisplayRole).toString();
@JonB said in What should I do to use treeView childs to select a subset of data to show in a TableView?:
Don't pre-populate tableModel with all the rows. Create a new table model or clear out an existing one. When a leaf is selected in the tree populate the table model with just the matching single row from dataMap.
I changed the table model to be empty of data so the function will be the one to fill it:
// Setting the table model tableModel = new QStandardItemModel(3,3,this); tableModel->setHeaderData(0, Qt::Horizontal, tr("1")); tableModel->setHeaderData(1, Qt::Horizontal, tr("2")); tableModel->setHeaderData(2, Qt::Horizontal, tr("3"));
void MainWindow::treeChild_selection(const QItemSelection &/*selected*/, const QItemSelection &/*deselected*/) { // to get the text of the selected item const QModelIndex index = treeView->selectionModel()->currentIndex(); QString selectedLeaf = index.data(Qt::DisplayRole).toString(); // iterating through the dataMap to find the rows corresponding to the selected item QMap<QString,QList<QList<QString>>>::iterator itr; for (itr = dataMap->begin(); itr != dataMap->end(); ++itr) { if (itr.key() == selectedLeaf) { // look up code to fill the tableView with the item data from the dataMap } // if a parent item is selected then the table is cleared of any data if (selectedLeaf == "Parent #1" || selectedLeaf == "Parent #2" || selectedLeaf == "Parent #3") { for (int row = 0; row < 3; ++row) { for (int col = 0; col < 3; ++col) { QModelIndex index2 = tableModel->index(row, col, QModelIndex()); tableModel->clearItemData(index2); } } } } }
So that's it, maybe it looks rough but it works.
(I made some changes in the way I declared and defined the Qmaps initially too, but I don't want to spam this answer with a lot of code. If anyone is curious just ask)
-
Thank you for your code, @jeremy_k. I learned some things about models with it. At first I tried to follow your code but at the time to fill the table with my data, the result table I was getting looked weird and I was getting some index errors, so in the end I went with JonB final answer.
-
-