PDA

View Full Version : Self-update QAbstractItemDelegate when item changes



jiveaxe
11th February 2013, 11:04
Hi, have a problem with a QListView that repaints only when moving mouse in the view.

The long story: I have a QVariantList of items coming as reply of network query. Each item has a member representing an url to a remote file image. The item class checks if QPixmapCache contains the pixmap (associated to the url) and eventually downloads and adds it to cache (using temporarily a default image). This QVariantList is assigned to a QAbstractListModel and this to the QListView.

It works quite good: I start searching something, the QListView is populated of items, each with a default image. Nothing changes until I move mouse, when default images are replaced with those downloaded and cached in the background.

Obviously I'm missing a step, something that connects item's replyFinished routine with model/view.

Which is the best way?

Very thanks.

alrawab
11th February 2013, 11:12
emit a signal to repaint with out moving the mouse !!!?
signalsandslots.html

Lykurg
11th February 2013, 11:13
The view updates when the model changes. So how does your model looks like and what do you do after the image gets loaded?

jiveaxe
11th February 2013, 11:19
The view updates when the model changes. So how does your model looks like and what do you do after the image gets loaded?

All happens in custom item class; the model isn't "informed" about changes.

Lykurg
11th February 2013, 11:24
then you should do that! otherwise you must force the view manually to repaint all using update(). If you have a list model, then simply add a custom role which is set to false, and as soon the image is loaded set it to true which will trigger an update.

jiveaxe
11th February 2013, 12:02
If I understood you well in CustomListModel I should add something like


QHash<int, QByteArray> customRoles;
customRoles.insert(ItemRoles::TriggerUpdate, "triggerUpdate");
setRoleNames(customRoles);

My ItemClass should contain a bool that become true when image download is finished.

Back in my CustomListModel, in 'QVariant data(const QModelIndex &index, int role) const' i have to insert something like this?



switch (role) {
case ItemRoles::TriggerUpdate:
if(itemsList.at(index.row())->triggerUpdate())
// update
...
}


If so, I'm having trouble to catch the right routine to call for update.

Lykurg
11th February 2013, 14:19
Well it is hard to give an adequate advise if one can't see how you created your model, but in theory I was talking about: QAbstractItemModel::setData() which is called after you have loaded the image. Inside that function you call QAbstractItemModel::dataChanged() which will trigger a update of the views.

wysota
11th February 2013, 14:29
I would rather suggest to implement your data model in such a way that you only access your underlying data structure through your model (either via methods available in QAbstractItemModel or your custom ones). You need to make sure that changing some item causes QAbstractItemModel::dataChanged() to be emitted, method inserting new items into the model calls beginInsertRows() and endInsertRows() and method removing data from the model calls beginRemoveRows() and endRemoveRows().

jiveaxe
11th February 2013, 14:34
Ok, here it is model implementation:



#include "matcheslistmodel.h"
#include "roles.h"

MatchesListModel::MatchesListModel(QObject *parent) :
QAbstractListModel(parent)
{
// Adding new roles
QHash<int, QByteArray> roles;
roles.insert(ItemRoles::Title, "title");
roles.insert(ItemRoles::Image, "image");
setRoleNames(roles);
}

MatchesListModel::~MatchesListModel()
{
}

void MatchesListModel::setElements(QList<Item *> items)
{
mItems = items;
}

QVariant MatchesListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();

if (index.row() >= mItems.size())
return QVariant();

switch (role) {
case Qt::DisplayRole:
return QVariant(mItems.at(index.row())->title());
case ItemRoles::Image:
return QVariant(mItems.at(index.row())->image());
default:
return QVariant();
}
}

int MatchesListModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return mItems.count();
}


As you can see, I'm not using setData().

ChrisW67
11th February 2013, 22:16
Here are the main points:

The view will not update itself unless the model tells it the data has changed or the user triggers a view change that forces it to refetch the data.
The model cannot tell the view that underlying data has changed unless it knows itself
You are changing the underlying data from outside the model and not informing the model.


The usual approach would be, as wysota points out, to put the actual data inside the model and maintain the state of the data through the model instance. You can use the setData() interface to the model, or your own methods, but whenever the model changes the data it must emit the relevant dataChanged() or related signals. When your network reply comes in you insert the new image into the model causing the model to emit dataChanged().

jiveaxe
13th February 2013, 23:18
The usual approach would be, as wysota points out, to put the actual data inside the model and maintain the state of the data through the model instance. You can use the setData() interface to the model, or your own methods, but whenever the model changes the data it must emit the relevant dataChanged() or related signals. When your network reply comes in you insert the new image into the model causing the model to emit dataChanged().

I've followed your and wysota way. I subclassed QNetworkAccessManager and now the model calls it with url and index; this new class downloads the image and emits a signal intercepted by the model that in turn emits dataChanged(), as you recommend. Now images are self-updated, as expected. I only encountered a small bug: seems that the model calls the download class 4 times per item and when items are a lot the program crashes with this message:


GLib-ERROR **: Creating pipes for GWakeup: Too many open files

Is there a way to limit model's calls? Or is it best to implement a download manager with queue support?

Thank you both for the valuable help.

ChrisW67
13th February 2013, 23:26
The model's data() method will be called multiple times for each model index in the view: for the DisplayRole, DecorationRole, SizeHintRole etc... If you fetch the image once for each call you will indeed be performing efficiency crimes. Make sure you are only fetching a remote image once and storing it in the model for later use.