QAbstractListModel updation issue after rows remove.
-
I have a TableView control in a QML file.
Created a mymodel class that is derived from QAbstractListModel and that model is bind with TableView model property.
There is a button in QML file for remove data from the model. When select a row then click on remove button. Then successfully remove the model data.But i remove the model item from another thread then a blank row appear in the removed place. What i do for that ?.
please see my current code is below :
Q_INVOKABLE void MyModel::deleteData(int rowIndex)
{
if(rowIndex <= m_DataList.count())
{
beginRemoveRows(QModelIndex(), rowIndex, rowIndex);
m_DataList.removeAt(rowIndex);
endRemoveRows();
}
} -
I have a TableView control in a QML file.
Created a mymodel class that is derived from QAbstractListModel and that model is bind with TableView model property.
There is a button in QML file for remove data from the model. When select a row then click on remove button. Then successfully remove the model data.But i remove the model item from another thread then a blank row appear in the removed place. What i do for that ?.
please see my current code is below :
Q_INVOKABLE void MyModel::deleteData(int rowIndex)
{
if(rowIndex <= m_DataList.count())
{
beginRemoveRows(QModelIndex(), rowIndex, rowIndex);
m_DataList.removeAt(rowIndex);
endRemoveRows();
}
}@Remya said in QAbstractListModel updation issue after rows remove.:
But i remove the model item from another thread then a blank row appear in the removed place. What i do for that ?.
This is not supported. All model actions have to be done in the UI thread.
-
Hi and welcome to devnet,
How are you removing the data from that other thread ?
-
It should not create blank row. This indicate that your model still has blank row with it.
-
Thank you all for the kind replays.
Please see my code below,main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QAbstractTableModel>
#include <QtQml/QQmlContext>
#include <qdebug.h>
#include <QThread>class TableModel : public QAbstractTableModel
{
Q_OBJECT
QList <QString> m_List;public:
TableModel()
{
for(int index =0; index < 100 ; index++)
{
insertValue(QString::number(index));
}
}int rowCount(const QModelIndex & = QModelIndex()) const override { return m_List.count(); } int columnCount(const QModelIndex & = QModelIndex()) const override { return m_List.count(); } QVariant data(const QModelIndex &index, int role) const override { switch (role) { case Qt::DisplayRole: { qDebug()<< QString("%1").arg(m_List[index.row()]); return QString("%1").arg(m_List[index.row()]); } default: break; } return QVariant(); } void insertValue(QString value) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_List.append(value); endInsertRows(); } void deleteData(int rowIndex) { if (rowIndex <= this->m_List.count()) { this->beginRemoveRows(QModelIndex(), rowIndex, rowIndex); this->m_List.removeAt(rowIndex); this->endRemoveRows(); } }
QHash<int, QByteArray> roleNames() const override
{
return { {Qt::DisplayRole, "display"} };
}
};class ThreadHandler : public QObject
{
Q_OBJECT
TableModel* m_pTableModel;
int m_Index;public:
void setTableMOdel(TableModel* pTableModel)
{
m_pTableModel = pTableModel;
}void setIndex(int index) { m_Index = index; }
public slots:
void execute()
{
m_pTableModel->deleteData(m_Index);
}};
class ButtonOperation : public QObject
{
Q_OBJECT
TableModel* m_pTableModel;public:
void setTableModel(TableModel* tableModel) { m_pTableModel = tableModel; } void run() { qDebug() << " THread started "; } Q_INVOKABLE void RemoveRows(int index) { QThread* pPThread = new QThread(); ThreadHandler* pHandler = new ThreadHandler(); pHandler->setIndex(index); pHandler->setTableMOdel(m_pTableModel); pHandler->moveToThread(pPThread); connect(pPThread, SIGNAL(started()), pHandler, SLOT(execute()), Qt::DirectConnection); connect(this, SIGNAL(quitThread()), pPThread, SLOT(quit()), Qt::DirectConnection); // Automatically delete pPThread and pHandler after the work is done. connect(pPThread, SIGNAL(finished()), pPThread, SLOT(deleteLater()), Qt::DirectConnection); connect(pPThread, SIGNAL(finished()), pHandler, SLOT(deleteLater()), Qt::DirectConnection); pPThread->start(); }
};
int main(int argc, char argv[])
{
QGuiApplication app(argc, argv);
TableModel pTableModel = new TableModel();
QQmlApplicationEngine engine;
QQmlContext* context = engine.rootContext();
context->setContextProperty("TableModel", pTableModel);
ButtonOperation* pButtonOperation = new ButtonOperation();
pButtonOperation->setTableModel(pTableModel);
context->setContextProperty("ButtonOperation", pButtonOperation);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}#include "main.moc"
######################################################main.qml
import QtQuick 2.4
import QtQuick.Controls 1.2ApplicationWindow {
property int currentIndex: 0
id: window
visible: true
title: "Table View Example"
width: 600
height: 800TableView { id: tableView width: 400 height: 500 anchors.centerIn: parent clip: true model: TableModel TableViewColumn { role: "display" title: "Title" width: 180 resizable: false movable: false delegate: Item { anchors.fill: parent Text { text: styleData.value verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft elide: Text.ElideRight width: 160 height: parent.height x:8 } } } selection.onSelectionChanged: { currentIndex = tableView.currentRow; console.log("selected Row " + currentIndex) } } Button{ id: button width: 100 height: 30 text: "Remove Row" onClicked: { console.log("Button Clicked") ButtonOperation.RemoveRows(currentIndex); } }
}
###################################################### -
You are using the QAbstractTableModel. You can use the signal & slots to update. Refer GIT
-
Did your program compile ? Also when you run your program did you see any errors/warning on the application output ?
-
Did your program compile ? Also when you run your program did you see any errors/warning on the application output ?
I am using QT Version : 5.5.1 in Linux platform (Ubuntu 16.04 LTS).
IDE : Qt Creator 4.4.1
Please see the below code I am able to successfully compile and run.//#################### TestAppModel.pro ##################################
QT += quick
CONFIG += c++11The following define makes your compiler emit warnings if you use
any feature of Qt which as been marked deprecated (the exact warnings
depend on your compiler). Please consult the documentation of the
deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
You can also make your code fail to compile if you use deprecated APIs.
In order to do so, uncomment the following line.
You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += main.cpp
RESOURCES += qml.qrc
Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =
Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += targetHEADERS +=
//#################### main.cpp ##################################
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QAbstractTableModel>
#include <QtQml/QQmlContext>
#include <qdebug.h>
#include <QThread>class TableModel : public QAbstractTableModel
{
Q_OBJECT
QList <QString> m_List;public:
TableModel()
{
for(int index =0; index < 100 ; index++)
{
insertValue(QString::number(index));
}
}Q_INVOKABLE int rowCount(const QModelIndex & = QModelIndex()) const override { return m_List.count(); } Q_INVOKABLE int columnCount(const QModelIndex & = QModelIndex()) const override { return m_List.count(); } Q_INVOKABLE QVariant data(const QModelIndex &index, int role) const override { switch (role) { case Qt::DisplayRole: { qDebug()<< QString("%1").arg(m_List[index.row()]); return QString("%1").arg(m_List[index.row()]); } default: break; } return QVariant(); } Q_INVOKABLE void insertValue(QString value) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_List.append(value); endInsertRows(); } Q_INVOKABLE void deleteData(int rowIndex) { if (rowIndex <= this->m_List.count()) { this->beginRemoveRows(QModelIndex(), rowIndex, rowIndex); this->m_List.removeAt(rowIndex); this->endRemoveRows(); emitDataChanged(); } }
QHash<int, QByteArray> roleNames() const override
{
return { {Qt::DisplayRole, "display"} };
}QVector<int> getRoles() const
{
QVector<int> roles;
roles.append(Qt::DisplayRole);
return roles;
}void emitDataChanged()
{
QModelIndex start = index(0,0);
QModelIndex end = index(rowCount() - 1, 0);
emit dataChanged(start, end, getRoles());
}};
class ThreadHandler : public QObject
{
Q_OBJECT
TableModel* m_pTableModel;
int m_Index;public:
void setTableMOdel(TableModel* pTableModel)
{
m_pTableModel = pTableModel;
}void setIndex(int index) { m_Index = index; }
public slots:
void execute()
{
m_pTableModel->deleteData(m_Index);
}};
class ButtonOperation : public QObject
{
Q_OBJECT
TableModel* m_pTableModel;public:
void setTableModel(TableModel* tableModel) { m_pTableModel = tableModel; } void run() { qDebug() << " THread started "; } Q_INVOKABLE void RemoveRows(int index) { QThread* pPThread = new QThread(); ThreadHandler* pHandler = new ThreadHandler(); pHandler->setIndex(index); pHandler->setTableMOdel(m_pTableModel); pHandler->moveToThread(pPThread); connect(pPThread, SIGNAL(started()), pHandler, SLOT(execute()), Qt::DirectConnection); connect(this, SIGNAL(quitThread()), pPThread, SLOT(quit()), Qt::DirectConnection); // Automatically delete pPThread and pHandler after the work is done. connect(pPThread, SIGNAL(finished()), pPThread, SLOT(deleteLater()), Qt::DirectConnection); connect(pPThread, SIGNAL(finished()), pHandler, SLOT(deleteLater()), Qt::DirectConnection); pPThread->start(); }
};
int main(int argc, char argv[])
{
QGuiApplication app(argc, argv);
TableModel pTableModel = new TableModel();
QQmlApplicationEngine engine;
QQmlContext* context = engine.rootContext();
context->setContextProperty("TableModel", pTableModel);
ButtonOperation* pButtonOperation = new ButtonOperation();
pButtonOperation->setTableModel(pTableModel);
context->setContextProperty("ButtonOperation", pButtonOperation);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}#include "main.moc"
//#################### main.qml ##################################
import QtQuick 2.4
import QtQuick.Controls 1.2ApplicationWindow {
property int currentIndex: 0
id: window
visible: true
title: "Table View Example"
width: 600
height: 800TableView { id: tableView width: 400 height: 500 anchors.centerIn: parent clip: true model: TableModel TableViewColumn { role: "display" title: "Title" width: 180 resizable: false movable: false delegate: Item { anchors.fill: parent Text { text: styleData.value verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft elide: Text.ElideRight width: 160 height: parent.height x:8 } } } selection.onSelectionChanged: { currentIndex = tableView.currentRow; console.log("selected Row " + currentIndex) } } Button{ id: button width: 100 height: 30 text: "Remove Row" onClicked: { console.log("Button Clicked") ButtonOperation.RemoveRows(currentIndex); } }
}
[0_1547714625948_TestAppModel.zip](Uploading 100%) -
I'm surprised. Following line in main.cpp compiles ?
TableModel pTableModel = new TableModel();
Now,
When you select the element & click on "Remove Row", what is the output you see in "Application Window" ? Do you see any warning ? -
The way you re-implemented the run method breaks the worker objects paradigm. The idea is to have an event loop running which is not the case anymore, so call the base class implementation after your
qDebug
statement or delete the method completely.Next question, why go that convoluted way to delete one row ?
-
I'm surprised. Following line in main.cpp compiles ?
TableModel pTableModel = new TableModel();
Now,
When you select the element & click on "Remove Row", what is the output you see in "Application Window" ? Do you see any warning ?@dheerendra
Thanks for your replay.
Yes i get two warnings please see below,-
QObject::connect: No such signal ThreadHandler::quitThread() in ../TestAppModel/main.cpp:148 // This part forgot to declare signal quitThread() in sample application.
-
QObject::connect: Cannot queue arguments of type 'QQmlChangeSet'
(Make sure 'QQmlChangeSet' is registered using qRegisterMetaType().)
-
-
This is where everything going for haywire. You are trying to push the changes from different thread. Due to this you are View is not updated properly. You have two options.
- Don't update the model directly in another thread. If you want to do it use signals/slots to update the model from different thread.
- As @SGaist already asked you, why you are doing round-way of updating/deleting just a row of elements of model. In your case just use the single threaded approach.
Comment everything in RemoveRows(..) method. Place the following code & see it works or not. This is single threaded approach.
ThreadHandler* pHandler = new ThreadHandler(); pHandler->setIndex(index); pHandler->setTableMOdel(m_pTableModel); pHandler->execute();
-
The way you re-implemented the run method breaks the worker objects paradigm. The idea is to have an event loop running which is not the case anymore, so call the base class implementation after your
qDebug
statement or delete the method completely.Next question, why go that convoluted way to delete one row ?
@SGaist said in QAbstractListModel updation issue after rows remove.:
convoluted
Thanks for the comments.
Yes i removed the run() method complete.
Actually i need to delete row from the tableview using a thread.
Is there any better method for that ? -
@Remya said in QAbstractListModel updation issue after rows remove.:
Actually i need to delete row from the tableview using a thread.
Again: why ?
A better way ? Don't use threads unless you have a very good reason (and "I need to" doesn't count as a good reason until explained properly)
-
This is where everything going for haywire. You are trying to push the changes from different thread. Due to this you are View is not updated properly. You have two options.
- Don't update the model directly in another thread. If you want to do it use signals/slots to update the model from different thread.
- As @SGaist already asked you, why you are doing round-way of updating/deleting just a row of elements of model. In your case just use the single threaded approach.
Comment everything in RemoveRows(..) method. Place the following code & see it works or not. This is single threaded approach.
ThreadHandler* pHandler = new ThreadHandler(); pHandler->setIndex(index); pHandler->setTableMOdel(m_pTableModel); pHandler->execute();
@dheerendra
Thanks a lot.
I did signal/slot updation from different thread.
Works fine for me. -
Cool. Still we wonder why update from threads unless you strong requirement. It could be some experiment as well. Also we need to look at the warning, errors which come when we run the application. It was clearly telling the issue.
Now it is working & made it move the issue to solved state. You can share your example also so that others benefit from the same.
-
@Remya said in QAbstractListModel updation issue after rows remove.:
Actually i need to delete row from the tableview using a thread.
Again: why ?
A better way ? Don't use threads unless you have a very good reason (and "I need to" doesn't count as a good reason until explained properly)
-
Please move the issue to solved state as well.
-
Please see the sample below. This works !!
//###############################Fiile : main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QAbstractTableModel>
#include <QtQml/QQmlContext>
#include <qdebug.h>
#include <QThread>class TableModel : public QAbstractTableModel
{
Q_OBJECT
QList <QString> m_List;public:
TableModel() { for(int index =0; index < 100 ; index++) { insertValue(QString::number(index)); } } Q_INVOKABLE int rowCount(const QModelIndex & = QModelIndex()) const override { return m_List.count(); } Q_INVOKABLE int columnCount(const QModelIndex & = QModelIndex()) const override { return m_List.count(); } Q_INVOKABLE QVariant data(const QModelIndex &index, int role) const override { switch (role) { case Qt::DisplayRole: { return QString("%1").arg(m_List[index.row()]); } default: break; } return QVariant(); } Q_INVOKABLE void insertValue(QString value) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_List.append(value); endInsertRows(); } Q_INVOKABLE void deleteData(int rowIndex) { if (rowIndex <= this->m_List.count()) { this->beginRemoveRows(QModelIndex(), rowIndex, rowIndex); this->m_List.removeAt(rowIndex); this->endRemoveRows(); emitDataChanged(); } } void emitDeleteRow(int rowIndex) { emit removeRow(rowIndex); }
QHash<int, QByteArray> roleNames() const override
{
return { {Qt::DisplayRole, "display"} };
}QVector<int> getRoles() const
{
QVector<int> roles;
roles.append(Qt::DisplayRole);
return roles;
}void emitDataChanged()
{
QModelIndex start = index(0,0);
QModelIndex end = index(rowCount() - 1, 0);
emit dataChanged(start, end, getRoles());
}signals:
void removeRow(int rowIndex);};
class ThreadHandler : public QObject
{
Q_OBJECT
TableModel* m_pTableModel;
int m_Index;
signals:
void quitThread();public:
void setTableMOdel(TableModel* pTableModel)
{
m_pTableModel = pTableModel;
}void setIndex(int index) { m_Index = index; }
public slots:
void execute()
{
m_pTableModel->emitDeleteRow(m_Index);
emit quitThread();
}};
class ButtonOperation : public QObject
{
Q_OBJECT
TableModel* m_pTableModel;public:
void setTableModel(TableModel* tableModel) { m_pTableModel = tableModel; } Q_INVOKABLE void RemoveRows(int index) { QThread* pPThread = new QThread(); ThreadHandler* pHandler = new ThreadHandler(); pHandler->setIndex(index); pHandler->setTableMOdel(m_pTableModel); pHandler->moveToThread(pPThread); connect(pPThread, SIGNAL(started()), pHandler, SLOT(execute()), Qt::DirectConnection); connect(pHandler, SIGNAL(quitThread()), pPThread, SLOT(quit()), Qt::DirectConnection); // Automatically delete pPThread and pHandler after the work is done. connect(pPThread, SIGNAL(finished()), pPThread, SLOT(deleteLater()), Qt::DirectConnection); connect(pPThread, SIGNAL(finished()), pHandler, SLOT(deleteLater()), Qt::DirectConnection); pPThread->start(); }
};
int main(int argc, char argv[])
{
QGuiApplication app(argc, argv);
TableModel pTableModel = new TableModel();
QQmlApplicationEngine engine;
QQmlContext* context = engine.rootContext();
context->setContextProperty("TableModel", pTableModel);
ButtonOperation* pButtonOperation = new ButtonOperation();
pButtonOperation->setTableModel(pTableModel);
context->setContextProperty("ButtonOperation", pButtonOperation);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}#include "main.moc"
///##############################File main.qml
import QtQuick 2.4
import QtQuick.Controls 1.2ApplicationWindow {
property int currentIndex: 0
id: window
visible: true
title: "Table View Example"
width: 600
height: 800TableView { id: tableView width: 400 height: 500 anchors.centerIn: parent clip: true model: TableModel TableViewColumn { role: "display" title: "Title" width: 180 resizable: false movable: false delegate: Item { anchors.fill: parent Text { text: styleData.value verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft elide: Text.ElideRight width: 160 height: parent.height x:8 } } } selection.onSelectionChanged: { currentIndex = tableView.currentRow; console.log("selected Row " + currentIndex) } } Connections{ target: TableModel onRemoveRow: { TableModel.deleteData(rowIndex); } } Button{ id: button width: 100 height: 30 text: "Remove Row" onClicked: { console.log("Button Clicked") ButtonOperation.RemoveRows(currentIndex); } }
}
///######################## TestAppModel.pro
QT += quick
CONFIG += c++11DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += main.cpp
RESOURCES += qml.qrc
QML_IMPORT_PATH =
QML_DESIGNER_IMPORT_PATH =
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += targetHEADERS +=
Thank you all.