QAbstractItemModel dataChanged() signal not updated TreeView



  • Hey all,

    I'm having an issue with the dataChanged() signal in QAbstractItemModel that is connected to QML TreeView. The data model updates itself independently of the view, so I would like to use the dataChanged() signal to notify the view that it has to repaint certain indices. I had much success using this approach with a ListView, but the TreeView is giving me some difficulties.

    This is the relevant section of my QAbstractItemModel subclass that is responsible for notifying the view that the model data has changed. (Again this works just fine for the ListView I'm using). I tried notifying the view through the layoutChanged() signal and this works, but is obviously far too costly for good performance.

    The dataChanged() signal is emitting from the refreshSpecific() function which is called from the handleTaskItemStatusChanged() function which is connected to a signal from the data item. That data item emits this signal when it's internal data has changed and the view should be updated. I'm expected to see my debug statements in the QAbstractItemModel data() function after dataChanged() is emitted, but I never do. I'm not quite sure where the dataChanged() signal is going?

    TaskTreeModel::TaskTreeModel(QObject *parent, TaskItemBare *rootTaskItem) :
        QAbstractItemModel(parent), rootTaskItem(rootTaskItem) {
    
        // Create role vector
        for (int i = TaskTreeRoles::nameRole; i < TaskTreeRoles::enableRole; i++) {
            roleVector << i;
        }
    }
    
    TaskTreeModel::~TaskTreeModel() {}
    
    QHash<int, QByteArray> TaskTreeModel::roleNames() const {
    
        QHash<int, QByteArray> roles;
    
        // Add role names
        roles[nameRole]             = "nameRole";
        roles[typeRole]             = "typeRole";
        roles[stateRole]            = "stateRole";
        roles[elapsedTimeRole]      = "elapsedTimeRole";
        roles[startTimeRole]        = "startTimeRole";
        roles[stopTimeRole]         = "stopTimeRole";
        roles[connectionStatusRole] = "connectionStatusRole";
        roles[enableRole]           = "enableRole";
    
        return roles;
    }
    
    // Return number of children at index (group tasks only)
    int TaskTreeModel::rowCount(const QModelIndex &parent) const {
    
        TaskItemBare *parentItem = NULL;
    
        if ((parentItem = getItem(parent)) != NULL) {
            if (parentItem->getTaskInfo()->getType() == Tasks::Type::Group) {
                return parentItem->getChildItemCount();
            }
        }
    
        return 0;
    }
    
    int TaskTreeModel::columnCount(const QModelIndex &/*index*/) const {
    
        return enableRole - nameRole;
    }
    
    TaskItemBare *TaskTreeModel::getItem(const QModelIndex &index) const {
    
        TaskItemBare *item = NULL;
    
        if (index.isValid()) {
            if ((item = static_cast<TaskItemBare*>(index.internalPointer())) != NULL) {
                return item;
            }
        }
    
        return rootTaskItem;
    }
    
    QModelIndex TaskTreeModel::index(int row, int column, const QModelIndex &parent) const {
    
        TaskItemBare *parentItem = NULL;
        TaskItemBare *childItem = NULL;
    
        if (parent.isValid() && parent.column() != 0) {
            return QModelIndex();
        }
    
        // Get parent item
        if ((parentItem = getItem(parent)) != NULL) {
    
            // Get child item
            if ((childItem = parentItem->getChildItem(row)) != NULL) {
                return createIndex(row, column, childItem);
            }
        }
    
        return QModelIndex();
    }
    
    QModelIndex TaskTreeModel::parent(const QModelIndex &index) const {
    
        TaskItemBare *parentItem = NULL;
        TaskItemBare *childItem = NULL;
    
        if (!index.isValid()) {
            return QModelIndex();
        }
    
        if (parentItem == rootTaskItem) {
            return QModelIndex();
        }
    
        if ((childItem = getItem(index)) != NULL) {
            if ((parentItem = childItem->getParentItem()) != NULL) {
                return createIndex(parentItem->getChildItemCount(), 0, parentItem);
            }
        }
    
        return QModelIndex();
    }
    
    QVariant TaskTreeModel::data(const QModelIndex &index, int role) const {
    
        TaskItemBare* childItem = NULL;
    
        qDebug() << "^^^ Data: Requested";
    
        if (!index.isValid()) {
    
            qDebug() << "^^^ Data: Index Invalid";
    
            return QVariant();
        }
    
        if ((childItem = getItem(index)) != NULL) {
    
            qDebug() << "^^^ Data Row:" << index.row() << "Role:" << role  << "Col:" << index.column() << "ID:" << childItem->getTaskInfo()->getID();
    
            switch(role) {
            case nameRole:
                return childItem->getTaskInfo()->getName();
            case typeRole:
                return Tasks::TypeNames.at(static_cast<int>(childItem->getTaskInfo()->getType()));
            case stateRole:
                return Tasks::StateNames.at(static_cast<int>(childItem->getTaskInfo()->getState()));
            case elapsedTimeRole:
                return childItem->getTaskInfo()->getElapsedTime();
            case startTimeRole:
                return childItem->getTaskInfo()->getStartTime();
            case stopTimeRole:
                return childItem->getTaskInfo()->getStopTime();
            case connectionStatusRole:
                return static_cast<int>(childItem->getTaskInfo()->getConnectionStatus());
            case enableRole:
                return childItem->getTaskInfo()->getEnable();
            default:
                return QVariant();
            }
        }
    
        qDebug() << "^^^ Data: Failed to get item";
    
        return QVariant();
    }
    
    bool TaskTreeModel::insertRows(int row, int count, const QModelIndex &parent) {
    
        // Check bounds
        if (row < 0 || count < 0) {
            return false;
        }
    
        if (count == 0) {
            return true;
        }
    
        if (row > rowCount()) {
            row = rowCount();
        }
    
        // Add rows doesn't change task item
        beginInsertRows(parent, row, row+count-1);
        endInsertRows();
    
        return true;
    }
    
    bool TaskTreeModel::removeRows(int row, int count, const QModelIndex &parent) {
    
        // Check bounds
        if (row < 0 || count < 0 || rowCount() <= 0) {
            return false;
        }
    
        if (count == 0) {
            return true;
        }
    
        if (row >= rowCount()) {
            row = rowCount() - 1;
        }
    
        // Remove rows doesn't change task item
        beginRemoveRows(parent, row, row+count-1);
        endRemoveRows();
    
        return true;
    }
    
    void TaskTreeModel::addTaskItem(TaskItem *taskItem) {
    
        TaskItemBare *parentItem = NULL;
        int parentIndex = 0;
    
        if (taskItem != NULL && taskItem != TaskItem::taskItemCast(rootTaskItem)) {
    
            // Get parent item and index
            if ((parentItem = taskItem->getParentItem()) != NULL) {
                parentIndex = parentItem->getIndex();
            }
    
            // Insert Rows
            insertRows(taskItem->getIndex(), 1, createIndex(parentIndex, 0, parentItem));
    
            // Connections
            connect(taskItem, SIGNAL(statusChangedSignal(int,StatusType)), this, SLOT(handleTaskItemStatusChanged(int,StatusType)));
        }
    }
    
    void TaskTreeModel::handleTaskItemStatusChanged(int index, StatusType statusType) {
    
        TaskItemBare *taskItem = NULL;
    
        // Get sender
        if ((taskItem = qobject_cast<TaskItemBare*>(sender())) != NULL) {
    
            // Update specific status
            switch(statusType) {
            case StatusType::State:
                refreshSpecific(index, taskItem, TaskTreeRoles::stateRole);
                break;
            default:
                break;
            }
        }
    }
    
    // Emits data changed for a specific row and role
    void TaskTreeModel::refreshSpecific(int row, TaskItemBare *taskItem, TaskTreeRoles role) {
    
        qDebug() << "@@@ Got refresh specific: Row:" << row << "Role:" << role  << "ID:" << taskItem->getTaskInfo()->getID();
    
        QModelIndex topLeft = createIndex(row, 0, taskItem);
        QModelIndex bottomRight = createIndex(row, 0, taskItem);
        QVector<int> roles = QVector<int>() << role;
    
    //    layoutChanged();
        dataChanged(topLeft, bottomRight, roles);
    }
    

    Here is the relevant section of my QML TreeView that shows the model is in fact emitting dataChanged() with the correct row and role arguments.

    import QtQuick 2.7
    import QtQuick.Controls 2.0 as Controls2
    import QtQuick.Controls 1.5
    import QtQuick.Controls.Styles 1.4
    import QtQuick.Layouts 1.3
    
    import "../Delegates"
    import "../Library"
    
    TreeView {
    
        id: root
        alternatingRowColors: true
        selectionMode: SelectionMode.NoSelection
        model: taskManagerContainer.getTaskTreeModel()
    
        property int iconColumnWidth: 25
    
        property color activeColor: "#55b7c8"
        property color activeTextColor: "white"
    
        Connections {
            target: model
            onDataChanged: {
                console.log("QML got data changed signal, row:", topLeft.row, "item:", topLeft.data, "roles:", roles)
            }
        }
    
        // Columns
        TableViewColumn {
            role: "nameRole"
            title: "Name"
            width: 200
            movable: false
            horizontalAlignment: Text.AlignLeft
        }
    
        TableViewColumn {
            role: "typeRole"
            title: "Type"
            width: 100
            movable: false
            horizontalAlignment: Text.AlignHCenter
        }
    
        TableViewColumn {
            role: "stateRole"
            title: "State"
            width: 100
            movable: false
            horizontalAlignment: Text.AlignHCenter
        }
    
        TableViewColumn {
            role: "elapsedTimeRole"
            title: "Elapsed Time"
            width: 100
            movable: false
            horizontalAlignment: Text.AlignHCenter
        }
    
        TableViewColumn {
            role: "startTimeRole"
            title: "Start Time"
            width: 150
            movable: false
            horizontalAlignment: Text.AlignHCenter
        }
    
        TableViewColumn {
            role: "stopTimeRole"
            title: "Stop Time"
            width: 150
            movable: false
            horizontalAlignment: Text.AlignHCenter
        }
    
        TableViewColumn {
            role: "connectionStatusRole"
            title: "Status"
            width: 100
            movable: false
            horizontalAlignment: Text.AlignHCenter
            delegate: Item {
                SquareLed {
                    anchors.centerIn: parent
                    height: iconColumnWidth - 3*2
                    width: iconColumnWidth - 3*2
                    connectionType: styleData.value
                }
            }
        }
    
        TableViewColumn {
            role: "enableRole"
            title: "Enable"
            width: 100
            movable: false
            horizontalAlignment: Text.AlignHCenter
            delegate: CustomCheckBox {
                anchors.centerIn: parent
                manualCheck: true
                checked: styleData.value
                onManualChecked: {
                    model.enableRole = newCheckstate
                }
            }
        }
    
        TableViewColumn {
            role: "stateRole"
            title: "Active"
            width: 100
            movable: false
            horizontalAlignment: Text.AlignHCenter
            delegate: Controls2.BusyIndicator {
                anchors.fill: parent
                anchors.margins: 5
                running: getActive(styleData.value) ? true : false
                visible: running
            }
        }
    
        // Helper Functions
        function getActive(value) {
    
            return value === "Running"
        }

  • Moderators

    @ZergedU Did you forget to emit the dataChanged signal ? I see you just called it like a normal function.

    void TaskTreeModel::refreshSpecific(int row, TaskItemBare *taskItem, TaskTreeRoles role) {
    
        qDebug() << "@@@ Got refresh specific: Row:" << row << "Role:" << role  << "ID:" << taskItem->getTaskInfo()->getID();
    
        QModelIndex topLeft = createIndex(row, 0, taskItem);
        QModelIndex bottomRight = createIndex(row, 0, taskItem);
        QVector<int> roles = QVector<int>() << role;
    
    //    layoutChanged();
        dataChanged(topLeft, bottomRight, roles);
    }
    


  • @p3c0 Thanks for the suggestion, but the "emit" keyword is just syntax sugar. So, calling a signal like a normal function is the same as using the "emit" keyword.



  • I found the issue. It was in my parent function. The correct parent function should be:

    QModelIndex TaskTreeModel::parent(const QModelIndex &index) const {
    
        TaskItemBare *parentItem = NULL;
        TaskItemBare *childItem = NULL;
    
        if (!index.isValid()) {
            return QModelIndex();
        }
    
        if (parentItem == rootTaskItem) {
            return QModelIndex();
        }
    
        if ((childItem = getItem(index)) != NULL) {
            if ((parentItem = childItem->getParentItem()) != NULL) {
                // this is wrong: 
                // return createIndex(parentItem->getChildItemCount(), 0, parentItem);
                // this is correct:
                return createIndex(parentItem->getIndex(), 0, parentItem);
            }
        }
    
        return QModelIndex();
    }
    

Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.