How to forward signals in a QAbstractItemModel wrapper model - qt

I intend to create my own item model, derived from QAbstractItemModel. The model does not contain data but wraps some data repository. That repository emits signals after item(s) are insert, removed, renamed, etc.
Whenever something changes in the repository, my item model needs to forward those signals.
However the repository has standalone signals like void itemRemoved(int index); while QAbstractItemModel has begin/end pairs of protected functions (and signals) like beginInsertRows() and endInsertRows().
How should I handle this? E.g. I could connect a slot like the following to the repository's itemRemoved() signal:
void RepositoryItemRemoved(int i)
{
beginInsertRows(QModelIndex(), i, i);
endInsertRows();
}
Based on the above example: Is it valid to call beginInsertRows() / endInsertRows() sequently after a row has been inserted in the repository?

I've had a similar scenario, where the data is in a different object, and the model is just a wrapper, and only created if that data set is displayed in a view. I used a pointer to the model object in the data object, checking if it is null on insert operations, and if not call beginInsertRows() and endInsertRows() through it. Naturally, since those are protected, the data class would have to be declared a friend to the model class.
The documentation stresses that it is important to call beginInsertRows() before any data is inserted:
When reimplementing insertRows() in a subclass, you must call this
function before inserting data into the model's underlying data store...
...Otherwise, the views may end up in an invalid state.
You should test with a view, or alternatively, examine the actual implementation in the source.

I've had somewhat similar scenario as well, only in my case the Qt model wrapped the underlying model a little more literally: the underlying model contained sufficiently more data than the view had to know about. So I let the Qt model to contain its own list of small pieces of each underlying model's data item for the view/delegate to deal with. So the slots processing the updates from the underlying model looked like this:
void RepositoryItemRemoved(int i)
{
beginRemoveRows(QModelIndex(), i, i);
removeModelItem(i);
endRemoveRows();
}
Such design solved the problem of view's invalid state although it may be impractical for the use cases in which letting the Qt model contain its own items list would mean duplicating the sufficient amount of data being worked with.

Related

Qt Model/View: how to handle underlying data properly

I watched tons of videos and spent a lot of time reading papers about models, how to work with them and general idea is quite clear. However, I still don't get several things that really slow me down.
I realize that model works only as an interface between view and data. However, when I look at sample codes, most of the time, some sort of data structure is sent to model and all functions in the model uses that internal model data structure to do required things: evaluate headers, row count etc. Example of such constructor (in this case internal model QList is addressBook):
AddressbookModel::AddressbookModel(const QString& addresses,
QObject *parent): QAbstractTableModel(parent)
{
QStringList records = addresses.split(’\n’);
QStringList line;
foreach(QString record, records)
addressBook.append(splitCSVLine(record));
}
And that looks OK, but it gets very confusing when I try to think about modifying underlying data some where else in the program, when some sort of model is "attached" to that data structure.
For example, lets have a look at this sample code from learning material:
// addressbook/main.cpp
#include <QtGui>
#include "addressbookmodel.h"
int main( int argc, char* argv[] )
{
QApplication app( argc, argv );
QFile file("addressbook.csv");
if ( !file.open(QIODevice::ReadOnly|QIODevice::Text) )
return 1;
QString addresses = QString::fromUtf8(file.readAll());
AddressbookModel model(addresses);
QTableView tableView;
tableView.setModel(&model);
tableView.show();
return app.exec();
}
Here, there is a static variable of addresses which is then sent to model. Now, user would be able to see and modify that data. But what if I want to work more with that data somewhere else in the program? What if I insert new entries to addresses? I realize that model will not see those changes, and in this example (and in many more) that underlying data structure is even sent not as a pointer.
So my question is: how to manage data properly, when I will have new data coming from "behind the scenes" - not only from the model? Should I work with data management only within the model class (implement required functions etc.)? Should I somehow pass only pointers of data to model? Everything gets even more tricky, when I think of using Proxy Models for filtering, because they also work and somewhat "treat" data in their own way. Maybe I missed something important about this architecture, but it really stops me here.
Working with Qts data models can be quite confusing. You will need to take care of most of the "updates" of you own. For example, if you change the models data in your overload of QAbstractItemModel::setData, you will have to emit QAbstractItemModel::dataChanged on your own. Same goes for inserting, removing or moving entries. If you have the time, you should read the link posted by SaZ, but for some quick information about what to emit in which overload, you can check the QAbstractItemModel Documentation.
Regarding the modifying of data "behind the scenes":
Best practice is to change the data over your model, i.e. call QAbstractItemModel::setData to change some data. But since this function is designed to get data in a "displayable format", your better of if you create your own functions. Inside of this functions you will need to "notify" the model of your changes. This way all Views will updater properly.
For example, if your "AddressRecord" has a name property:
void AddressbookModel::changeName(QModelIndex addressIndex, QString name) {
//For this example I assume you are using a simple list model with only one column
//The addressIndex´s column is always 0 in this case, and the parent invalid
addressBook[addressIndex.row()].setName(name);
emit dataChanged(addressIndex, addressIndex);
}
As you can see, you will have to somehow work with the QModelIndex-class, which represents the position of an entry inside your model.
I hope I could help at least a little bit, but Qts Model-View framework can by very tricky, especially if you have to add, remove, move or sort your data. But to get a deeper understanding of it, I'm afraid you will just have to try it out!

How to reset a user-defined tree model in Qt

I have finally implemented my own tree model (inherited from QAbstractItemModel) for a QTreeView.
A blueprint of what I did can be found here:
http://www.trinitydesktop.org/docs/qt4/itemviews-simpletreemodel.html
So, I have:
the user-defined tree items, which are pure C++ (no Qt) and these are wrapped by
the TreeModel class which is inherited from QAbstractItemModel (like in the example link above).
I now have a generated tree hierarchy of tree items from (1.). This hierarchy has thousands of items, and I want to insert this hierarchy into my existing model at runtime.
How do I do that?
(All I have is the root-node to the c++ tree hierarchy as TreeItem pointer and a QModelIndex of the existing model where the "new sub-tree" has to be inserted)
I found modelAboutToBeReset(), modelReset(), and endResetModel() from here: http://qt-project.org/doc/qt-4.8/qabstractitemmodel.html#beginResetModel
But I don't know if these are the right functions, and, if they are, how to use them.
Any ideas?
You should call modelAboutToBeReset() before removing real items from your model. This call will "freeze" all views from requesting any data. After removing all real items you should call endResetModel() - it will unfreeze data requesting and force all connected views to update it content.
This is what i did:
void
TreeModel::addNewSubTreeToModel( TreeNode* t_rootOfNewTree, TreeNode* t_addNewSubTreeAsChildOfThisItem )
{
beginResetModel();
t_rootOfNewTree->setParent(t_addNewSubTreeAsChildOfThisItem);
t_addNewSubTreeAsChildOfThisItem->addChild(t_rootOfNewTree);
endResetModel();
}

How to delete an item from model view?

I am using QTreeView and QAbstractItemModel to establish view whose data also comes from a tree structure. But when I delete a node from a tree structure(data source), then I found that the model view can't automatically adjust itself, it also use the invalid pointer which I don't know it points to which memory block. I don't know how to refresh or what I need to do to fix this problem.
To delete data from the model, use beginRemoveRows() and endRemoveRows().
beginRemoveRows tells the model that you will now change the underlying data structure.
Then change the structure and call endRemoveRows when done. endRemoveRows will then trigger the notifications to update the views:
beginRemoveRows(QModelIndex(), 0, 0);
m_topLevelNodes.remove(0);
endRemoveRows();
This removes the first top-level row (and its children), assuming that the underlying structure in your model keeps the top-level tree items in a container named m_topLevelNodes.

Subclassing QStandardItemModel to avoid QAbstractItemModel

I'm implementing a Model/View for a tree like structure, and I've decided to try the QStandardItemModel on which I want to wrap on it a specific class, (which I call here "appSpecificClass").
Basically, I want part of that class (like names, or some data), to be shown in the model, and when I change the model (in edit role, or drag and drop), I want that to have consequences on the appSpecificClass (which is, when I change a name that is showing on the model, the name on the object associated with the model's item of the appSpecificClass also changes).
So, I started from subclassing the QStandardItem by a appSpecificItem, which only has a pointer to the appSpecificClass. When I construct the appSpecificItem, the text and icons are called from appSpecificClass and everything works fine.
However, when change data from appSpecificItem, naturally it does not change the appSpecificClass, because so far I didn't found any way of interacting with the appSpecificItem's pointer via overloading a virtual function (or else)
Does anyone knows how to do this/if this is possible? What can I do such that if for instance the signal
QStandardItemModel::itemChanged ( QStandardItem * item )
is emitted, I can change a the appSpecificItem's pointer.
If not, is there any good tutorial about implementing a Model from scratch? I've tried myself some, but it is not an easy task. Ideally I would like a QStandardItemModel like model, but a little more abstraction on it (such that I can put my appSpecificClass on it).

Passing QModelIndex cross Thread queued connection

WMIQuery::wmiquery(WMI::WMITable* table, const QString& query, WMI::ProgressIndicator* progressIndicator)
This is the Function signature. and I am calling it through QtConcurrent::run
QFuture<quint32> future = QtConcurrent::run(WMI::WMIQuery::wmiquery, _table, query);
The architecture is quite simple.
Expected number of rows that will be returned by the query is known.
query is ran parallelly and on each record fetch a row is added to table: WMI::WMITable*
WMI::WMITable is a Simple QObject Table Data Structure .
it emits rowsAboutToBeInserted(QModelIndex, int, int) and rowsInserted(QModelIndex, int, int) upon row addition.
On the other hand ProgressIndicator in instantiated on main thread and the table is passed to its ctor . it gets the expected total number of rows from WMI::WMIQuery::wmiquery() through ProgressIndicator::setRecordCount(quint64 count).
it has a slot rowAdded() which emits the progress out of 100 by doing some simple mathematics. In its ctor it connects
connect(_table, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowAdded()));
What I think. as WMI::WMIQuery::wmiquery() i running on a different thread (on QThreadPool) this connection is a cross thread queued connection . am I correct ?
I am getting the following error at runtime
QObject::connect: Cannot queue arguments of type 'QModelIndex'
(Make sure 'QModelIndex' is registered using qRegisterMetaType().)
What should I do ? as my SLOT(rowAdded()) does not require the 3 arguments of SIGNAL(rowsInserted(QModelIndex,int,int)) should I make another signal like rowInserted() and emit it whenever I am emitting rowsInserted(QModelIndex,int,int) and use this SIGNAL instead for this coinnection
You may ask why I am using model like signals like rowsInserted(QModelIndex,int,int) in the table data structure. cause I do also have a model that is connected to this table. which will also be updated row by row. however I think that is immater in this regard.
Before emitting a signal across a thread boundary with a non-trivial argument type (like QModelIndex), you must first call this:
qRegisterMetaType<QModelIndex>("QModelIndex");
That prepares Qt to be able to emit the signal across a thread boundary.
Normally you would do this in main() or somewhere that only runs once, before calling emit, but after your QApplication has been instantiated.
This is only necessary for types that are non-trivial. For example, a signal like this would not require you to call qRegisterMetaType()
signals:
void mySignal(int foo, int bar);
But a signal like this does require qRegisterMetaType():
signals:
void mySignal(QModelIndex);
For more info, see the Qt docs here: http://doc.qt.nokia.com/latest/qmetatype.html#qRegisterMetaType
I know this is rather late, but I wanted to be sure someone mentioned it: QModelIndex is not meant to be queued, for the same reason that it's not meant to be stored and used later in other ways. That is, if the model changes before you use the QModelIndex, you will get undefined behavior. If you need queued events with model indices, you should probably use QPersistentModelIndex. Not really relevant to the original question, but may be of use to someone who lands here.

Resources