PDA

View Full Version : QListView/QAbstractItemView and list internal drag & drop



Infinity
14th June 2014, 02:20
Hi,

I have a QListView which shows data from a custom model which is based on QAbstractListModel.

Now I want that the user can reorder the items via drag & drop. To achive that I set movment to Snap, dragAndDropMode to InternalMove and defaultDropAction to Move.

I also reimplemented some methods of the model to enable drag and drop funcationality (see the code above).

There are two problems:
- When moving an item via drag & drop it gets inserted properly at the new position (and the data is properly set as well), but the old item at the old position gets not removed properly. removeRow() is called, but row is according to the debugger and the result I see always 2. Has anybody an idea why where this problem comes from?

- It is possible to drop an item over another item. This works. The old item gets removed and the data of the "target item" is set correctly. But I don't want that behaviour. I want to disable the possibility of droping an item over another item. I just want the possiblity to reorder the items.

Here is my code. I added some comments.



QVariant FieldModel::data(const QModelIndex &index, int role) const
{
if(index.isValid() && index.row() < m_fields.size()) {
switch(role) {
case Qt::DisplayRole:
return fieldName(m_fields.at(index.row()).first);
case Qt::CheckStateRole:
return m_fields.at(index.row()).second ? Qt::Checked : Qt::Unchecked;
case fieldRole:
return m_fields.at(index.row()).first;
default:
;
}
}
return QVariant();
}

bool FieldModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
bool success = false;
if(index.isValid() && index.row() < m_fields.size()) {
switch(role) {
case Qt::CheckStateRole:
if(value.canConvert(QMetaType::Int)) {
m_fields[index.row()].second = value.toInt() == Qt::Checked;
success = true;
}
break;
case fieldRole:
if(value.canConvert(QMetaType::Int)) {
m_fields[index.row()].first = value.toInt();
success = true;
}
break;
default:
;
}
}
if(success) {
dataChanged(index, index, QVector<int>() << role);
}
return success;
}

// I'm using an user role. I reimplemented itemData to return data for that role as well.
QMap<int, QVariant> FieldModel::itemData(const QModelIndex &index) const
{
QMap<int, QVariant> roles;
for (int i = 0; i <= fieldRole; ++i) {
QVariant variantData = data(index, i);
if (variantData.isValid())
roles.insert(i, variantData);
}
return roles;
}

bool FieldModel::setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles)
{
for (QMap<int, QVariant>::ConstIterator it = roles.constBegin(); it != roles.constEnd(); ++it)
setData(index, it.value(), it.key());
return true;
// the default implementation (from qabstractitemmodel.cpp)
// bool b = true;
// for (QMap<int, QVariant>::ConstIterator it = roles.begin(); it != roles.end(); ++it)
// b = b && setData(index, it.value(), it.key()); // does not call setData() after setData() returned once false -> thats why I reimplemented the method
// return b;
}

Qt::DropActions FieldModel::supportedDropActions() const
{
return Qt::MoveAction;
}

Qt::DropActions FieldModel::supportedDragActions() const
{
return Qt::MoveAction;
}

bool FieldModel::insertRows(int row, int count, const QModelIndex &parent)
{
if(parent.isValid())
return false;

beginInsertRows(parent, row, 0);
for(int index = row, end = row + count; index < end; ++index) {
m_fields.insert(index, p(KnownField::Invalid, false));
}
endInsertRows();
return true;
}

// since the model has only one column is don't think I need to do here more
bool FieldModel::insertColumns(int , int , const QModelIndex &)
{
return false;
}

bool FieldModel::removeRows(int row, int count, const QModelIndex &parent)
{
// row is always 2 when this method is called
if(parent.isValid())
return false;

if(count) {
beginRemoveRows(parent, row, row + count - 1);
for(int index = row, end = row + count; index < end; ++index) {
m_fields.removeAt(index);
}
endRemoveRows();
}
return true;
}

// since the model has only one column is don't think I need to do here more
bool FieldModel::removeColumns(int , int , const QModelIndex &)
{
return false;
}


Thanks for helping.

Infinity
15th June 2014, 17:37
I solved the second problem. To allow drops only outside the items and not over the items I use the following flags reimplementation in my model:


Qt::ItemFlags FieldModel::flags(const QModelIndex &index) const
{
if (!index.isValid() || index.row() >= m_fields.count() || index.model() != this)
return Qt::ItemIsDropEnabled; // we allow drops outside the items
return QAbstractListModel::flags(index) | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled;
}

This solution is taken from qtbase/src/widgets/qlistwidget.cpp.

Edit: I also solved the first problem. These are the correct reimplementations of insertRows and removeRows in my case:


bool FieldModel::insertRows(int row, int count, const QModelIndex &parent)
{
if (count < 1 || row < 0 || row > rowCount() || parent.isValid())
return false;
beginInsertRows(QModelIndex(), row, row + count - 1);
for(int index = row, end = row + count; index < end; ++index)
m_fields.insert(index, p(KnownField::Invalid, false));
endInsertRows();
return true;
}

bool FieldModel::removeRows(int row, int count, const QModelIndex &parent)
{
if (count < 1 || row < 0 || (row + count) > rowCount() || parent.isValid())
return false;
beginRemoveRows(QModelIndex(), row, row + count - 1);
for(int index = row, end = row + count; index < end; ++index)
m_fields.removeAt(index);
endRemoveRows();
return true;
}

The problem was caused here: beginInsertRows(parent, row, 0);
The third argument has to be row + count - 1.