QSortFilterProxyModel crashing app - c++

I have a model subclassed from QAbstractListModel which has a QList to maintain the data which contains QDateTime which is used to maintain this list. I have to maintain this data for an hour i.e., older data will be removed from the list. This basically is FIFO list. I have a proxy model (subclass of QSortFilterProxyModel) to sort the data. Whenever the data changes, proxy model is loosing the index and displaying data unfiltered. Following is the code snippet to do this.
emit layoutAboutToBeChanged();
beginInsertRows(QModelIndex(), 0, 1); //we are prepending
m_entries.prepend(e);
endInsertRows();
emit layoutChanged();
This seems to have solved the problem. But, if something is selected on the view (QTreeView), then the application is crashing after sometime with lot of these error messages.
QSortFilterProxyModel: index from wrong model passed to mapFromSource
QSortFilterProxyModel: index from wrong model passed to mapFromSource
QSortFilterProxyModel: index from wrong model passed to mapFromSource
Stack trace on the debugger shows the mouseSelectEvent and other functions which needs QModelIndex.
Sorry for the long question. Could someone please help in solving this problem?
Thanks.

The documentation of beginInsertRows says void QAbstractItemModel::beginInsertRows(const QModelIndex & parent, int first, int last) which means that when you insert only one item parameters first = last = 0. In your snippet you insert one item with m_entries.prepend(e) but you delcare that you are going to insert two: beginInsertRows(QModelIndex(), 0, 1); The view receives signal that two rows have been inserted and when it asks for the second one - boom! access violation. What you need is beginInsertRows(QModelIndex(), 0, 0);. Also I don't think you need to emit layoutAboutToBeChanged() an emit layoutChanged(); but I am not sure about that.

Related

The easiest way to make a "tracking" item selection in TreeView?

Okay, so I have a QTreeView and a Model subclassed from QAbstractItemModel set to it. So far so good.
The data of the model gets updated every constant time interval, for example, two seconds. Let's say for simplicity the items are just short integers, and have no hierarchical structure. All items are unique.
t=0:
497
1739
18
125
19
The user selects the item 18:
t=1:
497
1739
[18]
125
19
t=2, the list of items updates and changes!
497
18
[1739]
125
For example, 1739 and 18 are now swapped, and the last 19 disappears.
Whoops! 1739 is now selected, which seems legit: the user has selected the third item, and it's still being selected even after an update.
I hope the question is now obvious: I need the selection to "track" what item exactly is selected. Using some internal id of the item itself. So if the new list contains an item with the same id as the previously selected one, it gets selected.
I could change the selection myself on update, or use insertRow and removeRow, but I don't know what exactly (1739 and 18 got swapped) has changed in the list, I just receive a new list of integers.
I've looked at QItemSelectionModel, and although the Qt documentation says
The QItemSelectionModel class keeps track of a view's selected items.
, it doesn't look like what I need. I thought it is another proxy model I need to subclass, but it's not.
Next I thought my Model is the right place to write the code that will track the selection, but I've failed again: a model is unable to retrieve the selection model of the view (view->selectionModel()), because it cannot access the view(s).
I could end up writing some signals and slots in my Model so that the code outside the Model could update the selection, plus a slot outside the model connected to the view's selectionModel updating the id of the currently selected item stored in the Model, etc, etc, but before writing such things I thought of asking if there already is some practice I don't know about.
There is so few documentation and articles explaining all these stuff, and I was unable to find an answer to the above myself.
Thank you!
It would be best to detect when items are added/moved/deleted from model and emit respective signals, but this would be to complex and probably you don't need such complex functionality.
Assuming that you are interested in single selection you can do that quite simply. When current selection changes simply store selected value.
When you will be notified that model has changed just find new position of items and update selection.
void onCurrentSelectionChange(const QModelIndex & index) { // slot
selectedValue = index->data(); // update field: QVariant selectedValue
}
void onModelReset() { // slot connected to signal where model reports that it was changed
QModelIndex newSelection = yourModel->findIndexOfValue(selectedValue); // you have to provide such method in your moel
emit newSelection(newSelection); // signal connected to setCurrentIndex slot of QTreeView or selection model
}
If you want handle more complex selection it will be more trouble.
I think you provide the answer your self. You can use an internal id that is unique for each item:
template<typename T>
class Item {
public:
Item(int id, T data);
// Access methods here.
private:
int id;
T data;
};
Then, every time you receive the timeout from your timer you can update your item selection list :
vector<int> selection;
for (int &index: treeview->selectionModel()->selectedIndexes()){
selection.push_back(model[index].id); // This assumes you implement [] operator for your model.
}
// Do modify list.
// and the using QModelSelection select again every item which id is in
// `selection`
That way you can "track" the selected items.
It is not a good idea depend on the item value, since this limit the solution to a model containing uninque values. Neither is good idea use the selectionChanged signal since it will be triggered by the user but also by you when change selections programmatically, and that would generate an infinite loop.

Refreshing QListView not working

Need some help finding why my QListView will not refresh.
I'm using QListView with a QSqlTableModel. I implement the model in the following function. I call this function from the class' constructor.
void myclass::refresh()
{
model_path = new QSqlTableModel(this);
model_path->setTable("mytable");
model_path->setEditStrategy(QSqlTableModel::OnManualSubmit);
model_path->select();
ui->listView_path->setModel(model_path);
ui->listView_path->setModelColumn(1);
}
The following function will add a row and the qlistView refreshes without any issue.
void myclass::on_pushButton_add_clicked()
{
QSqlRecord rec (model_path->record());
rec.setValue(1,ui->lineEdit->text());
rec.setValue(2,2);
model_path->insertRecord(-1, rec);
emit model_path->layoutChanged();
}
The following function will remove a row based on which line is highlighted in the QListView. The remove works as the row is deleted from the database once the .submitAll is done. However the QListView does not update consistently.
void myclass::on_pushButton_remove_clicked()
{
model_path->removeRow(ui->listView_path->currentIndex().row());
emit model_path->dataChanged(ui->listView_path->currentIndex(),ui->listView_path->currentIndex());
emit model_path->layoutChanged();
}
If I delete a row, the list won't refresh. If I add one or more new rows, and then delete one or all of them, they will get refreshed. As you can see, I use both dataChanged and layoutChanged but they don't seem to do much here.
I don't understand why the refresh is not consistent. Can anyone help?
You don't need to call both layoutChanged or dataChanged when you add or delete some rows. They are not designed for such kind of updates. Check documentation
Possible, your problem is in understanding edit strategy QSqlTableModel::OnManualSubmit. Try to change it to QSqlTableModel::OnFieldChange

Sorting QtTableModel - QTableView doesn't get updated

I implemented a custom QAbstractTableModel and I'm using a std::vector for my data objects.
Now I wanted to implement the sort() method, to get my table sorted by column.
That's basically what I do:
void SBStateTableModel::sort (int column, Qt::SortOrder order)
{
emit layoutAboutToBeChanged();
switch (column)
{
case Address:
if (order == Qt::DescendingOrder)
std::sort(states.begin(), states.end(), addr_comp_desc);
else
std::sort(states.begin(), states.end(), addr_comp_asc);
default:
return;
}
emit layoutChanged();
}
But emitting layoutChanged() alone doesn't redraw the view. When mark a row and cycle through them, they get updated as they are being highlighted.
The documentation also speaks about updating the persistent indexes. Some people on here have suggested, that this is in fact not necessary.
I'm not even sure how to go about it. Getting the list with persistentIndexList() and then I have to sort it. But std::sort is not a stable sort. I'm not sure how to match the persistent indices with my vector indices.
EDIT:
There was just a "break" missing in the 'case'! So the function would return before emitting the layoutChanged signal.
D'oh!
I was ready to dig into the Qt Source Code. But as I single stepped through my code, I saw the cursor jumping to the return statement in the 'default' case.
I had just forgotten to add a 'break' to my switch-case! It was just a simple fall-through error :((
It works now perfectly fine with "layoutChanged".
I just did this. First, you have to connect the header signal to the sort method you have created. This is a Python sample so you'll need to adapt it to C++:
model = SBStateTableModel()
table = QtGui.QTableView()
table.setModel(model)
table.setSortingEnabled(True)
When you sort, the entire view will change - or at least most of the view will change. So emitting the modelReset signal will cause the view to change. Model reset is one of the most inefficient signals to call, because it causes the entire view to be redrawn. However, most of the view will change anyway on a sort.
emit modelReset();
You could also emit the dataChanged signal, indicating that all of the data has changed. The dataChanged signal may be faster.
Python:
self.dataChanged.emit(self.index(0, 0), self.index(self.rowCount()-1, self.columnCount()-1))
C++:
(emitting a dataChanged signal, in a subclass of QTableView)
auto m = model();
emit dataChanged (m->index(0, 0), m->index(m->rowCount()-1, m->columnCount()-1));

QTreeView::scrollTo not working

Qt 4.8
I have a QTreeView based class with an asociated QAbstractItemModel based class. If I reload the model with new information I want to expand/scroll the tree to a previous selected item.
Both clases, tree view and model are correctly created and connected using QTreeView::setSelectionModel(...) working everything properly.
After reloading the model, I get a valid index to the previous selected item and I scrollTo it:
myTreeView->scrollTo(index);
but the tree is not expanded. However, if I expand the tree manually, the item is really selected.
Tree view is initialized in contruct with:
header()->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
header()->setStretchLastSection(false);
header()->setResizeMode(0, QHeaderView::ResizeToContents);
Any idea about expanding the tree to the selection?
Even QTreeView::scrollTo documentation says:
Scroll the contents of the tree view until the given model item index is
visible. The hint parameter specifies more precisely where the item should
be located after the operation. If any of the parents of the model item
are collapsed, they will be expanded to ensure that the model item is visible.
That is not really true (I think)
If solved the problem expanding all previous tree levels manually:
// This slot is invoqued from model using last selected item
void MyTreeWidget::ItemSelectedManually(const QModelIndex & ar_index)
{
std::vector<std::pair<int, int> > indexes;
// first of all, I save all item "offsets" relative to its parent
QModelIndex indexAbobe = ar_index.parent();
while (indexAbobe.isValid())
{
indexes.push_back(std::make_pair(indexAbobe.row(), indexAbobe.column()));
indexAbobe = indexAbobe.parent();
}
// now, select actual selection model
auto model = _viewer.selectionModel()->model();
// get root item
QModelIndex index = model->index(0, 0, QModelIndex());
if (index.isValid())
{
// now, expand all items below
for (auto it = indexes.rbegin(); it != indexes.rend() && index.isValid(); ++it)
{
auto row = (*it).first;
auto colum = (*it).second;
_viewer.setExpanded(index, true);
// and get a new item relative to parent
index = model->index(row, colum, index);
}
}
// finally, scroll to real item, after expanding everything above.
_viewer.scrollTo(ar_index);
}
I just dealt with similar situation, setting model index via setModelIndex (which internally ends up with scrollTo) worked OK for one of my models, but badly for the other one.
When I forcefully expanded all level 1 items, the scrollTo worked just as described above (calling expandAll suffices).
The reason was a bug in my model class in:
QModelIndex MyBadModel::parent(const QModelIndex& index) const
and as I fixed that, things got normal there too. The bug was such that internalId of parent model index was not the same as when this same model index (for parent) is calculated "from other direction", therefore in this model index (returned by parent method) could not be found in the list of visual indices.
Everything was simple.
Just change autoExpandDelay property from -1 to 0(for example).
ui->treeView->setAutoExpandDelay(0);
QTreeView::scrollTo should expand the hierarchy appropriately.
It's likely that your QModelIndex object is being invalidated when the model is updated (and perhaps still selecting the correct row because the row information is still valid though the parentage is not, don't ask me how those internals work). From the QModelIndex documentation:
Note: Model indexes should be used immediately and then discarded. You should not rely on indexes to remain valid after calling model functions that change the structure of the model or delete items. If you need to keep a model index over time use a QPersistentModelIndex.
You can certainly look into the QPersistentModelIndex object, but like it says in its documentation:
It is good practice to check that persistent model indexes are valid before using them.
Otherwise, you can always query for that item again after the model refresh.
Recently I struggled with same problem.
It's most likely a bug in your model class implementation.
in my case the row() method (that supposed to return index of the index under its parent) was not implemented correctly.
Sadly QT doesnt complain about that, and even selects the index (if you expand manually you will notice).
So, just go though the model code and hunt for bugs in row() and parent() methods etc.
You may be calling scrollTo before the tree view has finished reacting to the changes in current index and which indices are expanded/collapsed. A possible solution may be to delay the call to scrollTo by connecting it to a single-shot timer like this:
QTimer::singleShot(0, [this]{scrollTo(index);});
Using the timer will delay the call until control is passed back to the event queue.

Qt QTreeView not updating when adding to model

The source code relating to this question is available on my public Git repository on BitBucket.
I'm trying to dynamically add some items to a QTreeView model using the following code in mainwindow.cpp:
if(dlg->exec() == QDialog::Accepted) {
QList<QVariant> qList;
qList << item.name << "1111 0000" << "0x00";
HidDescriptorTreeItem *item1 = new HidDescriptorTreeItem(qList, hidDescriptorTreeModel->root());
hidDescriptorTreeModel->root()->appendChild(item1);
}
This works when run from within my MainWindow constructor, just after ui->setupUi(this), but I need this to run from within an event filter, but the same code doesn't get the QTreeView updating. When I set a breakpoint at mainwindow.cpp:70 and step through the next few lines, I can see the data is being added to the Model, but I need the QTreeView to refresh.
I understand this is done by emitting dataChanged(), but not really sure how to do this. The signal signature for the dataChanged signal looks as follows:
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>());
so I need to come up with topLeft and bottomRight QModelIndex instances. How do I build/obtain these from item1 in the above snippet?
Also, where does beginInsertRows() and endInsertRows() come into view with this, should I be calling these functions?
From QAbstractItemModel documentation:
void QAbstractItemModel::beginInsertRows ( const QModelIndex & parent, int first, int last ) [protected]
Begins a row insertion operation.
When reimplementing insertRows() in a subclass, you must call this function before inserting data into the model's underlying data store.
The parent index corresponds to the parent into which the new rows are inserted; first and last are the row numbers that the new rows will have after they have been inserted.
The other protected functions say similar things.
And insertRows() says:
If you implement your own model, you can reimplement this function if
you want to support insertions. Alternatively, you can provide your
own API for altering the data. In either case, you will need to call
beginInsertRows() and endInsertRows() to notify other components that
the model has changed.
Take a look to QAbstractItemModel protected functions and signals
Views connect to those signals to know when model data changes and rearrange data inside. The functions emit the signals internally to make it easy for you to warn the view when it has happenned. But signals can only be emitted by abstract class.
Components connected to this signal use it to adapt to changes in the
model's dimensions. It can only be emitted by the QAbstractItemModel
implementation, and cannot be explicitly emitted in subclass code.
So you will have to stick to the methods.
Edit in answer to your comment:
Indeed, Items should have a reference to model and tell it about changes, check theses lines from QStandardItem:
void QStandardItem::emitDataChanged()
void QStandardItem::removeRows(int row, int count)
( Note, how, in second, it calls model's rowsAboutToBeRemoved() and rowsRemoved() )
Maybe you should try to use QStandardItem and QStandardItemModel.
Either direct or subclassing. It will hide a lot of ugly stuff.
There's also a less proper but much easier way to achieve this - emit layoutChanged() instead of dataChanged(). More info - https://stackoverflow.com/a/41536459/635693