QTreeView how to support move action?
-
I just want to move my items in order to reorder them. How can i actually support that? As far as i know i have to do something with QMimeData but i don't exactly how. Any good example or help would be appreciated very much. <3
Thanks.Or should i better do the reordering in the drag and drop event of QTreeView by removing and inserting rows? I thought doing it directly in the model would be better.
StandartItem Model:
#include "ViewLayerStandartItemModel.h" #include "ViewLayerList.h" #include <QMimeData> ViewLayerStandartItemModel::ViewLayerStandartItemModel(int rows, int columns, QObject *parent) : QStandardItemModel(rows, columns, parent) { } Qt::ItemFlags ViewLayerStandartItemModel::flags(const QModelIndex &index) const { Qt::ItemFlags defaultFlags = QStandardItemModel::flags(index); // Überprüfen Sie, ob der Index zu einem Ihrer speziellen Delegaten gehört if (!data(index, CanHaveChildrenRole).toBool()) { return (defaultFlags & ~Qt::ItemIsDropEnabled) | Qt::ItemIsDragEnabled; // Entfernen Sie das ItemIsDropEnabled-Flag und fügen Sie das ItemIsDragEnabled-Flag hinzu } return defaultFlags | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; // Fügen Sie das ItemIsDragEnabled und ItemIsDropEnabled Flag hinzu } Qt::DropActions ViewLayerStandartItemModel::supportedDropActions() const { return Qt::MoveAction; }
QTreeView:
#include "ViewLayerList.h" #include <QHBoxLayout> #include <QCheckBox> #include <QLabel> #include "ViewLayerDropIndicatorStyle.h" #include <QMouseEvent> #include "resizablepixmapitem.h" #include "SignalManager.h" #include <QHeaderView> #include <QPushButton> #include <QTimer> #include <QApplication> ViewLayerList::ViewLayerList(CustomGraphicsScene *scene, QWidget *parent) : QTreeView{parent}, scene_durchgereicht(scene) { setStyle(new ViewLayerDropIndicatorStyle(style())); // Ändern Sie den Abstand zwischen den Items und der vertikalen Scrollbar setViewportMargins(0, 0, 50, 0); // Passen Sie den rechten Rand (20) an Ihre Anforderungen an //Versteckt die sinnlose Kopfzeile setHeaderHidden(false); setRootIsDecorated(true); setMouseTracking(true); //setFocusPolicy(Qt::StrongFocus); //setEditTriggers(QAbstractItemView::AllEditTriggers); mydelegate = new ViewLayerItemDelegate(this); model = new ViewLayerStandartItemModel(4,1,this); for(int row = 0; row < 4; ++row) { for(int col = 0; col < 1; ++col) { QModelIndex index = model->index(row, col, QModelIndex()); model->setData(index, ""); model->setData(index, true, ViewLayerStandartItemModel::CanHaveChildrenRole); // Dieses Element kann keine Kinder haben if(row == 0 && col == 0) { model->setData(index, false, ViewLayerStandartItemModel::CanHaveChildrenRole); // Dieses Element kann keine Kinder haben } } } this->setModel(model); this->setItemDelegate(mydelegate); this->setDragDropMode(QAbstractItemView::InternalMove); this->setSelectionMode(QAbstractItemView::ExtendedSelection); this->setDragEnabled(true); this->setAcceptDrops(true); this->setDropIndicatorShown(true); }
-
Isn't this the same (or similar) topic as this one?
-
@Pl45m4 Similar but not the same as i scraped that approach i used before. I got it working but it turned to be to error prone and maybe the wrong way. So therefore the question here if i may should choose the approach of doing it in the model directly instead and if so how exactly?
:)
-
@StudentScripter said in QTreeView how to support move action?:
the question here if i may should choose the approach of doing it in the model directly instead and if so how exactly?
In your QAbstractItemModel subclass, you need to implement:
mimeData()
supportedDropActions()
supportedDragActions()
mimeTypes()
dropMimeData()See HERE
-
@mpergand So handling this in the model would be the better way you say?
Any good examples on how to implement these properly? I have struggled with QMimeData ever since.
mimeData()
supportedDropActions()
supportedDragActions()
mimeTypes()
dropMimeData() -
@StudentScripter
Here the code I 've used in a bookmarks treeview for a web browser:QMimeData* mimeData(const QModelIndexList &indexes) const { if(indexes.isEmpty()) return NULL; QMimeData* mime = new QMimeData; QByteArray bytes; QDataStream stream(&bytes,QIODevice::WriteOnly); QModelIndex index=indexes.first(); // supporte seulement un élément if(index.isValid()) { QString text=data(index, Qt::DisplayRole).toString(); stream<<index.internalId()<<index.row()<<index.column()<<text; } mime->setData(MIME_Bookmark,bytes); return mime; } QStringList mimeTypes() const { QStringList list; list<<"text/uri-list"<<"text/plain"<<MIME_Bookmark; return list; } bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(column) if(action EQ Qt::IgnoreAction) return false; if(action EQ Qt::MoveAction) { QByteArray bytes=data->data(MIME_Bookmark); QDataStream stream(&bytes,QIODevice::QIODevice::ReadOnly); qintptr i; int r,c; QString text; stream>>i>>r>>c>>text; QModelIndex index=createIndex(r,c,i); moveRow(index,index.row(),parent,row); } EventCenter().postEvent(Event_BookmarksChanged,this); return true; } Qt::DropActions supportedDragActions() const { return Qt::MoveAction;//|Qt::CopyAction; } Qt::DropActions supportedDropActions() const { return Qt::LinkAction|Qt::MoveAction|Qt::CopyAction; }
-
@mpergand Sorry for asking again but i got stuck eventually and haven't found any solution on the web. I want to support dragging and dropping between the rows, but everytime i try this my action gets prevented. How can i override that behaviour?
I have overwritten the dropindicator in my treeview:
void ViewLayerList::drawRow(QPainter *painter, const QStyleOptionViewItem &options, const QModelIndex &index) const { if (selectionModel()->isSelected(index)) { // Zeichnen Sie den Einzugsbereich (Indentation) mit Ihrer gewünschten Farbe painter->fillRect(options.rect, QColor(173, 216, 230)); // "lightblue" Hervorhebungsfarbe*/ } QTreeView::drawRow(painter, options, index); // Rufen Sie die Basisimplementierung auf, um die Standardzeichnung durchzuführen if(MouseIsPressed == true) { // Überprüfen Sie die Position des Drop-Indikators QAbstractItemView::DropIndicatorPosition position = dropIndicatorPosition(); // Überprüfen Sie, ob das aktuelle Element das Element ist, über dem sich die Maus befindet QModelIndex mouseIndex = indexAt(mapFromGlobal(QCursor::pos())); if (mouseIndex == index) { // Speichern Sie den ursprünglichen Stift QPen originalPen = painter->pen(); QPen IndicatorPen; // Bestimmen Sie das Aussehen des Drop-Indikators basierend auf seiner Position switch (position) { case QAbstractItemView::AboveItem: case QAbstractItemView::BelowItem: IndicatorPen.setWidth(9); // Setzen Sie die Breite des Stifts IndicatorPen.setColor(QColor(0, 255, 0, 127)); // Setzen Sie die Farbe des Stifts auf halbtransparentes Grün painter->setPen(IndicatorPen); painter->drawLine(options.rect.topLeft(), options.rect.topRight()); break; case QAbstractItemView::OnItem: break; case QAbstractItemView::OnViewport: break; } // Stellen Sie den ursprünglichen Stift wieder her painter->setPen(originalPen); } } } void ViewLayerList::dragEnterEvent(QDragEnterEvent *event) { QTreeView::dragEnterEvent(event); } void ViewLayerList::dragMoveEvent(QDragMoveEvent *event) { QModelIndex belowIndex; // Überprüfen Sie die Position des Drop-Indikators QAbstractItemView::DropIndicatorPosition position = dropIndicatorPosition(); // Bestimmen Sie das Aussehen des Drop-Indikators basierend auf seiner Position switch (position) { case QAbstractItemView::AboveItem: case QAbstractItemView::BelowItem: qDebug() << "Drop AboveItem or BelowItem"; DropOnItem = false; // Holen Sie sich den QModelIndex des Elements unterhalb der aktuellen Position belowIndex = indexAt(event->position().toPoint() + QPoint(0, 1)); if (belowIndex.isValid()) { // Holen Sie sich die Zeile des Elements RowBelowDropPositon = belowIndex.row(); qDebug() << "Row of item below: " << RowBelowDropPositon; } break; case QAbstractItemView::OnItem: qDebug() << "Drop OnItem"; DropOnItem = true; break; case QAbstractItemView::OnViewport: qDebug() << "Drop OnViewport"; break; } QTreeView::dragMoveEvent(event); }
I also added the appropriate flags in my model:
#include "ViewLayerStandartItemModel.h" #include "ViewLayerList.h" #include <QMimeData> #include <QDataStream> ViewLayerStandartItemModel::ViewLayerStandartItemModel(int rows, int columns, QObject *parent) : QStandardItemModel(rows, columns, parent) { } Qt::ItemFlags ViewLayerStandartItemModel::flags(const QModelIndex &index) const { Qt::ItemFlags defaultFlags = QStandardItemModel::flags(index); // Überprüfen Sie, ob der Index zu einem Ihrer speziellen Delegaten gehört if (!data(index, CanHaveChildrenRole).toBool()) { return (defaultFlags & ~Qt::ItemIsDropEnabled) | Qt::ItemIsDragEnabled; // Entfernen Sie das ItemIsDropEnabled-Flag und fügen Sie das ItemIsDragEnabled-Flag hinzu } return defaultFlags | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; // Fügen Sie das ItemIsDragEnabled und ItemIsDropEnabled Flag hinzu } Qt::DropActions ViewLayerStandartItemModel::supportedDragActions() const { return Qt::MoveAction | Qt::CopyAction; } Qt::DropActions ViewLayerStandartItemModel::supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } QStringList ViewLayerStandartItemModel::mimeTypes() const { QStringList list; list << "application/production"; // Ändern Sie den MIME-Typ nach Bedarf return list; } QMimeData *ViewLayerStandartItemModel::mimeData(const QModelIndexList &indexes) const { if(indexes.isEmpty()) return NULL; QMimeData *mimeData = new QMimeData; QByteArray encodedData; // Hier kannst du die Daten für das Drag-and-Drop speichern. // In diesem Beispiel codieren wir die Zeilen- und Spaltenindizes. QDataStream stream(&encodedData, QIODevice::WriteOnly); for (const QModelIndex &index : indexes) { if (index.isValid()) { int row = index.row(); int column = index.column(); stream << row << column; } } mimeData->setData("application/production", encodedData); return mimeData; } bool ViewLayerStandartItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if (!data || !data->hasFormat("application/production")) { qDebug() << "Wrong Data"; return false; } QByteArray encodedData = data->data("application/production"); QDataStream stream(&encodedData, QIODevice::ReadOnly); if (parent.isValid()) { // Wenn parent gültig ist, füge das Item als Kind des Parent-Items ein. while (!stream.atEnd()) { int sourceRow, sourceColumn; stream >> sourceRow >> sourceColumn; qDebug() << "SourceRow: " << sourceRow; if (row > parent.row()) { qDebug() << "Dropped below item at row: " << parent.row(); // Handle the drop below the parent item. } else if (row < parent.row()) { qDebug() << "Dropped above item at row: " << parent.row(); // Handle the drop above the parent item. } else { qDebug() << "Dropped on item at row: " << parent.row(); // Handle the drop on the parent item. } // You can also check the `column` value to determine the drop position in columns if needed. } return true; // Return true if the data was successfully processed. } // If parent is not valid, it means the item is dropped on an empty space in the view. qDebug() << "Dropped in empty space at row: " << row; return true; // Return true, wenn die Daten erfolgreich verarbeitet wurden. }
-
@StudentScripter Yeahhh i found the solution, i simply had to acceptProposedAction in the dragMoveEvent. :)
-
So i tried implementing all the methods and im able to drag and drop onto other items aswell as between the rows, but sadly in all those cases the dropped items just disappear, they just vanish. Why what could i do to fix this?
I want to support the reordering aswell as making items children of others by drag and drop.
Here is the code inside my standartitemmodel:
#include "ViewLayerStandartItemModel.h" #include "ViewLayerList.h" #include <QMimeData> #include <QDataStream> ViewLayerStandartItemModel::ViewLayerStandartItemModel(int rows, int columns, QObject *parent) : QStandardItemModel(rows, columns, parent) { } Qt::ItemFlags ViewLayerStandartItemModel::flags(const QModelIndex &index) const { Qt::ItemFlags defaultFlags = QStandardItemModel::flags(index); // Überprüfen Sie, ob der Index zu einem Ihrer speziellen Delegaten gehört if (!data(index, CanHaveChildrenRole).toBool()) { return (defaultFlags & ~Qt::ItemIsDropEnabled) | Qt::ItemIsDragEnabled; // Entfernen Sie das ItemIsDropEnabled-Flag und fügen Sie das ItemIsDragEnabled-Flag hinzu } return defaultFlags | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; // Fügen Sie das ItemIsDragEnabled und ItemIsDropEnabled Flag hinzu } Qt::DropActions ViewLayerStandartItemModel::supportedDragActions() const { return Qt::MoveAction | Qt::CopyAction; } Qt::DropActions ViewLayerStandartItemModel::supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } QStringList ViewLayerStandartItemModel::mimeTypes() const { QStringList list; list << "application/production"; // Ändern Sie den MIME-Typ nach Bedarf return list; } QMimeData *ViewLayerStandartItemModel::mimeData(const QModelIndexList &indexes) const { if(indexes.isEmpty()) return NULL; QMimeData *mimeData = new QMimeData; QByteArray encodedData; // Hier kannst du die Daten für das Drag-and-Drop speichern. // In diesem Beispiel codieren wir die Zeilen- und Spaltenindizes. QDataStream stream(&encodedData, QIODevice::WriteOnly); for (const QModelIndex &index : indexes) { if (index.isValid()) { int row = index.row(); int column = index.column(); stream << row << column; } } mimeData->setData("application/production", encodedData); return mimeData; } bool ViewLayerStandartItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if (!data || !data->hasFormat("application/production")) { qDebug() << "Wrong Data"; return false; } QByteArray encodedData = data->data("application/production"); QDataStream stream(&encodedData, QIODevice::ReadOnly); qDebug()<< "Wird gecalled"; while (!stream.atEnd()) { int sourceRow, sourceColumn; stream >> sourceRow >> sourceColumn; // Get the item that was dragged QStandardItem *item = this->item(sourceRow, sourceColumn); // Remove the item from its original position this->takeRow(sourceRow); // Insert the item at the new position this->insertRow(row, item); } return true; // Return true if the data was successfully processed. }
-
@StudentScripter takeRow removes a complete row of items, is that really what you want ?
-
@SGaist I only have 1 column so yes removing the entire row and reinserting it at the dropped postion should be fine. However the drop doesn't seem to work right. When i drop one item onto another the dropped item disapears but i do not get the usual dropdown arrow on the item that should be the new parent.
-
I've given up on the QTreeView a long time ago.
It's just rocket science to me and I really doubt the way it interacts the model is the simplest that it could be. It seems to be full of complications...
I found it's easier for me to write my own tree (view) widget that fits my use case than try to figure out the QTreeView.
In case you want to go this route here's my code (GPL 3.0)
https://github.com/ensisoft/detonator/blob/master/editor/gui/treewidget.cpp
-
@StudentScripter
I have not usedQTreeView
much or done drag and drop, but in your code I do not see anything which re-parents the moved item to whatever item it is dropped onto. Don't you have to do that?// Insert the item at the new position this->insertRow(row, item);
Do you not need to use something like bool QStandardItemModel::insertRow(int row, const QModelIndex &parent = QModelIndex()) to pass new parent?
Additionally/separately. The
row
you are passing here is the original one passed todropMimeData()
. However, you have preceded that withthis->takeRow(sourceRow);
. That deletes thesourceRow
row. Depending on where you drag from-to, do you have to be careful to maybe alter the value ofrow
ifsourceRow
came before it/is less than it? Perhaps only when they have the same parent, I don't know. -
-
@StudentScripter said in QTreeView how to support move action?:
@JonB well i gess i screw the approach of trying to do this in the model and instead handle all the moving and dropping in my qtreeview subclass directly.
I don't understand what you mean. Your code does stuff in
ViewLayerStandartItemModel
which isQStandardItemModel
. -
@JonB Yes but i can't get it to work by handling the methodes in the model directly. Instead i may gonna do this by accesing the model trough my QTreeView Drag and Drop Events. This seems way easier to me.
void ViewLayerList::dragMoveEvent(QDragMoveEvent *event) { QTreeView::dragMoveEvent(event); QModelIndex belowIndex; // Überprüfen Sie die Position des Drop-Indikators QAbstractItemView::DropIndicatorPosition position = dropIndicatorPosition(); // Bestimmen Sie das Aussehen des Drop-Indikators basierend auf seiner Position switch (position) { case QAbstractItemView::AboveItem: case QAbstractItemView::BelowItem: qDebug() << "Drop AboveItem or BelowItem"; DropOnItem = false; // Holen Sie sich den QModelIndex des Elements unterhalb der aktuellen Position belowIndex = indexAt(event->position().toPoint() + QPoint(0, 1)); if (belowIndex.isValid()) { // Holen Sie sich die Zeile des Elements RowBelowDropPositon = belowIndex.row(); qDebug() << "Row of item below: " << RowBelowDropPositon; } //AN AI HIER BEKOMME ICH IMMER EIN VERBOTEN SCHILD UND KANN ES NICHT DROPPEN event->acceptProposedAction(); break; case QAbstractItemView::OnItem: qDebug() << "Drop OnItem"; DropOnItem = true; break; case QAbstractItemView::OnViewport: qDebug() << "Drop OnViewport"; break; } } void ViewLayerList::dropEvent(QDropEvent *event) { if(DropOnItem == false){ } QTreeView::dropEvent(event); }
But i don't know if it has many downsides compared to doing this in the model directly. Thats what my question was to @SGaist