QTableView drag & drop rows not working properly - c++

I am trying to move rows of a QTableView. I build a small MVCE according to this article. I can successfully move rows, however a strange effect happens as soon as I drop the row, see below print screen:
A new row5 gets created instead of pushing two to Row0 and three to Row1
So the correct result should be:
instead I get the following incorrect result:
I have been trying to solve this problem reading this source, which was useful to build and test the model. In addition to that this was useful to understand the parameters to give to the QTableView but did not totally solved the problem. Also from here a small description of the process was useful to read but still my problem persists. Finally I found this interesting source that seems to describe to address Qt::ItemFlags as possible solution but could not properly relate it to a possible solution.
See below the most important part of the code:
main.cpp
#include <QApplication>
#include <QtGui>
#include <QAbstractItemModel>
#include <QTableView>
#include <QListView>
#include <QAbstractItemView>
#include "newmodel.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QStringList numbers;
numbers << "one" << "two" << "three" << "four" << "five";
QAbstractItemModel *model = new NewModel(numbers);
QTableView *tableView = new QTableView;
tableView->setSelectionMode(QAbstractItemView::ExtendedSelection);
tableView->dragDropOverwriteMode();
tableView->setDragEnabled(true);
tableView->setAcceptDrops(true);
tableView->setDropIndicatorShown(true);
tableView->setModel(model);
tableView->setDefaultDropAction(Qt::MoveAction);
tableView->show();
QListView *listView = new QListView;
listView->setDragEnabled(true);
listView->setAcceptDrops(true);
listView->setModel(model);
listView->setDefaultDropAction(Qt::MoveAction);
listView->show();
return a.exec();
}
newmodel.cpp
#include "newmodel.h"
#include <QStringListModel>
#include <QDebug>
NewModel::NewModel(const QStringList &strings, QObject *parent)
: QAbstractListModel(parent)
, stringList(strings)
{}
int NewModel::rowCount(const QModelIndex &parent) const
{
return stringList.count();
Q_UNUSED(parent);
}
QVariant NewModel::data(const QModelIndex &index, int role) const
{
if(!index.isValid())
return QVariant();
if(index.row() >= stringList.size())
return QVariant();
if(role == Qt::DisplayRole || role == Qt::EditRole)
return stringList.at(index.row());
else
return QVariant();
}
QVariant NewModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(role != Qt::DisplayRole)
return QVariant();
if(orientation == Qt::Horizontal)
return QString("Column %1").arg(section);
else
return QString("Row %1").arg(section);
}
Qt::ItemFlags NewModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
if(index.isValid())
return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
else
return Qt::ItemIsDropEnabled | defaultFlags;
}
bool NewModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if(index.isValid() && role == Qt::EditRole) {
stringList.replace(index.row(), value.toString());
emit dataChanged(index, index);
return true;
}
return false;
}
bool NewModel::insertRows(int position, int rows, const QModelIndex &parent)
{
beginInsertRows(QModelIndex(), position, position+rows-1);
for(int row = 0; row < rows; row++) {
stringList.insert(position, "");
}
endInsertRows();
return true;
Q_UNUSED(parent);
}
bool NewModel::removeRows(int position, int rows, const QModelIndex &parent)
{
beginRemoveRows(QModelIndex(), position, position+rows-1);
for(int row = 0; row < rows; ++row) {
stringList.removeAt(position);
}
endRemoveRows();
return true;
Q_UNUSED(parent);
}
Qt::DropActions NewModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
QStringList NewModel::mimeTypes() const
{
QStringList types;
types << "application/vnd.text.list";
return types;
}
QMimeData *NewModel::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/vnd.text.list", encodedData);
return mimeData;
}
bool NewModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent)
{
qDebug() << action;
if(action == Qt::IgnoreAction)
return true;
if(!data->hasFormat("application/vnd.text.list"))
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/vnd.text.list");
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;
}
bool NewModel::dragDropOverwtiteMode() const
{
return false;
}
Thank you very much for pointing in the right direction and trying to shed light on this matter

In NewModel::dropMimeData() you are inserting the row at the specified location. What you are not doing is:
check if the action is a CopyAction or MoveAction
For me I always get a CopyAction even with view.setDefaultDropAction(Qt::MoveAction). By now I'm suspecting a bug in QT5 that ignores the default drop action.
For a MoveAction remove the original row
I'm not sure how to do this right for moves from one view to another or even harder between applications. But as you are moving inside the same table you can just remove the old row in the drop method.
Actually models support moving rows directly instead of remove + insert. See https://doc.qt.io/qt-5/qabstractitemmodel.html#beginMoveRows

Related

QFileSystemModel drop items issue

I have reimplemented QFileSystemModel class to add the drag and drop feature between QTreeView and QListView but it still does not work. It displays the following dialog:
ExplorerModel.h
#ifndef EXPLORERMODEL_H
#define EXPLORERMODEL_H
#include <QObject>
#include <QFileSystemModel>
#include <QMimeData>
#include <QBrush>
#include <QDebug>
class ExplorerModel : public QFileSystemModel
{
Q_OBJECT
public:
using QFileSystemModel::QFileSystemModel;
Qt::DropActions supportedDragActions() const;
Qt::DropActions supportedDropActions() const;
QStringList mimeTypes() const;
Qt::ItemFlags flags(const QModelIndex &index) const;
QMimeData *mimeData(const QModelIndexList &indexes) const;
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent);
private:
bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent);
};
#endif // EXPLORERMODEL_H
ExplorerModel.cpp:
#include "explorermodel.h"
Qt::DropActions ExplorerModel::supportedDragActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
Qt::DropActions ExplorerModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
Qt::ItemFlags ExplorerModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = QFileSystemModel::flags(index);
if (index.isValid()) {
return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
} else {
return Qt::ItemIsDropEnabled | defaultFlags;
}
}
QStringList ExplorerModel::mimeTypes() const
{
QStringList types;
types << "application/octet-stream";
return types;
}
QMimeData *ExplorerModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *mimeData = new QMimeData;
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
if (indexes.first().isValid()) {
QString fileName = data(indexes.first(), Qt::DisplayRole).toString();
qDebug() << fileName;
stream << fileName;
}
mimeData->setData("application/octet-stream", encodedData);
return mimeData;
}
bool ExplorerModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
Q_UNUSED(action);
Q_UNUSED(row);
Q_UNUSED(parent);
if (!data->hasFormat("application/octet-stream")) {
return false;
}
if (column > 0) {
return false;
}
return true;
}
bool ExplorerModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
if (!canDropMimeData(data, action, row, column, parent)) {
return false;
}
if (action == Qt::IgnoreAction) {
return true;
}
int beginRow;
if (row != -1) {
beginRow = row;
} else if (parent.isValid()) {
beginRow = parent.row();
} else {
beginRow = rowCount(QModelIndex());
}
QByteArray encodedData = data->data("application/octet-stream");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
QString fileName;
while (!stream.atEnd()) {
stream >> fileName;
}
//removeRow(row, parent);
insertRow(beginRow, parent);
QModelIndex idx = index(beginRow, 0, QModelIndex());
qDebug() << "Filename: " << fileName;
setData(idx, fileName, Qt::UserRole);
return true;
}
It does not copy or move items. Any ideas how to fix it? Thanks in advance.

Qt 5.6.2(msvc2015-32bit) model/view-readonly example unexpected behaviour

I experiment with the 'readonly' model/view example:
examples/widgets/tutorials/modelview/1_readonly/mymodel.cpp
When I change it to this ...
#include <QDebug>
#include "mymodel.h"
MyModel::MyModel(QObject *parent)
:QAbstractTableModel(parent)
{
}
int MyModel::rowCount(const QModelIndex & /*parent*/) const
{
return 2;
}
int MyModel::columnCount(const QModelIndex & /*parent*/) const
{
return 22000000;
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
qDebug() << QString("R%1C%2")
.arg(index.row() + 1)
.arg(index.column() +1);
if (role == Qt::DisplayRole)
{
return QString("R%1C%2")
.arg(index.row() + 1)
.arg(index.column() +1);
}
return QVariant();
}
... QTableView from the main.c keeps asking for 'index' beyond the cells it is showing :
However, when I change return value to 21000000
int MyModel::columnCount(const QModelIndex & /*parent*/) const
{
return 21000000;
}
... it works as expected :
What is so special about the number 22000000 ?
Thank you.

Qt custom tree model display correctly but buggy and slow

I'm trying to implement a model in which different first-level parents have a child tables of different sizes. In this code there is only one first level parent index and child table of 8 by 8, and the application when displaying clearly buggy and slow, although the data is correct. What am I doing wrong?
testqtmodel.pro
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = testqtmodel
TEMPLATE = app
SOURCES += main.cpp
main.cpp
#include <QApplication>
#include <QAbstractItemModel>
#include <QModelIndex>
#include <QVariant>
#include <QTableView>
class TreeModel : public QAbstractItemModel
{
public:
explicit TreeModel(QObject *parent = 0)
: QAbstractItemModel(parent)
{
for (int i = 0; i < 8; ++i)
for (int j = 0; j < 8; ++j)
foo[i][j] = i + j;
}
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
if (index.internalId() == 0)
return QVariant();
if (index.internalId() == 1)
return foo[index.row()][index.column()];
}
QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE
{
if (!parent.isValid())
return createIndex(row, column, (quint64)0);
else
return createIndex(row, column, (quint64)1);
}
QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE
{
if (index.internalId() == 0)
return QModelIndex();
if (index.internalId() == 1)
return createIndex(0, 0, (quint64)0);
}
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE
{
if (!parent.isValid())
return 1;
if (parent.internalId() == 0)
return 8;
}
int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE
{
if (!parent.isValid())
return 1;
if (parent.internalId() == 0)
return 8;
}
private:
int foo[8][8];
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTableView *v = new QTableView();
TreeModel *m = new TreeModel();
v->setModel(m);
v->setRootIndex(m->index(1, 1));
v->show();
return a.exec();
}
I'm definitely a fool
v->setRootIndex(m->index(1, 1));
should be
v->setRootIndex(m->index(0, 0));

Display enabled QCheckBox as disabled

I use a QTreeView with a TreeModel (that inherits QAbstractItemModel). The TreeModel::flags() function returns Qt::ItemIsUserCheckable and therefore a checkbox is displayed besides every item in the view.
What I need to do is display the checkbox as disabled when the parent item is not checked but the user must be able to interact with it normally (the checkbox must be enabled).
I tried to implement a custom QStyledItemDelegate and draw the checkbox myself. However I don't know how to use custom images when drawing the checkbox at the draw() function of the delegate. I don't even know if this is the right way to do it.
I also thought of using stylesheets somehow, but the display of the checkbox depends on the model data (if the parent is checked or not).
Any Ideas?
This is my ItemDelegate::paint() function.
void ItemDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
int value = index.model()->data(index, Qt::DisplayRole).toInt();
QStyleOptionButton check_box_style_option;
check_box_style_option.state |= QStyle::State_Enabled;
if (value == 1)
check_box_style_option.state |= QStyle::State_On;
else
check_box_style_option.state |= QStyle::State_Off;
check_box_style_option.rect = option.rect;
QApplication::style()->drawControl(QStyle::CE_CheckBox,
&check_box_style_option, painter);
}
[Edit] TreeModel code. The tree model is based on the "simpletreemodel" example. I changed flags() to also return Qt::ItemIsUserCheckable and data(), setData() to enable the checkboxes to be checked and unchecked.
#include <QtGui>
#include <QDebug>
#include "treeitem.h"
#include "treemodel.h"
TreeModel::TreeModel(Container *data, QObject *parent)
: QAbstractItemModel(parent)
{
root_item_ = new TreeItem("");
SetupModelData(data, root_item_);
}
TreeModel::~TreeModel()
{
delete root_item_;
}
int TreeModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
return static_cast<TreeItem*>(parent.internalPointer())->columnCount();
else
return root_item_->columnCount();
}
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
if(role == Qt::CheckStateRole && index.column() == 0)
return static_cast<int>(item->checked()) ? Qt::Checked : Qt::Unchecked;
if(role != Qt::DisplayRole)
return QVariant();
return item->data(index.column());
}
bool TreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
if(role == Qt::CheckStateRole)
item->set_checked(!item->checked());
if(role == Qt::EditRole)
{
qDebug() << "value:" << value.toString();
item->set_data(value.toString());
}
emit dataChanged(index, index);
return true;
}
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return 0;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
}
QVariant TreeModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
return root_item_->data(section);
return QVariant();
}
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent)
const
{
if (!hasIndex(row, column, parent))
return QModelIndex();
TreeItem *parentItem;
if (!parent.isValid())
parentItem = root_item_;
else
parentItem = static_cast<TreeItem*>(parent.internalPointer());
TreeItem *childItem = parentItem->child(row);
if (childItem)
return createIndex(row, column, childItem);
else
return QModelIndex();
}
QModelIndex TreeModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex();
TreeItem *childItem = static_cast<TreeItem*>(index.internalPointer());
TreeItem *parentItem = childItem->parent();
if (parentItem == root_item_)
return QModelIndex();
return createIndex(parentItem->row(), 0, parentItem);
}
int TreeModel::rowCount(const QModelIndex &parent) const
{
TreeItem *parentItem;
if (parent.column() > 0)
return 0;
if (!parent.isValid())
parentItem = root_item_;
else
parentItem = static_cast<TreeItem*>(parent.internalPointer());
return parentItem->childCount();
}
void TreeModel::SetupModelData(Container *data, TreeItem *parent)
{
TreeItem *new_item = new TreeItem(data->id(), parent);
data->set_tree_item(new_item);
parent->appendChild(new_item);
foreach(Container *child, data->children())
SetupModelData(child, new_item);
}

QObject Model is empty in QT

I was working on the code which in simple word displays a list of items.
i am able to populate the QlistView but its a empty model.The model i have created is empty.
I mean i have added some four Items.but i cannot see it in the output.Please help.
Main.cpp
#include <QtGui/QApplication>
#include <QDeclarativeContext>
#include <QKeyEvent>
#include <QAbstractItemModel>
#include <QListView>
#include <QDebug>
#include "qmlapplicationviewer.h"
#include "listmodel.h"
#include "songitem.h"
#include "mainwindow.h"
#include "song.h"
#include "songs.h"
#include "songitem.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
ListModel *model = new ListModel(new SongItem, qApp);
model->appendRow(new SongItem( "Michel Telo","Ai Se Eu Te Pego","Ai Se Eu Te Pego", model));
SongItem test = new SongItem( "Michel Telo","Ai Se Eu Te Pego","Ai Se Eu Te Pego", model);
qDebug() << test.data(NameRole);
QModelIndex index = model->index(0, 2, QModelIndex());
qDebug() << model->data(index);
Songs songz;
Song s;
vector<Song> songCollection;
songCollection = songz.all(3);
int ii;
for(ii=0; ii < songCollection.size(); ii++)
{
QString qstr = QString::fromStdString(songCollection[ii].album);
model->appendRow(new SongItem( qstr , qstr, qstr, model));
}
QListView *view = new QListView;
view->setWindowTitle("Model Data");
view->setModel(model);
view->setStyleSheet("color: black");
view->show();
return app.exec();
}
ListModel.cpp
#include <QDebug>
#include "listmodel.h"
int i=0;
ListModel::ListModel(ListItem* prototype, QObject *parent) :
QAbstractListModel(parent), m_prototype(prototype)
{
setRoleNames(m_prototype->roleNames());
}
int ListModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_list.size();
}
QVariant ListModel::data(const QModelIndex &index, int role) const
{
if(index.row() < 0 || index.row() >= m_list.size())
return QVariant();
return m_list.at(index.row())->data(role);
}
ListModel::~ListModel() {
delete m_prototype;
clear();
}
void ListModel::appendRow(ListItem *item)
{
appendRows(QList<ListItem*>() << item);
//qDebug() << "Test";
//qDebug() << item;
}
void ListModel::appendRows(const QList<ListItem *> &items)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount()+items.size()-1);
foreach(ListItem *item, items) {
connect(item, SIGNAL(dataChanged()), SLOT(handleItemChange()));
m_list.append(item);
}
endInsertRows();
}
void ListModel::insertRow(int row, ListItem *item)
{
beginInsertRows(QModelIndex(), row, row);
connect(item, SIGNAL(dataChanged()), SLOT(handleItemChange()));
m_list.insert(row, item);
endInsertRows();
}
void ListModel::handleItemChange()
{
ListItem* item = static_cast<ListItem*>(sender());
QModelIndex index = indexFromItem(item);
if(index.isValid())
emit dataChanged(index, index);
}
ListItem * ListModel::find(const QString &id) const
{
foreach(ListItem* item, m_list) {
if(item->id() == id) return item;
}
return 0;
}
QModelIndex ListModel::indexFromItem(const ListItem *item) const
{
Q_ASSERT(item);
for(int row=0; row<m_list.size(); ++row) {
if(m_list.at(row) == item) return index(row);
}
return QModelIndex();
}
void ListModel::clear()
{
qDeleteAll(m_list);
m_list.clear();
}
bool ListModel::removeRow(int row, const QModelIndex &parent)
{
Q_UNUSED(parent);
if(row < 0 || row >= m_list.size()) return false;
beginRemoveRows(QModelIndex(), row, row);
delete m_list.takeAt(row);
endRemoveRows();
return true;
}
bool ListModel::removeRows(int row, int count, const QModelIndex &parent)
{
Q_UNUSED(parent);
if(row < 0 || (row+count) >= m_list.size()) return false;
beginRemoveRows(QModelIndex(), row, row+count-1);
for(int i=0; i<count; ++i)
{
delete m_list.takeAt(row);
}
endRemoveRows();
return true;
}
ListItem * ListModel::takeRow(int row)
{
beginRemoveRows(QModelIndex(), row, row);
ListItem* item = m_list.takeAt(row);
endRemoveRows();
return item;
}
SongItem.h
#ifndef SONGITEM_H
#define SONGITEM_H
#include "listmodel.h"
class SongItem : public ListItem
{
Q_OBJECT
public:
enum Roles {
NameRole = Qt::UserRole+1,
ArtistRole,
TrackRole
};
public:
SongItem(QObject *parent = 0): ListItem(parent){}
explicit SongItem(const QString &name, const QString &artist, const QString &track, QObject *parent = 0);
QVariant data(int role) const;
QHash<int, QByteArray> roleNames() const;
inline QString id() const { return m_name; }
inline QString name() const { return m_name; }
inline QString artist() const { return m_artist; }
inline QString track() const { return m_track; }
private:
QString m_name;
QString m_artist;
QString m_track;
};
#endif // SONGITEM_H
SongItem.cpp
#include "songitem.h"
#include <QDebug>
SongItem::SongItem(const QString &name, const QString &artist, const QString &track, QObject *parent) :
ListItem(parent), m_name(name), m_artist(artist), m_track(track)
{
}
QHash<int, QByteArray> SongItem::roleNames() const
{
QHash<int, QByteArray> names;
names[NameRole] = "name";
names[ArtistRole] = "artist";
names[TrackRole] = "track";
return names;
}
QVariant SongItem::data(int role) const
{
switch(role) {
case NameRole:
return name();
case ArtistRole:
return artist();
case TrackRole:
return track();
default:
return QVariant();
}
}
A QListView will fetch data with the Qt::DisplayRole role for display, so you have to adapt SongItem::data to return something for this role, e.g.
QVariant SongItem::data(int role) const
{
switch(role) {
case Qt::DisplayRole:
return name();
case NameRole:
return name();
case ArtistRole:
return artist();
case TrackRole:
return track();
default:
return QVariant();
}
}
It might be that you took some examples related to QML, where it works quite differently.