Всем здравствуйте!
Делаю модель, древовидную, на основе QAbstractIrtemModel. И нужна способность перемещать элементы дерева в пределах модели.
Модель сделана на основе динамического дерева (свой класс элемента дерева - 'Item'). Хочется реализовать drag-n-drop по-проще. То есть переопределить лишь insertRows, removeRows, data, setData, flags. И не связываться с переопределением mimeData, canDropMime и т.д.
Привожу листинги элемента дерева, модели.
Элемент:C++ (Qt)
#include <QtCore/QVector>
#include <QtDebug>
class Item{
public:
enum ItemType { ItemType_Root, ItemType_Common };
// Constructor.
Item ( const QString& text, int itemNo, Item* parent )
: m_itemNo(itemNo),
m_text(text),
m_parent(parent)
{}
// Empty constructor.
Item ()
: m_itemNo(0),
m_text(),
m_parent(0)
{}
// Copy constructor.
Item ( const Item& rhz )
: m_itemNo ( rhz.m_itemNo ),
m_text ( rhz.m_text ),
m_parent ( rhz.m_parent )
{}
~Item()
{
qDeleteAll(m_childs.begin(),m_childs.end());
m_childs.clear();
}
void setItemNo(int no)
{
m_itemNo=no;
}
int getItemNo() const
{
return m_itemNo;
}
void setText ( const QString& text )
{
m_text=text;
}
QString getText() const
{
return m_text;
}
bool addChild ( Item* child )
{
child->m_parent = this;
m_childs << child;
return true;
}
bool insertChild ( int i, Item* child )
{
if ( (i<0) || (i>m_childs.size()) ) {
qWarning("Item::insertChild - warning: 'i' is out of rage.");
return false;
}
child->m_parent = this;
if ( i == m_childs.size() )
m_childs << child;
else
m_childs.insert(i,child);
return true;
}
void removeChild ( Item* child )
{
if (!child)
return;
m_childs.removeAll(child);
child->m_parent = 0;
}
void removeChild (int i)
{
if ( (m_childs.count()-1 < i) || (i<0) ) {
qWarning("Item::removeChild - warning: index is out-from range.");
return;
}
m_childs.removeAt(i);
}
Item* getChild(int i)
{
if ( (i < 0) || i>m_childs.size()-1 ) {
qWarning("Item::getChild - warning: index is out of range.");
return 0;
}
return m_childs.at(i);
}
int getChildsCount() const
{
return m_childs.count();
}
Item* getParent()
{
return m_parent;
}
void setParent ( Item* newParent )
{
m_parent = newParent;
}
private:
int m_itemNo;
QString m_text;
Item* m_parent;
QVector<Item*> m_childs;
};
Q_DECLARE_METATYPE(Item*)
Модель, заголовок.C++ (Qt)
#ifndef MODEL_H
#define MODEL_H
#include <QtCore/QAbstractItemModel>
class Item;
// Goal class.
class Model : public QAbstractItemModel
{
protected:
enum UserItemRoles { SynchItemUserRole = Qt::UserRole };
public:
Model ( Item* m_data, QObject *parent );
virtual ~Model();
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
virtual QModelIndex parent(const QModelIndex& index) const override;
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
virtual Qt::DropActions supportedDropActions() const override;
virtual Qt::DropActions supportedDragActions() const override;
virtual bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex()) override;
virtual bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override;
virtual bool setData(const QModelIndex& index, const QVariant & value, int role = Qt::EditRole) override;
private:
Item* getItem ( const QModelIndex& index );
// ** Attrs.*****************
Item* m_root;
};
#endif // MODEL_H
Модель, реализация.C++ (Qt)
#include "Item.h"
#include <QtCore/QMetaType>
#include "model.h" // Goal class.
Model::Model (Item *m_data, QObject* parent )
: QAbstractItemModel(parent),
m_root(0)
{
int res = qRegisterMetaType<Item*>("Item*");
if ( !res )
qWarning("Can't reg 'Item' type.");
if ( m_data )
m_root = m_data;
}
Model::~Model()
{
delete m_root;
m_root =0;
}
QVariant Model::data(const QModelIndex& index, int role) const
{
if ( !index.isValid() )
return QVariant();
const Item* item = static_cast<Item*>(index.internalPointer());
if (!item) {
qCritical("Model::data - can't get internal item.");
return QVariant();
}
if ( (role == Qt::DisplayRole) || (role == Qt::EditRole) )
{
return item->getText();
}
else if (role == UserItemRoles::SynchItemUserRole )
{
QVariant v;
v.setValue(const_cast<Item*>(item));
return v;
}
return QVariant();
}
// virtual
bool Model::setData(const QModelIndex& index, const QVariant & value, int role)
{
qInfo() << "Set data:"
<< " index =" << index
<< " value =" << value
<< " role =" << role;
Item* item = getItem(index);
if ( !item ) {
qCritical("Model::setData - error: can't get internal item.");
return false;
}
if ( role == Qt::EditRole )
{
item->setText(value.toString());
}
else if ( role == UserItemRoles::SynchItemUserRole )
{
// Ge old item.
Item* oldItem = value.value<Item*>();
if ( !oldItem ) {
qCritical(" Model::setData - error: can't get 'oldItem' val from QVariant.");
return false;
}
// Synch new item with old item.Begin.
item->setText ( oldItem->getText() ); // Usefull data.
// Childs.
for ( int i=0; i<oldItem->getChildsCount(); i++ )
{
// Rem from old item.
Item* child = oldItem->getChild(i);
oldItem->removeChild(i);
// Add to new item.
item->addChild(child);
}
// Synch new item with old item.End.
}
return true;
}
QModelIndex Model::index(int row, int column, const QModelIndex& parent) const
{
Q_UNUSED(column);
if ( !hasIndex ( row, column, parent) ) {
qWarning("Model::index - has no index.");
return QModelIndex();
}
// Root.
if ( !parent.isValid() )
return createIndex(row,0,const_cast<Item*>(m_root->getChild(row)));
// Get internal item.
Item* item = static_cast<Item*>(parent.internalPointer());
if ( !item ) {
qCritical("Model::index - can't get intrernal item.");
return QModelIndex();
}
// Get child.
Item* child = item->getChild(row);
if ( !child ) {
qCritical("Model::index - can't get child item.");
return QModelIndex();
}
return createIndex(child->getItemNo(),0,child);
}
QModelIndex Model::parent(const QModelIndex& index) const
{
// Protection.
if ( !index.isValid() )
return QModelIndex();
Item* item = static_cast<Item*>(index.internalPointer());
if ( !item ) {
qCritical("Model::parent - error: can't get internal item.");
return QModelIndex();
}
// If this is root.
if ( item == m_root )
return QModelIndex();
// Get item's parent.
Item* parentItem = static_cast<Item*>(item->getParent());
if ( !parentItem ) {
qCritical("Model::parent - error: can't get item's parent.");
return QModelIndex();
}
if ( parentItem == m_root )
return QModelIndex();
const int row = parentItem->getItemNo();
return createIndex(row,0,const_cast<Item*>(parentItem));
}
int Model::columnCount(const QModelIndex& parent) const
{
Q_UNUSED(parent)
return 1;
}
int Model::rowCount(const QModelIndex& parent) const
{
if ( !parent.isValid() ) {
if ( m_root )
return m_root->getChildsCount();
else
return 0;
}
Item* item = static_cast<Item*>(parent.internalPointer());
if (!item) {
qCritical("Model::rowCount - error: can't get internal item.");
return 0;
}
return item->getChildsCount();
}
QVariant Model::headerData(int section, Qt::Orientation orientation, int role ) const
{
if ( role == Qt::DisplayRole )
{
if ( orientation == Qt::Horizontal )
{
if ( section == 0 )
return QString("Name");
}
}
return QVariant();
}
// virtual
Qt::ItemFlags Model::flags(const QModelIndex& index) const
{
if ( !index.isValid() )
return (QAbstractItemModel::flags(index)) | Qt::ItemIsDropEnabled;
return (QAbstractItemModel::flags(index))
| Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
}
// virtual
bool Model::insertRows(int row, int count, const QModelIndex& parent)
{
qInfo() << "Insert rows:"
<< "row:" << row
<< ", count:" << count
<< ", parent index:" << parent;
// New empty item.
Item* item = new Item();
// Parent item.
Item* parentItem = getItem(parent);
beginInsertRows(parent,row,row);
parentItem->insertChild(row,item); // Add new item to its parent.
endInsertRows();
item->setItemNo(row);
return true;
}
// virtual
bool Model::removeRows(int row, int count, const QModelIndex& parent)
{
qInfo() << "Remove rows:"
<< "row:" << row
<< ", count:" << count
<< ", parent index:" << parent;
// Parent item.
Item* parentItem = getItem(parent);
beginRemoveRows(parent,row,row);
Item* childItem = parentItem->getChild(row);
parentItem->removeChild(row);
delete childItem; childItem=0;
endRemoveRows();
return true;
}
// virtual
Qt::DropActions Model::supportedDragActions() const
{
return Qt::MoveAction;
}
// virtual
Qt::DropActions Model::supportedDropActions() const
{
return Qt::MoveAction;
}
Item* Model::getItem ( const QModelIndex& index )
{
if ( !index.isValid() )
return m_root;
Item* item = static_cast<Item*>(index.internalPointer());
if ( !item )
qWarning("Model::getItem - warning: null internal item got.");
return item;
}
А теперь узкое место:
При перемещении (drag-n-drop) элемента мышкой, затем бросания элемента - происходит:
1) Сначала вызывается метод
insertRows(), там я создаю новый пустой элемент, вставляю его в дерево.
2) Потом вызывается метод
setData(), там тот новый пустой элемент заполняется данными из старого элемента.
3) Вызывается метод
deleteRows(), в нем я удаляю старый элемент.
Все хорошо, кроме метода
setData.
Я хочу кроме стандартных ролей (
Qt::ItemDataRole) использовать и свою собственную роль. Для более хитрых манипуляций, например:
синхронизировать новый элемент данными старого элемента.
Но вот только в моей программе метод
setData(const QModelIndex& index, const QVariant & value, int role) вызывается только 2 раза.
Первый раз с ролью role == Qt::DisplayRole и с ролью role == Qt::EditRole.
Но эти роли у меня уже используются. И в методе
data() тоже. И для "хитрых" манипуляций они не подойдут. Повторюсь. "Хитрые" операции - это синхронизация нового элемента со старым.
Мне нужно чтобы метод setData вызывался у меня еще раз, с нужной мне пользовательской ролью.
Прикрепил минимальный проект.
А почему вы не хотите переопределять методы mimeData и canDropMime? Так же проще будет.
В стандартной имплементации копируются роли итемов только до Qt::UserRole. Если ваша роль больше или равна Qt::UserRole, она не будет скопирована. Смотрите методы
void QAbstractItemModel::encodeData(const QModelIndexList &indexes, QDataStream &stream) const
bool QAbstractItemModel::decodeData(int row, int column, const QModelIndex &parent, QDataStream &stream)
QMap<int, QVariant> QAbstractItemModel::itemData(const QModelIndex &index) const
например, тут : http://code.qt.io/cgit/qt/qt.git/plain/src/corelib/kernel/qabstractitemmodel.cpp (http://code.qt.io/cgit/qt/qt.git/plain/src/corelib/kernel/qabstractitemmodel.cpp)
Вот код который определяет роли для копирования:
QMap<int, QVariant> QAbstractItemModel::itemData(const QModelIndex &index) const
{
QMap<int, QVariant> roles;
for (int i = 0; i < Qt::UserRole; ++i) {
QVariant variantData = data(index, i);
if (variantData.isValid())
roles.insert(i, variantData);
}
return roles;
}