QT MVC pattern not updating view - specific SimpleTreeModel example - c++

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.

Related

Item views: setSelectionModel and support for row editing

In my Qt (6.3.1) application, for a model I developed, I noticed the submit() method being called all the time.
After some debugging, I noticed, in void QTableView::setSelectionModel/QTreeView::setSelectionModel, this:
if (d->selectionModel) {
// support row editing
connect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
d->model, SLOT(submit()));
}
The documentation for QAbstractItemModel::submit() mentions "this function is typically used for row editing", which means this is done on purpose.
I have got more than 1 problem with this way of doing things, compared to the alternative of letting/requiring application developers to create the connection themselves:
Views do not seem to have a property to stop this connection from being created, hence the behavior is more than just a default, it is mandatory.
I do not see any way to know what to do except looking through Qt's source code. I had rather have to create the connection myself if I want it.
Only QSqlTableModel seems to have a mechanism to handle this (editStrategy()) but I could find nothing in neither QAbstractItemModel nor QAbstractTableModel.
-> what would be a good reason to want this connection above to be always created? Or am I perhaps wrong thinking this design is a bad one?
Answering my own question after 4 weeks without another answer nor any comment.
Despite having found a solution that seems to work in every case (see below), I still think this very design choice made by Qt as well as other special case they implemented are bad choices, would be interested to read other opinions in the comments.
Better than disconnecting signals, the solution I ended up implementing was to subclass QIdentityProxyModel and create an attribute to block the calls to submit (+ optionally revert).
void MyModel::revert() {
if (forwardRevertCalls)
QIdentityProxyModel::revert();
}
bool MyModel::submit() {
if (forwardSubmitCalls)
return QIdentityProxyModel::submit();
else
return false;
}
The reason for this choice is because of another special case in QStyledItemDelegate::eventFilter. Found in the documentation:
If the editor's type is QTextEdit or QPlainTextEdit then Enter and Return keys are not handled.
And I suppose things like QSpinBox do not behave this way.
This was causing submit to be called whenever I pressed Enter to validate an input in my model and change the selected row in 1 input; more precisely, it would execute case QAbstractItemDelegate::SubmitModelCache in QAbstractItemView::closeEditor.

ListView not initially showing data

My QML ListView doesn't show my data until I perturb it with the mouse (e.g. just drag it up and down.) After this the view shows the model without issue until it empties, and then I once again need to perturb it to get it working again. Is there way to kick this ListView into working?
I'm using Qt 5.8 on Linux 14.04. My model is a subclass of QAbstractListModel. I build it by following the AbstractItemModel Example. The main difference is that my list model is a property of an entity, rather than being set with setContextProperty in main.cpp.
There are a few similar issues here on SO about the ListViews not updating, but none seem to only have an issue at the start. Most of them relate to the OP calling dataChanged manually instead of beforeInsertRows() & endInsertRows() - both methods I'm calling (see below.)
My ListView is in an item loaded with a SceneLoader.
I posted all the relevant code here, because I'm a little suspicious of how I use the Layouts on my ListView (maybe that's causing it? Maybe my hierarchy is broken? I haven't been able to prove that though.)
In short though,
ListView:
ListView {
anchors.fill: parent
model: sceneGraph.blobs
delegate: delegate
}
BlobModel.cpp:
auto BlobModel::addBlob(const BlobPointDataPtr& data) -> void
{
// ...
// Each blob has a uuid
const auto idx = Contains(uuid);
if (-1 != idx)
{
blobs_[idx]->Update(data);
Q_EMIT dataChanged(createIndex(idx, 0), createIndex(idx, 0));
}
else
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
blobs_ << new Blob{data, id_count_}; id_count_++;
endInsertRows(); // responsible for the QQmlChangeSet
Q_EMIT dataChanged(createIndex(rowCount(), 0), createIndex(rowCount(), 0));
}
}
Also, on my terminal, I receive the message:
QObject::connect: Cannot queue arguments of type 'QQmlChangeSet'
(Make sure 'QQmlChangeSet' is registered using qRegisterMetaType().)
This seems to be emitted by endInsertRows(), but I'm not sure why. In the past the solution has been to register the missing type, e.g. qRegisterMetaType<QQmlChangeSet*>("QQmlChangeSet"); but this seems not to be a public type with Qt, and because everything mostly works without it, I'm not sure missing that is the exact issue.
The problem, as pointed out in the comments, was that I was modifying my model outside of the main thread.
My code was set up so that another thread would trigger additions to my model by directly calling addData. The reason my minimal example wasn't able to replicate this was because in it I used a QTimer to simulate the other thread, however QTimer also runs on the main thread.
The solution was to change my direct call to addData(data) to emitted a signal to do the addition, thus moving the actual work back to the main thread.

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

Is there any way to save the order of columns?

I currently have a tableview attached to a class that is derived from QSortFilterProxyModel. Now I wanted to know if there is any way by which I can store the order of columns since the users tend to move the columns back and forth. Also is there any signal that is emitted when a user changes the order of the columns.I search this but I cannot find anything that might tel me when a column is moved around and how i can save the tablew columns orders.
Any suggetsions would be appreciated
You need to obtain a QHeaderView object using QTableView::horizontalHeader. You can use QHeaderView::saveState and QHeaderView::restoreState to save state of columns. You can use QHeaderView::sectionMoved signal to detect column moving.
The reason why you cannot find the relevant signal in the documentation because you are checking an about 6-7 years old documentation. That is, it is Qt 4.1. The signal in question was added to Qt in version 4.6.
QAbstractItemModel has this signal recently:
void QAbstractItemModel::columnsMoved(const QModelIndex & sourceParent, int sourceStart, int sourceEnd, const QModelIndex & destinationParent, int destinationColumn) [signal]
This signal is emitted after columns have been moved within the model. The items between sourceStart and sourceEnd inclusive, under the given sourceParent item have been moved to destinationParent starting at the column destinationColumn.
Note: 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.
This function was introduced in QtCore 4.6.
This looks like what you are looking for. See the documentation for further details:
http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#columnsMoved
Also, do not forget the fact that you will actually need QAbstractTableModel in the end of the day.
If you really wish, you could catch this signal as well:
void QHeaderView::sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) [signal]
This signal is emitted when a section is moved. The section's logical index is specified by logicalIndex, the old index by oldVisualIndex, and the new index position by newVisualIndex.
Please refer to the documentation for further details:
http://qt-project.org/doc/qt-5.1/qtwidgets/qheaderview.html#sectionMoved

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