How to map the QListWidget / QStandardItemModel item to my logic? - c++

Quick question
Is there an easy/quick way to map a QListWidget or QStandardItemModel (for QListView) item to my application logic?
Complete Question
Note: I will use undistinctly both QListWidget or QListView and itsQStandardItemModel. From my current point of view for this question, switching from one to another is trivial.
I face usually the need to have a QListView or equivalent in an HMI, which shows some text value and need to react on selection/click.
In the SLOT, in order to perform the required action, the row need to be identified. Qt::DisplayRole is NOT appropriate because some texts could be duplicated and QStrings are not the best way to identify data in Computer Science.
// Click on a row
connect( &myView, &QListView::clicked,
[&myView, this]( const QModelIndex &idx)
{
// E.G. need to update the database for this row. Which row?
});
Possible solutions:
Maintain a map to retrieve the ID from the QModelIndex row.
Save in the model row any ID, making easy to apply any operation.
First option is tedious: it requires to connect the model for keeping the map and the model synchronized.. Same logic again and again.
Second option seem by far the best: I save the (e.g. database id) and use it afterward; But, QListView model (up to what I know) does NOT include this very friendly and useful ID. So until now I had extended again and again the models for QListView.
How to map the QModelIndex back to my application logic? Do I really have to extend the model for that simple operation?
RELATED QUESTION: QTreeView: maintaining mapping between QModelIndex and underlying data

What about creating the QAbstractModel manually, this way you have complete control how the QModelIndexes are created, and you can use that to do stuff.
I'v implemented thousands of Qt Models, and I have never liked the QStandardItemModel approach as I would usually need to write more boilerplate than if I had come with the model myself.
Inherit from your QAbstractList/Table/TreeModel
implement index()
create a method for direct item access
.
// Click on a row
connect( &myView, &QListView::clicked,
[&myView, this]( const QModelIndex &idx)
{
auto& myItem = idx.model()->directAccess(idx.row());
});
profit.

Related

How to implement removeRow() function for customized model in QT?

I have searched QT doc for reference but I got some questions.
Here is the implementation in QT doc:
bool TableModel::removeRows(int position, int rows, const QModelIndex &index)
{
Q_UNUSED(index);
beginRemoveRows(QModelIndex(), position, position + rows - 1);
for (int row = 0; row < rows; ++row) {
listOfPairs.removeAt(position);
}
endRemoveRows();
return true;
}
I think it uses index.row in the tableview to directly locate the data in the model. But what if I enable sorting for the tableview? After sorting, the index in the tableview doesn't correspond to the data in the model. The first row in the tableview may be the third data in the model.
How can I locate the data in model through tableview?
And are there any better implementations for removeRow() function?
you can use tableWidgetItem->setData(0, Qt::UserRole, "Desired Identity");
it will be always unique even when you sort the rows. refer
QTableWidget find a row through userdata
As the official documentation says ,
There are two ways of approaching sorting in the model/view architecture:
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.
The alternative approach, if your model does not have the required interface or if you want to use a list view to present your data, is to use a proxy model to transform the structure of your model before presenting the data in the view
So what happens when you change the sorting depends on how you arrange sorting for your model: either sorting in the view changes the arrangement of rows in the model or it only changes that arrangement for the proxy model.
If you use the proxy model, you can insert there some mapping between proxy model's rows (as the view sees them with the current sorting) and the original model's rows. Or you can just add some mapping between the row and the actual data item for that row in the original model - you can in fact even embed a void* to the data item directly into QModelIndex in your model's reimplementation of QAbstractItemModel::createIndex and then retrieve the pointer to the item from the index passed to methods such as removeRow. Another possibility is to operate with some unique integer identifier of the item instead of the direct pointer to it. It's therefore up to you how which way of mapping between rows and the actual model items to choose.

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.

Change QComboBox item text in delegate

I want to change the color and the text of items displayed by a QComboBox, depending on some conditions, but without changing the data in the model itself.
I figured out how to change the color, which was rather easy:
//---------------------------------------------------------------------------------
void
ComboPriorityDelegate::paint(QPainter* p_painter, const QStyleOptionViewItem& p_option, const QModelIndex& p_index) const
{
QStyleOptionViewItem newOption(p_option);
// Set the color
newOption.palette.setColor(QPalette::Text, QColor(255, 0, 0));
QItemDelegate::paint(p_painter, newOption, p_index);
}
Just to explain, in my actual code, I have some conditions there, so I do not want to modify each item.
However, I cannot figure out how I would change the displayed text. I tried setting the text property of the newOption, but it seems like that has nothing to do with the actual text being displayed.
In addition, I need to change the text back to its original form as soon as the item is selected.
I found out that p_index.data().toString() gives me the displayed text, but that doesn't help me modifying it. What I need is an easy way to modify the text attribute of the QLabel (or whatever the QComboBox uses to display item text), without affecting the model itself.
I know that there would be workarounds, like remove the item from the combo box that I want to change and inserting a changed version, but I hope that there is an easier way.
Adding a proxy model on top of your model and changing its data() method seems the easiest solution to me.
There is QIdentityProxyModel which you can simply subclass and override data(). Use your original model as the proxy model's source, and the proxy model as the combobox's model.
Actual Answer:
It seems that what I want to do is simply not possible just using a delegate.
Workaround:
So what I did instead was using an event filter to set the correct suffix for the item texts as well as their color before the drop-down opens (listen to MousePressed event).
And to make sure that the text suffix is removed when an item is selected, I added a slot to the event filter class that must be connected to the activated() signal of the QComboBox. That slot then checks for the suffix and removes it.
This is not a nice thing, but at least it requires only one additional class and two lines of code to make use of. And it should be independent of the data model used.

How to get a QModelIndex from another QSqlQuery used for searching the database (needed for selecting the found items)

I have a problem when dealing with my QSqlQueryModel. I want to search the database for some items and select them in my QTableView with QItemSelectionModel which needs a QModelIndex.
What I don't understand is how should I search in order to get the corresponding QModelIndex for my model/view architecture.
I can't possibly use the model itself, because it would mess up the items shown in the view. So a QSqlQuery or an extra QSqlQueryModel would maybe be an option, but how is it possible to translate from their result-index (.at()?) to a QModelIndex which fits to my Model/View-System...
Thanks in advance!
The easiest way is to take a look at QSQLQueryModel, and set your TableView's model to it.

How can I autoexpand an item in a QTreeView when it is filtered by QSortFilterProxyModel?

I have a normal QTreeView, a custom QAbstractItemModel and a custom QSortFilterProxyModel.
I've reimplemented QSortFilterProxyModel::filterAcceptsRow to filter items from my model in the way I want, however now I want those filtered items to be expanded in the treeview.
The obvious solution was to emit a signal from QSortFilterProxyModel::filterAcceptsRow() when an accepted item was found, then connect that signal to the QTreeView::expand().
However, QSortFilterProxyModel::filterAcceptsRow() is const, so I cannot emit a signal from inside that method. QSortFilterProxyModel doesn't have any other signals that would aid me.. and I am beginning to think I will have to subclass QTreeView, which I'd rather not do (less code == better).
So, is there any way to autoexpand those items that the filtermodel accepts?
QTreeView has a "expandAll" slot, which could be called after you set the model. I would think that this should do what you want.