Qt Model/View: how to handle underlying data properly - qt

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!

Related

How can I express interaction via global variable in sequence diagram

I want to draw a sequence diagram for following example.
I know that I can use message line when there is a function call interaction for data exchange.
But in this case, read function interface is not defined since the target variable is defined as global for share to other components who want to read. I think all data flow between components has to be depicted during the design without considering whether it is via function interface or not. And i believe that it will give clear information about shared variable to other low level component designers.
Is there any way to draw directly shared variable in sequence diagram?
Following is my example explanation in code and what i want to depict is the variable_a which is used between A and B.
A.h
extern unsigned char variable_a;
A.c
unsigned char variable_a;
void func_A(void)
{
variable_a = input();
}
B.c
#include "A.h"
void func_B(void)
{
if(variable_a >= 100)
{
//do something
}
else
{
//do something
}
}
The global variable is an object an can be shown as a separate lifeline. Access to the object can be disclosed for example with get and set messages.
Remark: This technique can be seen as tedious or overkill, but it has the advantage of being accurate and visualising the coupling that would otherwise remain hidden. Btw, it also encourages good practice: the less global variables involved, the less additional lifelines ;-)
Additional hint: You may be interested also in this other question about how objects involved in an interaction are known.
Your code could be translated to this diagram:
The global variable_a is the assignment target of the reply message and the variable is also referenced in a guard of an alt-fragment. I think this covers most needs.
It is possible to model a lifeline for the string (or unsigned char). However, in my world a string doesn't have getters or setters. Maybe it could have an asReal():Real or asInteger():Integer operation. I doubt that it would be helpful to model that.

How to forward signals in a QAbstractItemModel wrapper model

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.

Qt Model View, update view on loosely coupled Model and Data

This question is an upgrade on the following two questions:
Qt Model View pattern, design choices for connecting Model with Data
Qt Model-View update view?
Here is the situation:
MODEL has a pointer to the SERVER (SERVER represents Data) through which it gets the required data and formats them into QStrings, so that the VIEW can understand them. The model keeps no internal copies of the QList, it accesses it directly and converts the requ QTcpSocket * to QStrings in the QVariant QAbstractItemModel::data method.
However the list of sockets can change without the Model or View knowing about it if a new connection to the SERVER is established. In that case another QTcpSOcket * is appended to the SERVERs QList.
How to notify the View on the Model/Data change?
Call QAbstractItemModel::reset() from the SERVER on each new connection. I consider this bad for it requires to modify the SERVER for the needs of the MODEL in which case i could of just had the MODEL and the SERVER as a single entity.
connect(&server, QTcpServer::newConnection, &model, &StationListModel::reset) Try to connect the SERVER and MODEL via Signals and Slots. However, &StationListModel::reset ISN'T a slot, so i believe this isn't the right way.
I would like to hear which of the mentioned approaches (if any) is considered appropriate in the given situation. And is the insisting on MODEL-SERVER loose coupling a bad design choice?
Here is how it should be done:
Create signals in SERVER that notify about data change (or use existing QTcpServer::newConnection signal if it is sufficient).
Create a slot (or slots) in your model class and connect SERVER's signal to this slot.
In the slot's implementation emit signals or call internal methods (e.g. beginInsertRows, endInsertRows) or just reset the model to notify the view about new changes.
Since you need to incrementally append new items to your view, I would do this in the following way:
In your model class
// A slot
void MyModel::onNewConnection()
{
// Append new socket to the list QList<QTcpSocket *>
m_socketList.puch_back(new QTcpSocket);
// Update the model.
insertRow(0);
}
// Virtual function
bool MyModel::insertRows(int row, int count, const QModelIndex &parent)
{
if (!parent.isValid()) {
// We need to append a row to the bottom of the view.
int rows = rowCount() - 1;
beginInsertRows(parent, rows, rows);
// Do nothing, but just inform view(s) that the internal data has changed
// and these rows should be added.
endInsertRows();
return true;
}
return QAbstractItemModel::insertRows(row, count, parent);
}
Somewhere in your code
[..]
connect(&server, QTcpServer::newConnection, &model, &StationListModel::onNewConnection)
I know this is an old question, but I would like to share what I do when I was working on the exactly same issue.
If you are placing the server pointer into the model implementation and you get all the model information from the QList< QTcpSocket *> use this connection:
connect(server, SIGNAL(newConnection()), this, SIGNAL(modelReset()));

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.

Binding a Qt model to an existing data-structure

I've a tree-like polymorphic data-structure, where the nodes are instances of class Node (implemented by me) or any its subclass. My application heavily uses Boost and the nodes are actually represented by boost::shared_ptr type rather than Node*.
Now, I want to create a Qt model to wrap my tree data-structure. Therefore I need a way to associate any model index with a node in my internal data structure. And here comes the problem:
Qt supports two ways of doing it:
First:
QModelIndex QAbstractItemModel::createIndex ( int row, int column, void * ptr = 0 ) const
Creates a model index for the given
row and column with the internal
pointer ptr.
And second:
QModelIndex QAbstractItemModel::createIndex ( int row, int column, quint32 id ) const
Creates a model index for the given
row and column with the internal
identifier, id.
Ok, and how exactly should I associate the node in my case? There is no possibility to associate a shared_ptr with the model index... Yes, I know, I can receive a raw pointer from my shared_ptr and supply it to CreateIndex(), but it smells bad - seems too unsafe to me.
Any ideas?
By the way, I feel that in general Boost / Qt integration seems to be not trivial at least in the area of memory management.
10x a lot.
If you want to do an easy association without passing a raw pointer, put the shared memory in a container and pass the ID value for that container element into the model index. For example, you could created declare
QMap< quint32, boost::shared_ptr< Foo > > index_map;
and use that. You'd have to be careful to not duplicate IDs for existing pointers, perhaps. It seems somewhat overly complicated to me....
You could also just keep a list of the pointers (to ensure continued availability as you need them) and then use the actual address of the pointer in the QModelIndex as well. This is probably what I would do.

Resources