Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Specialize Drag&Drop behaviour in QTreeWidget



  • I want to specialize the drop behaviour in a QTreeWidget in order to avoid user mistakes. I do have the following structure inside my QTreeWidget:

    1. Top-level items are called Maps. There can be one or more maps.
    2. Each map can have several layers as their children.
    3. Layers have a single child (describing the objects inside the layer) which does not participate in dragging and dropping.

    The following drag&drop actions make sense within the QTreeWidget:

    1. Maps can be rearranged in their order, but are always top-level items.
    2. Layers can be rearranged in their order within a map...
    3. or moved to a different map.
    4. Layers are not allowed to become top-level items.

    By turning on drag&drop on the QTreeWidget I can do everything I want to. However, there is some unwanted behaviour as well. Based on the old non-Qt version of the software users expect they can drop layers on top of each other and it will show up below the layer it is dropped on. Currently, when dropping one layer on top of the other it becomes a child of it.

    Is there an easy way to modify the behaviour of the QTreeWidget? I have seen QAbstractItemView::DropIndicatorPosition, but there is no way to influence it as far as I can tell. We already have a derived class from QTreeWidget. dropEvent(...) is also overloaded an mostly just forwards to its parent. Maybe modifying the QDropEvent inside this method somehow influences the drop behaviour? Maybe it is also possible when dropping a layer on to a map that it shows as the first layer of the map instead of the last?


  • Lifetime Qt Champion

    Hi
    I think you manually have to implement the dropping then.
    https://code.woboq.org/qt5/qtbase/src/widgets/itemviews/qtreewidget.cpp.html#3507



  • @mrjj I hoped that I could avoid navigating through that source code. Thanks anyway!


  • Lifetime Qt Champion

    @SimonSchroeder
    Well you can check the items being dropped on etc but as i recall it was not possible
    to avoid it becoming a child when dropped. But it was some years ago. Maybe i didn't try right way :)


  • Lifetime Qt Champion

    Hi,

    It might be simpler to re-implement dropMimeData.

    You can call the base class implementation by providing the adequate parent depending on what is being dropped and the current parent.



  • @SGaist said in Specialize Drag&Drop behaviour in QTreeWidget:

    It might be simpler to re-implement dropMimeData.

    My understanding is that dropMimeData just handles the data after it has been decided where the items should be put. It does not seem that with this function I could influence the position where the item will be inserted. Or is there something I am missing?



  • @SimonSchroeder
    You can do something like:

    bool NewTreeWidget::dropMimeData(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action)
    {
        if(QTreeWidget::dropMimeData(parent, index, data, action)) {  //call base function so you don't need to read QMimeData yourself
            QTreeWidgetItem *newItem = parent->child(index); //get the new item inserted by the base function
            if(some condition) //if parent is a layer and newItem is also a layer
            {
                newItem = parent->takeChild(index); //remove the new item from parent
                QTreeWidgetItem *grandParent = parent->parent();
                grandParent->insertChild(grandParent->indexOfChild(parent) + 1, newItem); //insert it to parent's parent
            }
            return true;
        }
        return false;
    }
    

    [ADDED]
    I've tested, when moving internal, dropMimeData won't be called.
    Maybe this doesn't suit you.
    But you can see that we can handle this when a layer item is inserted to a layer item.
    I think of reimplement rowsInserted:

    void NewTreeWidget::rowsInserted(const QModelIndex &parent, int start, int end)
    {
        QTreeWidget::rowsInserted(parent, start, end);//base function
        QTreeWidgetItem * parentItem = itemFromIndex(parent);//get parent item
        if(parentItem && parentItem is a layer)
        {
            for (int i = end; i >= start; i--) {
                QTreeWidgetItem *newItem = parentItem->child(i);
                if(newItem is also a layer) {
                    newItem = parentItem->takeChild(i); //remove the new item from parent
                    QTreeWidgetItem *grandParent = parentItem->parent();
                    grandParent->insertChild(grandParent->indexOfChild(parentItem) + 1, newItem); //insert it to parent's parent
                }
            }
        }
    }
    


  • Thank you, @Bonnie . Hijacking rowsInserted looks like an interesting idea. I will have a look at this approach when I have time for this again.


Log in to reply