Qt QTreeView: Only allow to drop on an existing item
I've got a custom model inherited from QTreeView. I've enabled drag and drop and can currently drop an item onto the tree. However, you can currently drop onto either an existing item or between items. I would like to restrict this so that you can only drop onto existing items.
I've set DragDropOverwriteMode to true (actually this is the default for QTreeView). However, this doesn't stop you from dropping between items - it just means you can al开发者_运维技巧so drop onto existing items.
I know that I can ignore the "insert" drops in dropMimeData (which I am reimplementing), by checking whether row and column are valid (drops onto existing items have row and column set to -1 and parent set to the current item) and I am doing this. However, I would prefer not to get these drops. Ie. I would like it so that you are always dropping over either the item above or the item below, never between items.
Any ideas?
Thanks for any advice, Giles
You'' need to catch the drag enter events by reimplementing the dragEnterEvent
method in your custom view. The example from the Qt docs is:
void Window::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasFormat("text/plain"))
event->acceptProposedAction();
}
In your case, you would probably need to compare the x and y position in the event with the x and y position of the closest item or something similar and reject or accept the proposed action based on that information.
From the QAbstractItemModel::dropMimeData
documentation:
It is the responsibility of the view to provide a suitable location for where the data should be inserted.
Which I have interpreted to mean that the view should reject the drop if it's not something that's supported by an underlying model, such as yours.
As of Qt 5.4 (and I assume this was true even in Qt 4.8), setting DragDropOverwriteMode
to true
will correctly cause the drags to be droppable only on existing items and prevents the "above/below items" drop targets from appearing.
Also, unlike what the question claims, DragDropOverwriteMode
is set to false
by default for QTreeView
(I didn't check, maybe it's newer Qt versions), so it needs to be set to true
manually.
However it is still useful to be able to calculate on what positions the item can be dropped. For example in QTreeView, one cannot drop a dragged thing on the left margin of the items, i.e the red area below:
If something is dropped in the invalid red area, dropMimeData
will be called with a parent
argument set to NULL
. So it would be useful to ignore
the dragMoveEvent
in advance to show a 'you can't drop here' cursor to the user so they know they can't drop there. Qt doesn't implement changing the mouse cursor on invalid areas while dragging (as of Qt 5.4), but we can do it like this:
bool SubclassedTreeView::dropResultsInValidIndex(const QPoint& pos)
{
QTreeWidgetItem* item = itemAt(pos);
if (item == NULL || !indexFromItem(item).isValid())
return false;
return visualRect(indexFromItem(item)).adjusted(-1, -1, 1, 1).contains(pos, false);
}
virtual void SubclassedTreeView::dragMoveEvent(QDragMoveEvent* event)
{
QTreeWidget::dragMoveEvent(event);
if (!event->isAccepted())
return;
if (dropResultsInValidIndex(event->pos()))
event->accept();
else
event->ignore(); //Show 'forbidden' cursor.
}
virtual bool SubclassedTreeView::dropMimeData(QTreeWidgetItem* parent, int index, const QMimeData* data, Qt::DropAction action)
{
Q_UNUSED(index);
//Modify the following action and data format checks to suit your needs:
if (parent == NULL || action != Qt::CopyAction || !data->hasFormat("my/preferred-type"))
return false;
QModelIndex modelIndex = indexFromItem(parent);
//modelIndex is where the data is dropped onto. Implement your custom drop action here...
return true;
}
The above code contains a little part visualRect….adjusted(-1, -1, 1, 1)
which was stolen from QAbstractItemViewPrivate::position
sources. Actually the sources of this function can be used to calculate the overwrite/insert/invalid areas for the item when QAbstractItemViewPrivate::position
is false
too.
I would like to suggest a solution based on the current position of the drop indicator (QAbstractItemView::DropIndicatorPosition
). It's pretty simple to implement, but unfortunately requires the drop indicator to be shown. However, this might be acceptable in some cases.
TreeView::TreeView(QWidget* parent) : QTreeView(parent)
{
setDropIndicatorShown(true);
}
void TreeView::dragMoveEvent(QDragMoveEvent* event)
{
QTreeView::dragMoveEvent(event);
if (dropIndicatorPosition() != QTreeView::OnItem)
event->setDropAction(Qt::IgnoreAction);
}
精彩评论