Using an existing (row, column) C++ model with QtQuick (Grid, TableView) - c++

I have some models that use the QAbstractItemModel rules for retrieving and providing data for a table.
My example model has multiple columns and rows. Unfortunately QtQuick widgets can only handle a single column. Other "columns" are added to the QtQuick widgets by way of roles. So multiple columns in the view match to the same column in the model. Other model columns are ignored, as explained in this question and its answer
I was thinking that it should not be too difficult to provide an abstraction for QML to be used on the C++ side (as a QAbstractProxyModel) which when asked for row N, modulos it by the column count of the source model and retrieves the data from the resulting actual column. This would appear to work for Grid, but won't work for TableView as it relies on TableViewColumn and role names instead of using only continuous row indices. For that, the proxy model would need to distinguish by the role which column of the source model to retrieve from.
The snippet present in the answer to http://qt-project.org/forums/viewthread/41793 does that for adoping QSqlTableModel, but still misses translating a lot of the signals to be usable. Like I imagine if the SQL source model would emit columnsInserted, it should translate to a signal dataChanged with the new roles chosen for that column and a change of the available role names. The QMLifyProxyModel appears to be better, but not production ready and dead for 4 years now, it seems.
How can we best fix this so that the two worlds work together fluently, according to the official recommendation? Why don't QtQuick views use the (row, column) notation that QAbstractItemModel and QTableView has been using already?

Part of the difficulty is that the way QtQuick use models is stricter than QWidget views', so it could be worth trying to achieve this the other way around; converting models to use roles and use a proxy model to map roles to column indexes + headerData the same way that TableViewColumn does, but for QWidget views. The column insertion and removal signals should be easier to handle if the source is a static number of roles rather than a changing number of columns.
Sadly this won't help for built-in or more complex models.
Most QtQuick views have been designed for 1D models when phones were its design target. Roles were used to map unordered properties of a single row/item to their scripted name.
TableView came a few years later and seems to be the one that should have done the extra mile to allow using 2D models, but it would probably have been quite some additional work at a time where QtQuick was already quite a beast, especially since TableView was mostly written in QML itself.

Related

Display modified data from QAbstractListModel in QTableView

I have a QAbstractListModel that has a bunch of custom objects stored in it, and you can access the different fields of the custom objects in the model by specifying a role (if this is an improper use of Qt roles let me know because I must be confused). I want to display this data in a user friendly QTableView. I can get things displaying using a proxy model, but the issue is I don't want to display the raw values, I want to display specific data derived from the raw data. So for instance, I don't want a column for both ItemA.foo and ItemA.bar, I want to display just ItemA.foo - ItemA.bar in a single column. And to add to that, I want the automatic update functionality you get with models where if either ItemA.foo or ItemA.bar change, I want the difference column to automatically update and recalculate.
I would think that the way to do this would be to use some kind of table proxy model that listens to the source model, and then populates its own fields with the values derived from the source model and listens for dataChanged() signals from the source model. Then you plug this proxy model in to a QTableView. But to me this sounds like something that should be done in a view. Or is this something that should be done by the delegate? I could even go so far as to do these calculations in the base model itself and add roles specific to these values that should be displayed in the table, but that sounds like I am really overloading the responsibilities of the model.
TLDR: How do you manipulate data from a model in a QTableView? Should I do the data manipulation in the base model and then send that to the QTableView? Should I use a proxy model that manipulates the base data and sends it to the QTableView? Or am I completely misunderstanding something?
and you can access the different fields of the custom objects in the model by specifying a role
If you look at the documentation for Qt::ItemDataRole, you would see that Qt models should indeed provide different data for different roles but each role means some distinguished purpose of the data corresponding to the role. For example, the most commonly used role is probably Qt::DisplayRole as the data for this role defines the content displayed in the view e.g. it's the text in the table cell. If you are satisfied with all the other aspects of the view - font, background etc - you can just return empty QVariant for corresponding roles from your model, the view would figure out these details on its own. If you are using roles as a substitute for columns i.e. to return different pieces of conceptually the same data item, it is probably not the intended use of roles.
For the other part of your question - you can customize the appearance of data displayed in the view through the use of a delegate. For example, you could subclass QStyledItemDelegate, override its displayText method to display ItemA.foo - ItemA.bar instead of just ItemA.foo and then set this delegate into the column of your view corresponding to ItemA.foo via setItemDelegateForColumn. The tricky part here would be to detect changes in both ItemA.foo and ItemA.bar columns which would affect the text displayed in the delegate. A while back I implemented a dedicated class in one of my projects which listens to changes in some column of the original model and "reroutes" the change into another column through signal emitting. I did it to solve this very issue - to catch changes in what delegate should display although technically another model column is affected into which the delegate is not set.

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.

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.

Kendo datasource and templates, specifically for Scheduler widget

I've noticed that the scheduler widget does things a bit differently from other widgets. In fact, I read in the documentation that the DS is a different one:
"http://docs.kendoui.com/api/framework/schedulerdatasource"
Anyways, on to my two questions.
when i was doing a template for the day cells, i noticed that if i used value called 'date' it would automatically use the correct date value for that day cell. But i never created this date variable, not did i include it in my datasource. So where did it come from? if its provided through framework, what other values similar to this one are available to me? where can i find some documentation on this?
For kendo widgets, when you apply a datasource and a template, it automatically maps each datasource item to one item in the widget (e.g. one row in the grid, one item in the list view etc.). its a one to one correlation. But this is not the case for the scheduler datasource since, like i stated above, it is a different type of datasource (its a schedulerdatasource). The scheduler datasource mandates that each item in the datasource have a start date and an end date so it can map it to the corresponding cell. hence, this destroys the one-to-one relationship of datasource item to day [template]. How can i revert to the behavior of the datasource with other widgets? do i have to somehow configure it to overwrite the schedulerdatasource to the original datasource? i want to preserve the correlation behavior of 1-to-1 between my datasource and my day template.
just to give a generic example of what i am trying to accomplish with this, imagine that instead of doing entrys with time slots, i want to instead have my scheduler display daily summaries of how many hours i worked out, how many calories i ate, amount of hours i slept etc. But i do not want to associate those amounts with hours in the day.
--
Sorry that was technically more than two questions.
But thanks in advance!
-B
Straight to your questions:
The options available in the eventTemplate are listed in the documentation.
The SchedulerDataSource does one thing more than the regular DataSource - it expands recurring events. This means that for one event which says repeats two days the SchedulerDataSource creates two data items - one for each day. If you don't have any recurring events then you would have the one-to-one mapping. The scheduler can only be bound to a SchedulerDataSource instance (it will throw an exception otherwise).
It looks that the scheduler may not be the widget you are looking for. If you just want to display a list of items the ListView or Grid widgets may be a better fit.

most effective row removal strategy for QStandardItemModel

I have a QStandardItemModel with several 100,000 records of data, and a QSortFilterProxyModel on top of it for filtering and sorting capabilities. I want to remove a substantial number of records, say 100,000, based on the value of one of the columns.
The current implementation iterates over the source model, tests for the value in the appropriate column, and calls removeRow. This turns out to be an extremely slow approach, I don't know why (I've already turned off the signalling of the source model and the sortfilterproxymodel).
What is a more efficient approach?
Can the QSortFilterProxyModel help, e.g. by creating a selection of records to be deleted, and using removeRows?
Thanks,
Andreas
QAbstractItemModel::removeRows() is a candidate, provided that the rows are contiguous. If the model is sorted by the column you are using to do the removal test, then you should be able to use this.
The more effecient approach would be implementing your own model with QAbstractItemModel interface instead of using QStandardItemModel. Then you can build custom indexes which will help you increase performance while deleting items.