Pregunta

My problem is that I have two QTreeWidgets and I would like to do drag and drop from one to an other (and vice-versa). I am able of drag and dropping QTreeWidgetItems, but, when I drop a QTreeWidgetItem that has children, I lose them and only the parent is dropped.

I don't really see how to do it. The only way I have found is reimplementing dropEvent and destroying all teh dropped objects and reconsructing them .. but I don't like that solution because it's slow and the objets are not the same, so it complicates very much the implementation of a Undo/redo feature...

Well, this is what I have:

#include <QApplication>
#include <QDebug>
#include <QObject>
#include <QWidget>
#include <QString>
#include <QTextStream>
#include <QIODevice>
#include <QMainWindow>
#include <QVBoxLayout>


#include "KTreeWidget.h"
#include "KAbstractItem.h"
#include "KItem.h"
#include "KItemGroup.h"

int main(int argc, char* argv[]){

 QApplication app(argc, argv);

 QMainWindow *window = new QMainWindow();
 QWidget* central = new QWidget(window);
 QVBoxLayout *layout = new QVBoxLayout();

 KTreeWidget* kv = new KTreeWidget(window);
 KTreeWidget* trash = new KTreeWidget(window);

 layout->addWidget(kv);
 layout->addWidget(trash);

 central->setLayout(layout);

 KItem* kiarr[5];
 for (int i = 0 ; i < 5 ; i++){
  kiarr[i] = new KItem(kv);
  kiarr[i]->setText(0,QString("Item %1").arg(i));
 }
 KItemGroup* kgarr[5];
 for (int i = 0 ; i < 5 ; i++){
  kgarr[i] = new KItemGroup(trash);
  kgarr[i]->setText(0,QString("Group %1").arg(i));
 }

 window->setCentralWidget(central);
 window->show();

 return app.exec();
}

My QTreeWidget: KtreeWidget.h

#ifndef _KTW_H_
#define _KTW_H_

#include <QTreeWidget>

class KTreeWidget: public QTreeWidget{
 Q_OBJECT

private:
 QTreeWidgetItem* _header;

public:
 KTreeWidget(QWidget* w = NULL);
};

#endif

and KTreeWidget.cc:

#include "KTreeWidget.h"

KTreeWidget::KTreeWidget(QWidget* w):QTreeWidget(w){
 setColumnCount(3);
 _header = new QTreeWidgetItem(NULL);
 _header->setText(0,"Title");
 _header->setText(1,"Edit");
 _header->setText(2,"Open");
 this->setDefaultDropAction(Qt::MoveAction);
 setHeaderItem(_header);
 setDragEnabled(true);
 setAcceptDrops(true);
}

And the items (3 classes, in order to distinguish groups and leafs): KAbstractItem.h

#ifndef _KABSI_H_
#define _KABSI_H_

#include <QObject>
#include <QTreeWidgetItem>
#include <QTreeWidget>

class KAbstractItem : public QObject, public QTreeWidgetItem{
 Q_OBJECT
public:
 KAbstractItem(QTreeWidget* p = NULL);
};

#endif

and KAbstractItem.cc

#include "KAbstractItem.h"
KAbstractItem::KAbstractItem(QTreeWidget* p):QTreeWidgetItem(p){}

KItem.h

#ifndef _KI_H_
#define _KI_H_

#include "KAbstractItem.h"

class KItem : public KAbstractItem{
 Q_OBJECT
public:
 KItem(QTreeWidget* p);
};

#endif

and KItem.cc

#include "KItem.h"

KItem::KItem(QTreeWidget* p):KAbstractItem(p){
 setFlags(Qt::ItemIsSelectable 
  | Qt::ItemIsEditable 
  | Qt::ItemIsDragEnabled
  | Qt::ItemIsUserCheckable
  | Qt::ItemIsEnabled);
}

and KItemGroup.h

#ifndef _KIG_H_
#define _KIG_H_

#include "KAbstractItem.h"

class KItemGroup : public KAbstractItem{
 Q_OBJECT
public:
 KItemGroup(QTreeWidget* p);
};

#endif

and KItemGroup.h #include "KItemGroup.h"

KItemGroup::KItemGroup(QTreeWidget* p):KAbstractItem(p){
 setFlags(Qt::ItemIsSelectable 
    | Qt::ItemIsEditable 
    | Qt::ItemIsDragEnabled
    | Qt::ItemIsDropEnabled
    | Qt::ItemIsUserCheckable
    | Qt::ItemIsEnabled);
}

Whenever I do a drop of one of the Items in the first WTreeWifget inside one of the groups of the second one, it works, but it then I move a group to the top QTreeWidget, I lose all the children...

Could you tell me what I am doing wrong? Thanks in advance!

¿Fue útil?

Solución

So, i checked it and it isnt working as required, you are not doing any wrong.

I seems the problem is that QAbstractItemModel (in wich QTreeWidget relies to encode the dragged data internally) mimeData() method is not considering nested items (se that it just encode row + column, but not parent.

It could be even that view is only passing the QModelIndex of the dragged item, to mimeData, thought, it could be better so, cause of not considering parent info...

The only solution i see is that of reimplementing the dropevent. But you dont have to destroy the items. Use

QTreeWidgetItem * QTreeWidgetItem::takeChild ( int index ) QTreeWidgetItem * QTreeWidget::takeTopLevelItem ( int index )

to take the dragged item

and

void QTreeWidgetItem::insertChild ( int index, QTreeWidgetItem * child )

to drop it

I've checked that this way children are moved right. (See this gist)

Otros consejos

This is how I solved it: When you drag and drop, At creates a new object, differen than yours with the same values for the texts, but it is an other object and yours is simply removed but memory is not freed.

The first thing to do is override removeChild since it is based in indexes and not in addresses so it won't work well with what we are going to do latter. (http://sourceforge.net/p/ckmapper/code/4/tree/trunk/src/KViewer/KAbstractItem.cc#l39)

Then what we need to do is to override the dropEvent of the QTreeWidget. The startegy is the following:

  1. get all the selected items that should be dragged but are not but Qt.
  2. allow Qt to do its dropping.
  3. Find the items on the target that might contain the new objects created by Qt. this can be done with itemAt(event->pos).
  4. Find among the childrne of those items which ones are the dropped ones: this can be done with a dynamic_cast<> because the ones created by Qt are ATreeWidgetItems and ours inherit from this class.
  5. Get its index.
  6. Remove the "parasite" item, and delete it.
  7. insert the selected items at the index from the parasite.

We nedded to override the removeChild because WE are removing children by hand (the selected items) and Qt is removing items (also the selected items) that it should drop latter (but it doesn't). So, if you check the Qt code you will see that it is based in indexes and this is not secure, whereas removing items based on their address is more secure. I'll post my code latter here, I forgot to commit it ;)

Anyway, Trompa's solution is also valid and seems simpler to me. I just wanted to share my solution too ;)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top