Qt: QAbstractItemModel and 'const' - qt

I'm trying to use a QTreeView for the first time with QAbstractItemModel and instantly have a problem. QAbstractItemModel interface declares methods as const, assuming they will not change data. But I want the result of a SQL query displayed, and returning data for a record with specified index requires the use of QSqlQuery::seek() which is non-const. Are there any 'official' guidelines to using a QAbstractItemModel with data that must be changed in order to get the number of items, data per item etc? Or must I hack C++ with const casts?

You can get away without any const casts by holding a pointer to the QSqlQuery; your pointer won't change, only the value to which you point, hence the operation will still be considered "const".

Related

Qt QAbstractItemModel function data() called with undefined role

I want to create a custom list using QListView and so I had to extend QListView, QItemDelegate and QAbstractListModel and then implement the specific methods, along with QAbstractItemModel::data(const QModelIndex & index, int role = Qt::DisplayRole) const.
It displays correctly on the screen at first glance, but the problem occurs after populating the list model.,The function data(index,role) is called 4-5 times per item model with different roles (some of them undefined roles/out of range/probably random). And it occurs not only after the initialization of the list model! When I hover a list element, the view calls data(index,role) with the correct index and role but right afterwards it is called again with an unexpected role value.
It seems an awkward behavior. I couldn't find the source of the strange calls. I put logs in every method of my custom classes to be sure that I'm not calling by mistake the data(index,role) method with wrong values.
Does anyone have some ideas where to look at or why this strange calls occur?
EDIT
The source of the "strange" calls is in:
QSize CDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const
{
return QSize(QItemDelegate::sizeHint(option, index));
}
somehow when calling QItemDelegate::sizeHint() it triggers data() with different role values.
In my case I have defined role values starting from 0(0,1,2,3). According to #Jens those values are reserved. Changing the starting value of my custom roles solved my problem.
Custom roles start at 0x100, 0..3 are roles defined by Qt. See the list of roles in qnamespace.h. 13 for example ist SizeHintRole.
The different role calls stem from the interns of QListView, which is trying to figure out, how the cells should be displayed. Example: If you want to change the font of a row, you simply add the switch for FontRole to your data() implementation and pass back a bold font whenever something is important and should be displayed in bold.

Communication between models

My program loads records (one per line) from file in raw format.
I have some Views for displaying this data in different ways:
Byte View
Decimal View (by doing some calculations with byte ranges)
Both Views must provide the ability for editing values.
Here is some code:
class Record
{
public:
void setByte (int position, Byte byte);
void setValue (ValueType type, Value value);
Byte getByte (int position) const;
Value getValue (ValueType type) const;
private:
RawData data;
}
I'm loading records in
QVector<Record> records;
Byte View is a simple table with hex bytes (row - line number, column - byte position). Decimal View is a table too (row - line number, one column - value).
So,what's the best way to design Model(s) for my Views with the ability to communicate with each other (f.e. sending signals when a data was modified from one of the Views)?
As you're using Qt, it's usually best to subclass one of the Qt's own models. This way you'll get a lot of view widgets for free, and at the same time make your code more consistent by following the same standard as the rest of the toolkit you use. Qt's documentation have some guidelines stating which methods/signals/slots you must implement.
Some classes to take a look at: QAbstractItemModel, QAbstractTableModel and QAbstractListModel
For the view side, you should prefer to use widgets provided by Qt, and if necessary, implement a delegate to change it's appearance.
In your specific case, I'd subclass QAbstractTableModel, and wrap your records inside this class.

Storing pointers using QListWidgetItem::setData

I have a QListWidget of calendars. Each QListWidgetItem is logically associated with an instance of Calendar, which is a class that belongs to the Model side of the application.
Can I store this association in the form of a pointer using QListWidgetItem::setData? When I attempt to do this, I get the following error:
error: 'QVariant::QVariant(void*)' is private
There is another constructor for void*: QVariant::QVariant(int typeOrUserType, const void * copy) where you should pass an unique integer to represent the pointer type.
But as stated by the documentation, you could declare your pointer type with Q_DECLARE_METATYPE(Calendar*) and use QVariant::fromValue<Calendar*>(...) and QVariant::value<Calendar*>() to store and retrieve the value.
Or instead, because you are using a QListWidget instead of a regular model, you can just subclass QListWidgetItem, and add a Calendar* member variable with the required accessors, to avoid the overhead of using QVariant.
I would suggest looking at this solution as well, which I think is quite elegant:
(there are minor syntax errors, but you will spot them quickly or the compiler will issue an error)
https://web.archive.org/web/20171025163314/http://blog.bigpixel.ro/2010/04/storing-pointer-in-qvariant/

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