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

move qml treeview items



  • Hi all,
    I have QML TreeView with model derived from QAbstractItemModel, there is single column and I need to move items, up and down.
    My code works well when all parent items are collapsed, then I can drag up/down items no problem.
    collapsed.png
    Problem is when some view is expanded, then when I select item to move, only root items are moving and app crashes when moving item over opened branch
    expanded.png
    The question is: how do I check in QML that the item I'm moving is some child item and on cpp side moveRow should move child items of this branch?
    Code I'm using is below.

     TreeView {
            id:treeView
            anchors.fill:parent
    
            TableViewColumn {
                title: "Name"
                role: "name_role"
                width: treeView.width
            }
            model:TreeModel
            rowDelegate: Rectangle {
                id:rowDelegate
                height:50
            }
            itemDelegate: dragDelegate
    }
    Component {
            id: dragDelegate
    
            MouseArea {
                id: dragArea
                property alias item_idx:content.index
    
                property bool held: false
                height: content.height
    
                drag.target: held ? content : undefined
                drag.axis: Drag.YAxis
    
                onPressAndHold: held = true
                onReleased: held = false
    
                Rectangle {
                    id: content
                    property int index:model.index
                    anchors {
                        horizontalCenter: parent.horizontalCenter
                        verticalCenter: parent.verticalCenter
                    }
                    width: dragArea.width
                    height: 20
                    Text{
                        anchors.fill:parent
                        text: model.name_role
                        verticalAlignment: Text.AlignVCenter
                    }
    
                    border.width: 1
                    border.color: "lightsteelblue"
                    color: dragArea.held ?
                               "lightsteelblue" : "white"
                    Behavior on color { ColorAnimation { duration: 100 } }
                    radius: 2
                    Drag.active: dragArea.held
                    Drag.source: dragArea
                    Drag.hotSpot.x: width / 2
                    Drag.hotSpot.y: height / 2
                    states: State {
                        when: dragArea.held
    
                        ParentChange { target: content; parent: treeView }
                        AnchorChanges {
                            target: content
                            anchors { horizontalCenter: undefined; verticalCenter: undefined }
                        }
                    }
                }
                DropArea {
                    id:objectDropArea
                    anchors { fill: parent; margins: 10 }
    
                    onEntered: {
                        TreeModel.move(drag.source.item_idx,dragArea.item_idx)
                    }
                }
            }
     }
    cpp side:
    
    void TreeModel::move(int from, int to) {
        qDebug()<<"TreeModel::move from:"<<from<<" to:"<<to;
        int toSignal = to;
        if(from < to){
            toSignal += 1;
        }
    
        if(!beginMoveRows(QModelIndex(), from, from, QModelIndex(), toSignal)){
            return;
        }
        this->moveRow(QModelIndex(),from,QModelIndex(),toSignal);
        endMoveRows();
    }
    void TreeModel::setupModelData(const QStringList &lines, TreeItem *parent) {
        qDebug()<<"TreeModel::setupModelData parent row:"<<parent->row();
        QList<TreeItem*> parents;
        QList<int> indentations;
        parents << parent;
        indentations << 0;
    
        int number = 0;
    
        while (number < lines.count()) {
            int position = 0;
            while (position < lines[number].length()) {
                if (lines[number].at(position) != ' ')
                    break;
                position++;
            }
    
            QString lineData = lines[number].mid(position).trimmed();
    
            if (!lineData.isEmpty()) {
                // Read the column data from the rest of the line.
                QStringList columnStrings = lineData.split("\t", QString::SkipEmptyParts);
                QList<QVariant> columnData;
                for (int column = 0; column < columnStrings.count(); ++column)
                    columnData << columnStrings[column];
    
                if (position > indentations.last()) {
                    // The last child of the current parent is now the new parent
                    // unless the current parent has no children.
    
                    if (parents.last()->childCount() > 0) {
                        parents << parents.last()->child(parents.last()->childCount()-1);
                        indentations << position;
                    }
                } else {
                    while (position < indentations.last() && parents.count() > 0) {
                        parents.pop_back();
                        indentations.pop_back();
                    }
                }
    
                // Append a new item to the current parent's list of children.
                parents.last()->appendChild(new TreeItem(columnData, parents.last()));
            }
    
            ++number;
        }
    }
    

    Best,
    Marek



  • There has been some progress, If I pass from QML to cpp QModelIndex then I can change order of the child items

    void TreeModel::move(QModelIndex from, QModelIndex to) {
        qDebug()<<"TreeModel::move from:"<<from.row()<<" to:"<<to.row();
        if(to.row()<0)
            return;
        if(from.row()<0)
            return;
    
        int toSignal = to.row();
        if(from.row() < to.row()){
            toSignal += 1;
        }
    
        if(!beginMoveRows(from.parent(), from.row(), from.row(), to.parent(), toSignal)){
            return;
        }
        endMoveRows();
    }
    

    Now the problem is that change is not persisten, when I open branch, change order of the items, close branch, and open again - order is reversed, so I need to change cpp model.
    Second problem: if I move item from one branch to another, bad thing happens, crash
    Best,
    Marek



  • Anybody? Some help would be appreciated ;)

    This is based on this example:
    https://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html
    I have added some simple functions to TreeItem

    void TreeItem::move(int from, int to) {
        qDebug()<<"TreeItem::move from:"<<from<<" to:"<<to;
        m_childItems.move(from,to);
    }
    void TreeItem::remove(int pos) {
        qDebug()<<"TreeItem::remove pos:"<<pos;
        m_childItems.removeAt(pos);
    }
    void TreeItem::insert(int pos,TreeItem *child) {
        qDebug()<<"TreeItem::insert pos:"<<pos<<" child:"<<child<<" count:"<<m_childItems.count();
        m_childItems.insert(pos,child);
    }
    

    So now when I'm moving items with the same parent, all is well.
    When I'm moving item to change parent, something is wrong, like in model rowCount is not refreshed, or maybe I need to rebuild some persisten index ?
    This is my code now:

    void TreeModel::move(QModelIndex from, QModelIndex to) {
        qDebug()<<"TreeModel::move from:"<<from.row()<<" to:"<<to.row();
        if(to.row()<0)
            return;
        if(from.row()<0)
            return;
    
        int toSignal = to.row();
        if(from.row() < to.row()){
            toSignal += 1;
        }
    
        if(!beginMoveRows(from.parent(), from.row(), from.row(), to.parent(), toSignal)){
            return;
        }
    
        if(from.parent().isValid()) {
            if(from.parent()==to.parent()) {
                //move inside the same parent
                TreeItem *parent=static_cast<TreeItem*>(from.parent().internalPointer());
                parent->move(from.row(),to.row());
            }
            else {
                //change parent
                TreeItem *old_parent=static_cast<TreeItem*>(from.parent().internalPointer());
                TreeItem *new_parent=static_cast<TreeItem*>(to.parent().internalPointer());
                TreeItem *child=static_cast<TreeItem*>(from.internalPointer());
                if(!old_parent || !new_parent || !child)
                    return;
                qDebug()<<"old_parent:"<<old_parent<<" new_parent:"<<new_parent<<" child:"<<child;
                old_parent->remove(from.row());
                new_parent->insert(to.row(),child);
    
            }
        }
        qDebug()<<"item moved";
        endMoveRows();
    }
    

    result of moving first item (Creating a Dialog) from second branch to first branch, indenatation is wrong, and later on app crashes when I try to move again this item.

    change_parent.png

    Best,
    Marek



  • I have corrected move function a bit, it almost work ;) the problem is that some strange "artifact" is left afte move with change parent operation

    Here is the code:

    void TreeModel::move(QModelIndex from, QModelIndex to) {
        qDebug()<<"TreeModel::move from:"<<from.row()<<" to:"<<to.row();
    
        int toSignal = to.row();
        if(from.row() < to.row()){
            toSignal += 1;
        }
    
        if(from.parent().isValid()) {
            if(from.parent()==to.parent()) {
                //move inside the same parent
                qDebug()<<"TreeModel::move move inside parent";
                if(!beginMoveRows(from.parent(), from.row(), from.row(), to.parent(), toSignal)){
                    return;
                }
                TreeItem *parent=static_cast<TreeItem*>(from.parent().internalPointer());
                parent->move(from.row(),to.row());
                endMoveRows();
            }
            else {
                //change parent
    
                TreeItem *old_parent=static_cast<TreeItem*>(from.parent().internalPointer());
                TreeItem *new_parent=static_cast<TreeItem*>(to.parent().internalPointer());
                TreeItem *child=static_cast<TreeItem*>(from.internalPointer());
                if(!old_parent || !new_parent || !child)
                    return;
                qDebug()<<"old_parent:"<<old_parent<<" new_parent:"<<new_parent<<" child:"<<child;
    
                emit beginRemoveRows(from.parent(),from.row(),from.row());
                old_parent->remove(from.row());
                emit endRemoveRows();
    
                emit beginInsertRows(to.parent(),to.row(),to.row());
                child->setParent(new_parent);
                new_parent->insert(to.row(),child);
                emit endInsertRows();
            }
        }
        else {
            qDebug()<<"TreeModel::move parent invalid";
        }
    }
    

    Here is the result:
    change_parent2.png
    And some debug from this operation:

    TreeModel::move from: 0  to: 1
    old_parent: 0x55ef91136820  new_parent: 0x55ef911363d0  child: 0x55ef91136930
    TreeItem::remove pos: 0
    TreeItem::insert pos: 1  child: 0x55ef91136930  count: 3
    TreeModel::move from: -1  to: 1
    TreeModel::move parent invalid
    

    I was moving "Creating a Dialog" from first position in second branch to first branch, at the end there is some move operation with invalid parent index, I don't know where this comes from...

    Best,
    Marek


Log in to reply