Russian Qt Forum
Август 18, 2017, 21:09 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: [РЕШЕНО] Иерархическая модель, drag-n-drop  (Прочитано 311 раз)
Yegor
Самовар
**
Offline Offline

Сообщений: 159


Просмотр профиля
« : Август 10, 2017, 17:09 »

Всем здравствуйте!

Делаю модель, древовидную, на основе 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 вызывался у меня еще раз, с нужной мне пользовательской ролью.

Прикрепил минимальный проект.
« Последнее редактирование: Август 11, 2017, 12:24 от Yegor » Записан
Swa
Самовар
**
Offline Offline

Сообщений: 152


Просмотр профиля
« Ответ #1 : Август 10, 2017, 19:56 »

А почему вы не хотите переопределять методы 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

Вот код который определяет роли для копирования:
Код:
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;
}
Записан
__Heaven__
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 1909



Просмотр профиля
« Ответ #2 : Август 11, 2017, 09:00 »

Посмотрите здесь
http://www.prog.org.ru/topic_31100_0.html
Возможно поможет
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 9730


Просмотр профиля
« Ответ #3 : Август 11, 2017, 09:28 »

И нужна способность перемещать элементы дерева в пределах модели.
Делал недавно. От Qt-шного DnD пришлось отказаться - слишком многое не устроило, а кое-какого ф-ционала вообще нет. Желание обойтись "как можно меньшей кровью" нормально и понятно, но велосипедизм здесь вполне уместен (то на то и выйдет).
Записан
Yegor
Самовар
**
Offline Offline

Сообщений: 159


Просмотр профиля
« Ответ #4 : Август 11, 2017, 12:24 »

Решил добавить костыли, и получил такой алгоритм:
1) Когда в insertRows добавился новый пустой элемент, запомнить его.
2) Когда вызывается setData, то игнорировать его, выйти сразу, используя флаг "новый элемент запомнен".
3) Когда вызывается removeRows, то перед тем как удалить старый элемент, сначала синхронизировать новый элемент со старым. Далее удалить старый элемент. Сбросить флаг "новый элемент запомнен".

Как по мне, то для такой задачи это намного проще и понятнее, чем если использовать стандартную реализацию.
Прикрепляю исходники проекта.
Записан
lit-uriy
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3811


Просмотр профиля WWW
« Ответ #5 : Август 11, 2017, 12:51 »

а почему в setData не заполняешь нужные данные (собственно синхронизация)?
Записан

Юра.
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  

Страница сгенерирована за 0.202 секунд. Запросов: 22.