PDA

View Full Version : Updating a view's proxy model when the source model changes



graffy
29th August 2013, 06:35
Hello,

I have a model that manages a list of files. It's viewed by two widgets (a combobox and a tableview). Each widget has it's own QSortFilterProxy to pick which files it's supposed to display.

The problem I'm encountering is related to drag and drop operations. After I perform a drag and drop (reordering list items), I've had trouble updating the views. For some reason, neither proxy model responds to the source model's dataChanged() signal (which I emit in the source model's setData() function, as well as the removeRows() function which is called last in the drag-drop operation). Anyway, I can solve the tableview refresh problem easily enough by adding a call to reset() in a slot in it's proxymodel, but the combo box is proving more difficult.

I can't use the reset() trick with the combobox's proxy model because it will reset the current index to -1, losing whatever item has been selected.

This leaves me with two possible solutions. Either I need to make a connection which ensures a proxy model is always synced with changes in it's source, or I need to save the current index of the combo box before the model is changed, then restore that index after the change is made. On the latter technique, I'm not sure how I can do that, nor am I certain that the view would update if I updated it's proxy model programmatically...

Any help would be much appreciated...

Santosh Reddy
29th August 2013, 07:34
The problem I'm encountering is related to drag and drop operations. After I perform a drag and drop (reordering list items), I've had trouble updating the views.
What do you mean by reordering?
If it is just inserting row(s), are you using beginInsertRows() and endInsertRows()?
If it is complete reordering, are you using beginModelReset() and endResetModel()?

graffy
29th August 2013, 14:22
I'm only inserting a row at a time. I still have to call reset() on each proxy (or in the source model alone) to get the view to refresh properly. That's fine for the table view, but not good for the combo box view...

Santosh Reddy
29th August 2013, 14:46
are you using beginInsertRows() and endInsertRows()?

graffy
29th August 2013, 22:58
Yes - sorry - I forgot to mention that. Here's some code...

Source model drag/drop and other relevant code...



Qt::ItemFlags EsxModel::ContentModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;

EsmFile *file = item(index.row());

if (!file)
return Qt::NoItemFlags;

Qt::ItemFlags dragDropFlags = Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
Qt::ItemFlags checkFlags = Qt::ItemIsUserCheckable;
Qt::ItemFlags defaultFlags = Qt::ItemIsDropEnabled | Qt::ItemIsSelectable;

if (canBeChecked(file))
return Qt::ItemIsEnabled | dragDropFlags | checkFlags | defaultFlags;
else
return defaultFlags;
}

bool EsxModel::ContentModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole)
{
QString fname = value.value<QString>();
mFiles.replace(index.row(), findItem(fname));
emit dataChanged(index, index);
return true;
}

return false;
}

bool EsxModel::ContentModel::insertRows(int position, int rows, const QModelIndex &parent)
{
beginInsertRows(QModelIndex(), position, position+rows-1);

for (int row = 0; row < rows; ++row)
mFiles.insert(position, new EsmFile);

endInsertRows();
return true;
}

bool EsxModel::ContentModel::removeRows(int position, int rows, const QModelIndex &parent)
{
beginRemoveRows(QModelIndex(), position, position+rows-1);

for (int row = 0; row < rows; ++row)
mFiles.removeAt(position);

endRemoveRows();
emit dataChanged(index(0,0,parent), index(rowCount()-1, 0, parent));
return true;
}

Qt::DropActions EsxModel::ContentModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}

QStringList EsxModel::ContentModel::mimeTypes() const
{
QStringList types;
types << "application/omwcontent";
return types;
}

QMimeData *EsxModel::ContentModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *mimeData = new QMimeData();
QByteArray encodedData;

QDataStream stream(&encodedData, QIODevice::WriteOnly);

foreach (const QModelIndex &index, indexes)
{
if (index.isValid())
{
QString text = data(index, Qt::DisplayRole).toString();
stream << text;
}
}

mimeData->setData("application/omwcontent", encodedData);
return mimeData;
}

bool EsxModel::ContentModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
if (action == Qt::IgnoreAction)
return true;

if (!data->hasFormat("application/omwcontent"))
return false;

if (column > 0)
return false;

int beginRow;

if (row != -1)
beginRow = row;
else if (parent.isValid())
beginRow = parent.row();
else
beginRow = rowCount(QModelIndex());

QByteArray encodedData = data->data("application/omwcontent");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
QStringList newItems;
int rows = 0;

while (!stream.atEnd())
{
QString text;
stream >> text;
newItems << text;
++rows;
}

insertRows(beginRow, rows, QModelIndex());

foreach (const QString &text, newItems)
{
QModelIndex idx = index(beginRow, 0, QModelIndex());
setData(idx, text);
beginRow++;
}

return true;
}


And the proxy model:



EsxModel::MasterProxyModel::MasterProxyModel(QObje ct *parent, QAbstractTableModel* model) :
QSortFilterProxyModel(parent)
{
setFilterRegExp(QString("game"));
setFilterRole (Qt::UserRole);

if (model)
setSourceModel (model);
}

QVariant EsxModel::MasterProxyModel::data(const QModelIndex &index, int role) const
{
return QSortFilterProxyModel::data (index, role);
}

Santosh Reddy
30th August 2013, 08:11
Can you show the rowCount()? Is it based on anything other than mFiles.size()?

graffy
30th August 2013, 10:34
Nope.



int EsxModel::ContentModel::rowCount(const QModelIndex &parent) const
{
return mFiles.size();
}

Santosh Reddy
30th August 2013, 12:10
Add this


int EsxModel::ContentModel::rowCount(const QModelIndex &parent) const
{
if(parent.isValid())
return 0;

return mFiles.size();
}


also do the corresponding changes in parent() and index()

graffy
30th August 2013, 15:23
Being new to Qt in general, I'm a bit lost... The parent() and index() functions have not been implemented in the source model. I presume that's something that needs to be done? Any examples you could point to that might fill in the gaps in my understanding on this?

graffy
31st August 2013, 01:34
I may not have a clear enough understanding of Qt, but could it be that the proxied view gets messed up because the item is being manipulated w.r.t the source model parent index and not the proxy model? That is, I presume each proxy has a local parent for it's filtered version of the source model (since it will have it's own unique index values)... So, if the drag/drop and related operations reference the items according to the source model's parent, then it would make sense that they would get screwed up when rendered in a view that is filtered by a proxy...

Which would suggest implementing the source model methods that take a parent() parameter in the proxy and calling the source model method with mapToSource() ... ?

Santosh Reddy
31st August 2013, 07:43
What is you model based on QAbstractItemModel/QAbstractTableModel/QStandardItemModel?

graffy
31st August 2013, 12:17
source inherits QAbstractTableModel, proxies inherhit QSortFilterProxyModel...

Santosh Reddy
31st August 2013, 12:36
Ok, Can you post a minimal working code which illustrates the problem?

graffy
2nd September 2013, 03:04
Ok, attached are the code files.

A few notes:

1. The purpose is to manage the selection of items which have dependencies. A base item is selected in the combo box ("master"), and the corresponding items in the table view which share that master are enabled ("slaves"). Also, some dependencies serve as bases for others. For example, "slave1.10.1" and "slave1.10.2" depend upon "slave 1.10" and "master 1"...

2. "slave" Items are checkable. Whether or not an item is checkable is not affected by whether or not it is enabled (i.e., whether or not it's dependencies are checked).

3. Drag/drop allows for ordering of dependencies...

4. The error, here, appears to have something to do with the fact that when I replace the dummy item inserted in my model list (resulting from a drop operation) with the item that was dragged, the item is copied, but not the "master" list that is associated with it. Thus, the dropped item disappears from the slave view and apperas in the master view. Calling reset() fixes this for both views.

5. For some reason, removeRows() is not being called, though I have the drag action set to Qt::MoveAction.

6. The problem appears to be related to the fact that I'm filtering SourceModel with a QSortFilterProxy for both views.

Wish I could have boiled it down to a simpler case, but I'm not really sure where the problem lies, so I kept a lot of code that I might have otherwise removed.

Thanks for taking time to look at this...

EDIT: I just noticed I forgot to remove the call to reset() in SourceModel::setData(). Also, adding "mModel->toggleCheckState(0);" to the end of MainWindow::buildMasterView will sync the combobox and tableview on startup...

Last two files...

Santosh Reddy
2nd September 2013, 12:53
Here is corrected code :)

graffy
2nd September 2013, 14:13
Hmmm...

I see that the code works... and I noticed how you recopied the master string list for the item that gets dropped. I had thought to do that, but it didn't make sense to me that the originial master list would be lost when the pointer to the itemContainer object was moved to a different place in the QList... Seems to me that's a bug?

Anyway, I ran your code and it worked... sort of. That is, the master view isn't corrupted, but the item that's dragged and dropped disappears from the slave view entirely... It's still in the list, but the tableView doesn't refresh properly. Strangely, when I ran in debug mode, put a break at the end of SourceModel::dropMimeData(), and then resumed, it behaved perfectly. Wondering if maybe this a problem with my machine and not the code?

I'm at a loss. I appreciate the help you've provided thus far and would be grateful for any other thougths you might have.

Thanks.

Santosh Reddy
3rd September 2013, 06:36
It is only a matter of emitting proper dataChanged() signals. Take a closer look at all the dataChanged() signals in my code and try to understand why they are emitted.

Hint: When a master is selected (using the combo box), you will have to emit dataChaged() for the master and all the slaves which have the selected item as master. The second emit was missing in your code.

graffy
3rd September 2013, 12:56
Ok - I see that dataChanged calls must track all of the changes in the underlying model... Following that logic, it seems the reordering of items in a model would require a dataChanged() signal be emitted for those items whose list position changed. Presumably, this would be the item which is dragged / dropped to a different position, as well as the items between the two positions? (I tried emitting dataChanged() for every item in the model after a drop event, but no luck...)

Santosh Reddy
3rd September 2013, 14:07
Presumably, this would be the item which is dragged / dropped to a different position, as well as the items between the two positions?
dataChanged not required in this case. it just row insert and and row remove.


Hint: When a master is selected (using the combo box), you will have to emit dataChaged() for the master and all the slaves which have the selected item as master. The second emit was missing in your code. Did you understand the hint?

graffy
4th September 2013, 00:51
I thought I understood you but it seems clear I did not. On a hunch, I added a loop under the Qt::EditRole switch case in setData(), which took the item being dropped, looked up the master, then looked up any other items which depended on that master and called dataChanged() for each, including the master item... Should be functionally identical to the code you added under the Qt::UserRole + 1 switch case. Unfortunately, the dragged / dropped (reordered) slave item in the slave table view still disappears completely...

Unfortunately, I'm at a loss to see what you're getting at.

Santosh Reddy
4th September 2013, 05:18
What is the version of Qt you are using for this code?

Just so that you know the code I posted works fine in Qt 5.0.2 and Qt 5.1.0, I can see the problem is with Qt 4.7.4 and Qt 4.8.1

graffy
4th September 2013, 12:42
Ah, yes. I thought to mention that at one point and it slipped my mind.

Qt 4.8.5. No choice there. This is for an open source project I'm involved with and they're locked in to 4.8 for some reason. I pled the case to go to 5.0, but no luck. :(

Santosh Reddy
5th September 2013, 07:14
I am busy rest of the week, I will take a closer look some time early next week.

graffy
5th September 2013, 12:25
No worries. Thanks for all the effort so far. I learned a good bit in the process.

graffy
18th September 2013, 06:35
After recompiliing, it seems as though I finally have it running under 4.8.5. Not sure exactly why it was excluding the dropped item in the model, but it appears to run fine, now. Thanks again for all of the help.