Solved 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" }
-
@ZergedU Did you forget to
emit
thedataChanged
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(); }