How to signal QAbstractItemModel::dataChanged with changes to few columns only? - c++

After updating the model data of my derived class of a QAbstractItemModel, I do not want the listeners (assume a QTreeView) to completely update all of the tree representation, but only the data in an individual column of most of the rows.
Example: I want to only update the second column of my items, so I thought I had to emit dataChanged(createIndex(0,1,&root), createIndex(rowCount(),1,&root);
However, this still updates the complete tree for some reason - is that expected behaviour? Does it have to do with the underlying model organisation (which works very similar to Qt's own simple tree example)? Or does it have yet other reasons I did not consider?

Related

Update of QTreeView without changing selection

I have a model that retrieves data from a table in a database from a certain SQL query, and shows the items in a QTreeView. The characteristics are:
the data comes from a table, but has an underlying tree structure (some rows are parents that have rows below them as children)
this tree structure is shown in the QTreeView
the children are selectable in the QTreeView (not so the parents)
the table in the database gets updated continuously
in the updates, a children can be added to any existing parent
periodically (with a QTimer) the QTreeView is updated with the contents of the table
Since the children are added at any time to any parent, the first silly approach when updating the QTreeView is clearing it all, and append all the rows again, in form of parent or children, to the QTreeView. This is a 0-order approximation, and it is indeed terrible inefficient. In particular, the following problems appear:
Any existing selection is gone
Any expanded parent showing its children is collapsed (unless ExpandAll is active)
The view is reset to show the very first row.
What is the best solution to this problem? I mean, the first solution I will try will be not to clear the QTreeView, but instead parse all the returned rows from the table, and check for each of them whether the corresponding item in the QTreeView exists, and add it if not. But I wonder if there is a trickiest solution to engage a given table in a database with a QTreeView (I know this exists for a QTableView, but then the tree structure is gone).
This thread mentions a general approach, but this might get tricky quickly, but I am not sure how this would work if the underlying model is changing constantly (i.e. the QModelIndex becoming invalid).
Worst case is that you will have to write your own mechanism to remember the selection before updating and then re-applying it.
I assume you use some model/view implementation? You could enhance your model with a safe selection handling, in case the example mentioned above does not work for you.
I guess this is the case for a self-answer.
As I presumed, after a careful analysis of what data is retrieved from the database, I had to do the following "upgrades" to the retrieval code:
I retrieve, along with the fields I want to show in the view, two identifiers, one for grouping rows and one for sorting items into groups
I also retrieve the internal record ID (an increasing integer number) from the table in the database, in order to ask only for new records in the next retrieval.
In the model population code, I added the following:
I first scan the initial records that may belong to existing groups in the model
When, in the scanning, I reach the last group in the model, this implies that the rest of retrieved records belong to new groups (remember the records are retrieved sorted such that items that belong to the same group are retrieved together)
Then start creating groups and adding items to each group, until we use all the records retrieved.
Finally, it is very important:
the use beginInsertRows() and endInsertRows() before and after inserting new items in the model
capture the sorting status of the view (with sortIndicatorSection() and sortIndicatorOrder()) and re-apply this sorting status after updating the model (with sortByColumn())
Doing that the current position and selection in the QTreeView receiving the model updates are preserved, and the items in the view are added and the view updated transparently for the user.

Qt Table model to Tree model

In Qt I have a sqlite database which I'm pulling in. One of the tables (configTable) has a QSqlTableModel attached.
The table has a simple 2-column key/value structure. The keys are strings with folder-like values such as "general/name", "general/version", "foo/bar/baz", etc. Values are just arbitrary variants.
I'd like to display this data in an easier-to-browse QTreeView instead of a QTableView, as my key structure lends itself very nicely to that.
Before I go reimplementing classes and all sorts of crazy things - is there an elegant solution to this? And if I reimplement or extend classes, which ones should I look at?
Thank you.
You have to do the parsing+mapping between the list of value/value/value and a tree model yourself. But there is a (tricky) Qt way to do this yes.
The Qt Model-View architecture can represent many different structures of data, based on the QAbstractItemModel class. A Qt model must implement some functions to tell the view : how many columns, row, children etc.
A list model (Qt provides QAbstractListModel), is basically a model that says to the view :
I have one root item (all data items are represented by a QModelIndex, root has an invalid parent)
This root item has only one column
This root item has as many rows as your list has elements
A tree model will return the appropriate children for each QModelIndex. The abstract model of Qt actually allows each child item to be a table (QModelIndex always has a parent and a row-column index).
Long story short, you have to create a proxy model (QAbstractProxyModel or a suitable subclass, but for your need I don't think there is one). This proxy will transform the data your QSqlTableModel is sending, and this is where you can tell the view that you actually have a tree and not a list.
Your root items are the items from your database list of keys (first element of the foo/bar/whatever), but you need to regroup all the root items that has the same key.
AFAIK you can make it only manually.
Basically, because how did you think Qt knows how to convert your data into tree model.

Is Qt’s model–view system intended to work with one model or multiple models?

I’m trying to display some tabular data with a QTableView subclass and a QAbstractTableModel subclass. I can’t get the data to show up, but before I start really pounding on it I want to make sure that I’m using models in the way they were intended.
The data layer of my application periodically receives new data and distributes the data to the other parts of the application by calling slots like
void new_data_received(QSharedPointer<Measurement> measurement)
where Measurement is my data class. This allows the data to be passed around without being copied (some of my data classes are very large). Measurements are immutable; the table view that displays them doesn’t allow any editing.
Measurement is a subclass of QAbstractTableModel, so whenever I receive a new measurement I call set_model on my QTableView subclass instance with the new data as a parameter. (In the time before the first measurement is received there is no model set on the table view.)
Are Qt’s view classes intended to be used like this, with a new model being set every so often? Or should there be just one instance of the model class, with the same lifetime as the table view, that receives the new data and emits dataChanged? The latter seems like it adds unnecessary structure—at least in my case—but maybe that’s the way the system was designed to be used.
I don't think your Measurement class should be a subclass of QAbstractTableModel. It should represent raw data instead. So maybe a struct with some parameters or a list of structs will be a right type for your data class.
Then you should implement a custom model where incoming data are added to. So, when new data arrives that model will automatically update all the views connected to it. In this case new data affects directly your model only, not the views.
I suppose resetting view's model every time is not the right way to do what you want.

QTableView preserve selection after model refresh

I tried to build a user interface which displays the content of a table while the data is refreshed every second.
Therefore I have a chain of models:
QSqlTableModel - to access the tables content
MyModel - inherited from QIdentityProxyModel to modify the data a bit (source is the TableModel)
SomeFilterModels - which have MyModel as source
This chain ends in a QTableView. Because the QSqlTableModel is refreshed every second,
any selection in the TableView is removed every second also. Now I had two Ideas to fix this.
Prevent the TableModel from detecting changes. Which was not working very well.
Catching some events fired before and after the model is about to change to store and restore the current selection. Sadly the QIdentityProxyModel does not forward signals like modelAboutToBeReset or modelReset or dataChanged .. it is also impossible to reemit those signals from MyModel because they are private.
I was searching for other ways to counter those refresh problems without success. But I can't imagine that I am the first person who uses a chain of proxy models combined with a periodic model refresh and selections.
Can anyone give me some tips?
Thanks in advance.
Maybe worth to note:
One QSqlTableModel is used for many TableViews. (With a different FilterProxyModel chain.) So I can't just stop refreshing because one View has a selection.
You may think that I know when I call the models refresh method. But for now it is a bit to complicated to pass this trough my ui architecture. I mean the model is updated and the TableView has already a connection to the updated model through some ProxyModels. There should be no need for another way of communication.
Hope my question makes sense.
QAbstractItemModel includes a number of signals that can help you know when the data in the model is or will be changing. In particular, it has the following signals:
dataChanged
headerDataChanged
modelAboutToBeReset
modelReset
columnsAboutToBeInserted
columnsAboutToBeMoved
columnsAboutToBeRemoved
columnsInserted
columnsMoved
columnsRemoved
rowsAboutToBeInserted
rowsAboutToBeMoved
rowsAboutToBeRemoved
rowsInserted
rowsMoved
rowsRemoved
Given that you lose the selection, I assume that the bolded signals are the ones you want to be concerned with, because the default Qt behavior is to preserve selection if they can where the columns or rows are removed/inserted and it doesn't affect the selection.
Once you are connected to these signals, in modelAboutToBeReset I suggest getting IDs for the cells that you can reuse to select them again, and in modelReset then using those IDs to get the QModelIndexs and using them to again select the same cells.

Supporting multi-add/delete (and undo/redo) with a QAbstractItemModel (C++)

Greetings,
I've been writing some nasty code to support the undo/redo of deletion of an arbitrary set of objects from my model. I feel like I'm going about this correctly, as all the other mutators (adding/copy-pasting) are subsets of this functionality.
The code is nastier than it needs to me, mostly because the only way to mutate the model involves calling beginInsertRows/beginRemoveRows and removing the rows in a range (just doing 1 row at a time, no need to optimize "neighbors" into a single call yet)
The problem with beginInsertRows/beginRemoveRows is that removal of a row could affect another QModelIndex (say, one cached in a list). For instance:
ParentObj
->ChildObj1
->ChildObj2
->ChildObj3
Say I select ChildObj1 and ChildObj3 and delete them, if I remove ChildObj1 first I've changed ChildObj3's QModelIndex (row is now different). Similar issues occur if I delete a parent object (but I've fixed this by "pruning" children from the list of objects).
Here are the ways I've thought of working around this interface limitation, but I thought I'd ask for a better one before forging ahead:
Move "backwards", assuming a provided list of QModelIndices is orderered from top to bottom just go from bottom up. This really requires sorting to be reliable, and the sort would probably be something naive and slow (maybe there's a smart way of sorting a collection of QModelIndexes? Or does QItemSelectionModel provide good (ordered) lists?)
Update other QModelIndeces each time an object is removed/added (can't think of a non-naive solution, search the list, get new QModelIndeces where needed)
Since updating the actual data is easy, just update the data and rebuild the model. This seems grotesque, and I can imagine it getting quite slow with large sets of data.
Those are the ideas I've got currently. I'm working on option 1 right now.
Regards,
Dan O
Think of beginRemoveRows/endRemoveRows, etc. as methods to ask the QAbstractItemModel base class to fix up your persistent model indexes for you instead of just a way of updating views, and try not to confuse the QAbstractItemModel base class in its work on those indexes. Check out http://labs.trolltech.com/page/Projects/Itemview/Modeltest to exercise your model and see if you are keeping the QAbstractItemModel base class happy.
Where QPersistentModelIndex does not help is if you want to keep your undo/redo data outside of the model. I built a model that is heavily edited, and I did not want to try keeping everything in the model. I store the undo/redo data on the undo stack. The problem is that if you edit a column, storing the persistent index of that column on the undo stack, and then delete the row holding that column, the column's persistent index becomes invalid.
What I do is keep both a persistent model index, and a "historical" regular QModelIndex. When it's time to undo/redo, I check if the persistent index has become invalid. If it has, I pass the historical QModelIndex to a special method of my model to ask it to recreate the index, based on the row, column, and internalPointer. Since all of my edits are on the undo stack, by the time I've backed up to that column edit on the undo stack, the row is sure to be there in the model. I keep enough state in the internalPointer to recreate the original index.
I would consider using a "all-data" model and a filter proxy model with the data model. Data would only be added to the all-data model, and never removed. That way, you could store your undo/redo information with references to that model. (May I suggest QPersistentModelIndex?). Your data model could also track what should be shown, somehow. The filter model would then only return information for the items that should be shown at a given time.