[Qt-interest] Accessors for custom data on QAIM implementations (Re: Animated QProgressBar in a QTableView)
Stephen Kelly
steveire at gmail.com
Thu Sep 2 22:57:47 CEST 2010
Andre Somers wrote:
> Hi,
>
> I think you are right about your critique, but I wanted to comment on
> one issue (just for the lurkers out there ;-) )
>
> Op 2-9-2010 19:12, Stephen Kelly schreef:
>> If you want real decoupling of model from everything else (the whole
>> point) never put additional accessors (like getEmitters) on your model.
>>
> I think the idea is that you should never put additional accessors on
> your model for the views or delegates that interact with it. That should
> all stay inside the QAbstractItemModel interface. But, for back-end code
> that interacts with the model, I think it is perfectly fine to add
> custom accessors on your model. Compare with for instance the
> QFileSystemModel. It adds quite a bit of file system specific API on the
> model class, but all of this API is for use by the back-end part of the
> code, not for use by the views.
I see the point you're making and that it is sometimes convenient to have
type-specific data accessors, and that QFileSystemModel has accessors like
that too for convenience, but that doesn't make it a good idea :). For a
beginner-friendly class it's fine, but if you do complicated applications it
doesn't make sense. It encourages people to write methods which take a
QFileSystemModel* instead of a QAbstractItemModel* or a QModelIndex, which
makes it hard to use the same method when you have a proxy on top.
/**
Returns whether the file at @p index in the QFileSystemModel @p model
has been modified since @p dateTime.
*/
bool SomeClass::isModifiedSince(QFileSystemModel *model, const QModelIndex
&index, const QDateTime &dateTime)
{
return model->lastModified(index) > dateTime;
}
Now lets say you use a proxy model which gives you a model of the children
of a particular QModelIndex such as KSelectionProxyModel or this one:
http://lynxline.com/jongling-qt-models/
and you want to use a particular QModelIndex in that proxy with the same
method. Here's what people do, possibly with more error checking:
bool SomeClass::isModifiedSince(QAbstractItemModel *model, const QModelIndex
&index, const QDateTime &dateTime)
{
QFileSystemModel *realModel = qobject_cast<QFileSystemModel*>(model);
if (!realModel) {
QAbstractProxyModel *proxy = qobject_cast<QAbstractProxyModel*>(model);
realModel = qobject_cast<QFileSystemModel*>(proxy->sourceModel());
}
return realModel->lastModified(index) > dateTime;
}
Someone else might use UngroupProxyModel instead of QAbstractProxyModel and
that would be less useful. So now you can have one proxy on top of the
QFileSystemModel and you can still use your method. What if you add another?
Do you do the qobject_cast in a loop until you get your 'real' model? Isn't
that throwing away any separation in the architecture?
Here's a better implementation of that method.
/**
... The caller must ensure that the QModelIndex @p index is valid.
*/
bool SomeClass::isModifiedSince(const QModelIndex &index, const QDateTime
&dateTime)
{
Q_ASSERT(index.isValid());
return index->data(QFileSystemModel::LastModifiedRole).toDateTime() >
dateTime;
}
That one works no matter what proxy index is from. Remember that any proxy
on top of a QFileSystemModel is 'A model of part of a filesystem' just like
a QFileSystemModel is 'A model of part of a filesystem'. Views are just
observers or consumers of the 'model of part of a filesystem' just like all
of your API should be, so they should use the model interface which is
QAbstractItemModel. I see no data accessors in the QFileSystemModel API
which couldn't use data() instead. Particular methods shouldn't care whether
it is dealing the 'root model' or a proxy on top of it. It shouldn't matter
whether it's a view or delegate or something else. You shouldn't care what
model a QModelIndex is from, only that it is an accessor for data. Think of
it as a pointer, and that you implement your API to take pointers instead of
strong types, which you then cast (which is obviosly not a good idea but
it's a good analogy).
/**
@p filePtr must be a QFile.
*/
bool SomeClass::isModifiedSince(QObject* filePtr, const QDateTime &dateTime)
{
QFile *file = qobject_cast<QFile*>(filePtr);
QFileInfo fi(*file);
return fi.lastModified() > dateTime;
}
bool SomeClass::isModifiedSince(const QModelIndex &index, const QDateTime
&dateTime)
{
Q_ASSERT(index.isValid());
QDateTime fileDateTime = qmodelIndex_cast<QDateTime>(index,
QFileSystemModel::LastModifiedRole);
return fileDateTime > dateTime;
}
where qmodelindex_cast is
template <typename T>
T qmodelindex_cast(index, role) {
return index->data(role).value<T>();
}
Think of models and proxies like base classes and derived classes. If you
have a method which reads from a file you make it take a QIODevice* instead
of a QFile* so that you can also use it with a QLocalSocket* or a QBuffer*.
You implement your API to use the most 'base' API possible. The more you do
that the more you'll be able to write re-usable components which consume
models.
>
> Remember that a QAbstractItemModel derived class is supposed to only
> provide a view on an existing source of data in your application. It is
> an adaptor with a standardized interface between your actual data store
> and the UI views on that data.
Not just the UI views :). Anything that accesses the model can be considered
a view. Even a proxy is a 'view' or observer of a source model, but it also
implements the same standard interface so that it can be 'viewed' too by
other consumers of that standardized interface.
> In a nicely separated design, that will
> often mean that you need to define slots in your model so it can be
> signalled of changes in the data store.
Sure, but those slots can be private or Q_PRIVATE_SLOTS.
Of course, it's only API for access of custom data of particular
QModelIndexes that's a bad idea. API for configuring the 'root' model or
accessing its configuration is fine, like resolveSymlinks().
Sorry for the long email, but it's for the lurkers, you understand :).
All the best,
Steve.
>
> André
More information about the Qt-interest-old
mailing list