Sorting QtTableModel - QTableView doesn't get updated - c++

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));

Related

QT MVC pattern not updating view - specific SimpleTreeModel example

I have had problems in my own code to get the views to update after model data is updated.
I then took the SimpleTree example from QT and added a timer in TreeModel to change the data after 10s and then invoke the same set data function used in the constructor to update the model. The code is below for the slot that executes on the timer timeout. No matter what I try, the view does not update. The setDate and emit dataChanged were some attempts.
In my own code, I have a XML-RPC call updating the data, but considering I dont even get the simpleTreeModel to work, I suppose that would be a long shot.
Is there something fundamental that I am missing here?
void TreeModel::slotTimeout(void)
{
QStringList tmp;
tmp << "qaz";
tmp << "wsx";
tmp << "edc";
setupModelData(tmp,rootItem);
setData(QModelIndex(),QModelIndex());
emit dataChanged(QModelIndex(), QModelIndex());
qDebug() << "Timer update";
}
The SimpleTreeModel example is for static models only. It lacks the implementation of the required QAbstractItemModel functions to update the model.
Have a look on the detailed description of the models documentation in order to see what should be implemented.
The problem is, that the required methods are implemented as empty methods by default so you will not get any error messages if something is missing. It will just not work.
In addition it‘s a bit tricky to do the necessary data changed emits.
Within the setData method you have to emit dataChanged().
Within the also necessary insertRows you have to call the methods beginInsertRows(...) and endInsertRows() in order to get the required signals emitted.
A first approach toward the MV paradigma is to use the QStandardItemModel. It provides all the necessary implementation if a QStandardItem is sufficient what it usually will be.

The description about sorting in Model/View Qt document maybe wrong?

In Qt document online Model/View Programming, it's said that If your model is sortable, i.e, if it reimplements the QAbstractItemModel::sort() function, both QTableView and QTreeView provide an API that allows you to sort your model data programmatically. In addition, you can enable interactive sorting (i.e. allowing the users to sort the data by clicking the view's headers), by connecting the QHeaderView::sortIndicatorChanged() signal to the QTableView::sortByColumn() slot or the QTreeView::sortByColumn() slot, respectively.
However, firstly the QTableView::sortByColumn() is not a slot, so one cannot connect a signal to it; secondly, the code of QTableView::sortByColumn() is something like
d->header->setSortIndicator(column, order);
//If sorting is not enabled, force to sort now.
if (!d->sortingEnabled)
d->model->sort(column, order);
and QHeaderView::setSortIndicator() function emit sortIndicatorChanged(logicalIndex, order). But if one uses function setSortingEnabled(true), the signal sortIndicatorChanged(logicalIndex, order) can also be emitted automatically by the view header when click the header column of the view.
So maybe the right way is to make a slot to receive the signal sortIndicatorChanged(logicalIndex, order) and in the slot, call the override virtual function sort() of the model?
Sort the tree view by click a column.
Set the view can be sorted by click the "header".
treeView_->setSortingEnabled(true);
Connect the header signal to a slot made by you.
connect(headerView, SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)),
treeModel_, SLOT(sortByColumn(int, Qt::SortOrder)));
In the slot, call the sort() virtual function of the model. sort() virtual function is a virtual function of QAbstractItemModel, and one should override it.
void TreeModel::sortByColumn(int column, Qt::SortOrder order)
{
sort(column, order);
}
Override the sort() function as your model should do.
emit dataChanged(QModelIndex(), QModelIndex()); from a model to update the whole tree view.

QSortFilterProxyModel crashing app

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.

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

Catch QTableWidgetItem check state change

I have one QTableWidget with some QTableWidgetsItems on it. Some items use checkboxes. I've added the checkboxes using the follow code:
QTableWidgetsItem->setCheckState(Qt::Checked);
I would like now to call some function when this checkbox state change. Using a signal for example.
What may be the easiest way to accomplish this?
The easiest way to do this is to capture signal(s) of QTableWidget with slot(s) in the class that contains QTableWidget. While it would seem that QTableWidget::itemActivated might be our best bet, it is uncertain whether or not this is emitted when the Qt::CheckState is equal to Qt::Checked. In addition, even if this was true, the signal would not provide you the capabilities of handling item unchecking which your application may need to do.
So, here is my proposed solution. Capture the QTableWidget::itemPressed and QTableWidget::itemClicked signals with slots defined in the class that contains the QTableWidget. As itemPressed should be called BEFORE the mouse button is released, and itemClicked should be called AFTER the mouse button is released, the Qt::CheckState for that QTableWidgetItem should only be set in between these two signal emissions. Thus, you can determine exactly when a QTableWidgetItem's checkState has changed with low memory overhead.
Here is an example of what these slots could look like:
void tableItemPressed(QTableWidgetItem * item)
{
// member variable used to keep track of the check state for a
// table widget item currently being pressed
m_pressedItemState = item->checkState();
}
void tableItemClicked(QTableWidgetItem * item)
{
// if check box has been clicked
if (m_pressedItemState != item->checkState())
{
// perform check logic here
}
}
And the signals/ slots would be connected as follows:
connect(m_tableWidget,SIGNAL(itemPressed(QTableWidgetItem *)),this,SLOT(tableItemPressed(QTableWidgetItem *)));
connect(m_tableWidget,SIGNAL(itemClicked(QTableWidgetItem *)),this,SLOT(tableItemClicked(QTableWidgetItem *)));
Where m_tableWidget is the QTableWidget * you associate with your table widget.