Automatically Refreshing a Qtableview When Data Changed

Automatically refreshing a QTableView when data changed

I actually found the problem, which was that my other view was not properly notified of the data changes: my views each showed different portions of my data, so the other views needed to be notified of the dataChanged(), but for their own, proper, indices.

On a side note, I also had the problem of updating my views while my Qt application was not the active window in my window manager. The solution was to call repaint() on the main window.

Updating view based on model update on Qt

I got help from Qt forum and solved it.

How to force refresh of changed cells with Qt Custom TableModel (deriving from QAbstractTableModel) ?

Qt versions changes all the time. Changing from one version to the other is trivial. Planning your project is very important. Qt Widgets that you choose for your user interface (ui) are import. Qt Widgets transmits signals, you can connect to those signals by using slots. Depending on what you want to do with your project. When implementing some of the functions, it is important to call the appropriate functions so that all connected views are aware of any changes. QAbstractTableModel may be useful for developers who want to implement their own table models or custom delegates. There is a theory behind the MVC (Model, View and Controller). Implementing MVC will solve the problem. There are many strategies you can use to implement it. The starting point is planning.
You can use the class DataSource and class MyTableModel as Models. What is missing is the definition of the Views and Controllers. Your “#include”s are also importent in Qt. QAbstractTableModel provides the standard MV API to access data. We can use QtableView to view data using different views. The basic functions that must be implemented are: rowCount(), columnCount(), data(), headerData(). For TableModel to be editable, it has to provide implementations insertRows(), removeRows(), setData() and flags() functions.


  1. Demo Custom Table.

enter image description here



  1. main.cpp

//
#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();

return a.exec();
}
//


  1. widget.h

//
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTableView>
#include <QListView>
#include <QLabel>
#include <QLineEdit>
#include <QTextBrowser>
#include <QDateEdit>
#include <QString>
//namespace Ui {
//class Widget;
//}
class MyTableModel;
class Widget : public QWidget
{
Q_OBJECT
public:
//explicit Widget(QObject t, QWidget *parent = 0);
Widget(QWidget *parent = 0);
~Widget();
public slots:
void updateView();
private:
//Ui::Widget *ui;
QTableView *tableView;
MyTableModel *tableModel;
QLineEdit *ledMessage;
QLabel *lblMessage;
QTextBrowser *logs;
QDateEdit *dateEdit;
};
#endif // WIDGET_H
//


  1. widget.cpp

//
#include <QtGui>
#include "mytablemodel.h"
#include "widget.h"
//#include "ui_widget.h"
#include <QGridLayout>

Widget::Widget(QWidget *parent) :
QWidget(parent)/*,
ui(new Ui::Widget*)*/
{
//ui->setupUi(this);
tableModel = new MyTableModel(this);
tableView = new QTableView;
tableView->setModel(tableModel);
lblMessage = new QLabel(tr("&Message:"));
ledMessage = new QLineEdit;
lblMessage->setBuddy(ledMessage);
dateEdit = new QDateEdit(QDate::currentDate());
logs = new QTextBrowser;
logs->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred));

QGridLayout *layout = new QGridLayout;
layout->addWidget(lblMessage, 0, 0);
layout->addWidget(ledMessage, 0, 1);
layout->addWidget(dateEdit, 0, 2);
layout->addWidget(tableView, 1, 0, 1, 2);
layout->addWidget(logs, 2, 0, 1, 2);

setLayout(layout);
setWindowTitle(tr("Demo Custom Table."));
//connect(ledMessage, SIGNAL(textChanged(const QString &)),
// tableModel, SLOT(newMessage(const QString &)));
//connect(tableModel, SIGNAL(updateMode()),
// this, SLOT(updateView()));
connect(ledMessage, SIGNAL(editingFinished()), this, SLOT(updateView()));
}

Widget::~Widget()
{
//delete ui;
}

void Widget::updateView()
{
QModelIndex p = tableView->currentIndex();
tableModel->update(p,ledMessage->text(), dateEdit->text());
logs->append(tr("%1 : ").arg(p.row()));
logs->append(ledMessage->text());
}
//


  1. mytablemodel.h

//
#ifndef MYTABLEMODEL_H
#define MYTABLEMODEL_H

#include <QAbstractTableModel>
#include <QLabel>
//#include <QDateEdit>
#include <QDateTime>
#include "datasource.h"

class MyTableModel : public QAbstractTableModel
{
Q_OBJECT
public:
//explicit MyTableModel(QObject t, QObject t2,QObject *parent = 0);
MyTableModel(QObject *parent = 0);
MyTableModel(QList< QPair<QString,QString> > t, QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
QVariant headerData(int part, Qt::Orientation orient, int role) const;
void update(const QModelIndex &parent,const QString &mes, const QString &date);
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole);
Qt::ItemFlags flags(const QModelIndex &index) const;
bool insertRows(int position, int rows, const QModelIndex &index=QModelIndex());
bool removeRows(int position, int rows, const QModelIndex &index=QModelIndex());

signals:
void updateMod();

public slots:
void newMessage(const QString &mes);

protected:
void update(const QModelIndex &parent);

private:
DataSource *datasource;
QPair<QString,QString> message;
QList< QPair<QString,QString> > messageList;
QList<QString> dList;
int listSize;
int col;
QLabel *lblNumber;
QDateTime *dTime;
//QDateEdit *dateEdit;
};

#endif // MYTABLEMODEL_H
//


  1. mytablemodel.cpp

//    
#include "mytablemodel.h"
#include <QApplication>

MyTableModel::MyTableModel(QObject *parent) :
QAbstractTableModel(parent)
{
datasource = new DataSource();
lblNumber = new QLabel;
dTime = new QDateTime();
//dateEdit = new QDateEdit(QDate::currentDate());
col = datasource->nbColumns();
int l = datasource->size();
for(int i = 0; i < l; i++)
{
message.first = datasource->data(i,0);
message.second = datasource->data(i,1);
messageList.append(message);
dList.append(dTime->currentDateTime().toString());
}
listSize = messageList.size();

}

MyTableModel::MyTableModel(QList< QPair<QString,QString> > t, QObject *parent) :
QAbstractTableModel(parent)
{
messageList = t;
datasource = new DataSource();
listSize = t.length();
col = datasource->nbColumns();
}

void MyTableModel::update(const QModelIndex &,const QString &mes, const QString &d)
{
int first = messageList.count();
int last = first + 1;
first = last;
lblNumber->setText(tr("%1").arg(first));
beginInsertRows(QModelIndex(),first,last);
message.first = lblNumber->text();
message.second = mes;
messageList.append(message);
dList.append(d);
listSize++;
endInsertRows();
datasource->hasChanges();
}

int MyTableModel::rowCount(const QModelIndex &p) const
{
Q_UNUSED(p);
return listSize;
}

int MyTableModel::columnCount(const QModelIndex &p) const
{
Q_UNUSED(p);
return col;
}

QVariant MyTableModel::data(const QModelIndex &index, int role) const
{
int c = 0;
if (!index.isValid())
{
return QVariant();
}
if(index.row() < 0)
{
return QVariant();
}
if (role == Qt::DisplayRole)
{
c = index.column();
if(c == 0)
{
return messageList.at(index.row()).first;
}
else if (c == 1)
{
return messageList.at(index.row()).second;
}

return dList.at(index.row());
}

return QVariant();
}

void MyTableModel::update(const QModelIndex &p)
{
Q_UNUSED(p);
emit updateMod();
}

void MyTableModel::newMessage(const QString &mes)
{
listSize++;
message.first = lblNumber->text();
message.second = mes;
messageList.append(message);
dList.append(dTime->currentDateTime().toString());
}

bool MyTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
QString d = dTime->currentDateTime().toString();
if (index.isValid() && role == Qt::EditRole)
{
int row = index.row();

message = messageList.value(row);
if (index.column() == 0)
message.first = value.toString();
else if (index.column() == 1)
message.second = value.toString();
else
return false;

messageList.replace(row, message);
dList.replace(row,d);
emit(dataChanged(index, index));

return true;
}

return false;
}

bool MyTableModel::insertRows(int post, int rows, const QModelIndex &p)
{
Q_UNUSED(p);
beginInsertRows(QModelIndex(), post, post+rows-1);
for (int row=0; row < rows; row++)
{
message.first = " ";
message.second = " ";
messageList.insert(post, message);
dList.insert(post, " ");
}
endInsertRows();
return true;
}

bool MyTableModel::removeRows(int post, int rows, const QModelIndex &p)
{
Q_UNUSED(p);
beginRemoveRows(QModelIndex(), post, post+rows-1);
for (int row=0; row < rows; ++row)
{
messageList.removeAt(post);
dList.removeAt(post);
}
endRemoveRows();
return true;
}

Qt::ItemFlags MyTableModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::ItemIsEnabled;

return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
}

QVariant MyTableModel::headerData(int part, Qt::Orientation orient, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();

if (orient == Qt::Horizontal)
{
switch (part)
{
case 0:
return tr("Number");

case 1:
return tr("Message");

case 2:
return tr("Date");

default:
return QVariant();
}
}
return QVariant();
}
//


  1. datasource.h

//
#include <QString>
#include <QVariant>
#include <QList>
#include <QPair>

class DataSource
{
public:
DataSource();
int size();
int nbColumns();
const QString& data(int row, int col);
bool hasChanges();

private:
int beginRow;
int columns;
QPair<QString,QString> message;
QList< QPair<QString,QString> > messages;
};

#endif // DATASOURCE_H
//


  1. datasource.cpp

//
#include "datasource.h"

DataSource::DataSource()
{
beginRow = 1;
columns = 3;
message.first = "One";
message.second = "Testing123";
messages.push_back(message);
message.first = "Two";
message.second = "QObject";
messages.push_back(message);
message.first = "Three";
message.second = "QWidget";
messages.push_back(message);
}

int DataSource::size()
{
return messages.length();
}

int DataSource::nbColumns()
{
return columns;
}

const QString& DataSource::data(int r, int c)
{
int l = messages.length();
if(r < l)
{
message = messages.at(r);
if (c == 0)
{
return message.first;
}
else if (c == 1)
{
return message.second;
}
}
return "Testing123";
}


bool DataSource::hasChanges()
{
beginRow++;
return true;
}
//


  1. customtab.pro

//
#-------------------------------------------------
#
# Project created by QtCreator 2018-09-10T15:39:01
#
#-------------------------------------------------

QT += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = customtab
TEMPLATE = app


SOURCES += main.cpp\
widget.cpp \
datasource.cpp \
mytablemodel.cpp

HEADERS += widget.h \
datasource.h \
mytablemodel.h

#//FORMS += widget.ui
//


  1. ENJOY.

If you are new to Qt. Qt uses C++. It implements the GUI. Most C++ applications uses console applications. Qt uses design patterns and frameworks. Enjoy!


How to refresh QTableView when it is driven by model

For this purpose, you can use the QSortFilterProxyModel class. This way, we don't tamper with the actual source model's structure or it's data. We just map the main source to this proxy model, which the view uses to display filtered/sorted data. We can affect the data in the proxy model as we please, without the risk of tampering the source model.

Here is your source code modified to use this:

import sys, os
from PyQt4 import QtCore, QtGui
app=QtGui.QApplication(sys.argv)

class TableModel(QtCore.QAbstractTableModel):
def __init__(self):
QtCore.QAbstractTableModel.__init__(self)
self.items=['One','Two','Three','Four','Five','Six','Seven']

def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.items)
def columnCount(self, index=QtCore.QModelIndex()):
return 1

def data(self, index, role):
if not index.isValid() or not (0<=index.row()<len(self.items)):
return QtCore.QVariant()

item=str(self.items[index.row()])

if role==QtCore.Qt.DisplayRole:
return item
else:
return QtCore.QVariant()


class MySortFilterProxyModel(QtGui.QSortFilterProxyModel):
def __init__(self):
super(MySortFilterProxyModel, self).__init__()
self.cb_status=True

def cbChanged(self, arg=None):
self.cb_status=arg
print self.cb_status
self.invalidateFilter()

def filterAcceptsRow(self, sourceRow, sourceParent):
print_when_odd_flag = self.cb_status
is_odd = True
index = self.sourceModel().index(sourceRow, 0, sourceParent)
print "My Row Data: %s" % self.sourceModel().data(index, role=QtCore.Qt.DisplayRole)

if (sourceRow + 1) % 2 == 0:
is_odd = False

if print_when_odd_flag:
if is_odd:
return True
else:
return False
else:
if not is_odd:
return True
else:
return False


class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
mainLayout=QtGui.QHBoxLayout()
self.setLayout(mainLayout)

self.viewA=QtGui.QTableView()
self.viewA.horizontalHeader().setResizeMode(QtGui.QHeaderView.Stretch)

self.myModel=TableModel()

self.sortModel = MySortFilterProxyModel()
self.sortModel.setSourceModel(self.myModel)

self.viewA.setModel(self.sortModel)

self.checkBox=QtGui.QCheckBox("Show All")
self.checkBox.stateChanged.connect(self.sortModel.cbChanged)
self.checkBox.setChecked(self.sortModel.cb_status)

mainLayout.addWidget(self.viewA)
mainLayout.addWidget(self.checkBox)
self.show()

view=Window()
sys.exit(app.exec_())

As you can see, I have removed all connection from UI and the main source model. The main source model does not care about whether the checkbox is set or not. This keeps it decoupled. It's cleaner. The proxy model has been given this responsibility now. The filterAcceptsRow() does the main heavy lifting of displaying the right row based on whether the index of the row shown is odd or even based on the checkbox status.

I have added a few print statements to it, just in case you want to alter the logic based on the data and not the index.

Check out the docs on QSortFilterProxyModel and some examples here.

Hope this was useful.

How to update QTableView on QAbstractTableModel Change

Basically, you can connect a function to the model dataChanged signal/event, or you can insert this signal inside the function used to modify the model if you have implemented one.

The first option could be like below, in your model class,

self.dataChanged.connect(self.view.refresh) 

where refresh() is a custom slot in your view which trigger a simple self.update(), otherwise you need to handle the parameters send by the signal (affected parents QModelIndex).


The second option needs to emit the signal with QModelIndex, call this in the function when you apply some changes in the model class :

self.dataChanged.emit(self.index(X, Y), self.index(X, Y)) 

where X and Y represent the position of the changed data in your table

The third parameter role is an option, i.e. you can specify the DisplayRole, otherwise all roles can be updated.

PyQt - Automatically refresh a custom view when the model is updated?

You need to connect your view to the dataChanged ( const QModelIndex & topLeft, const QModelIndex & bottomRight ) signal of the model:

This signal is emitted whenever the data in an existing item changes.

If the items are of the same parent, the affected ones are those
between topLeft and bottomRight inclusive. If the items do not have
the same parent, the behavior is undefined.

When reimplementing the setData() function, this signal must be
emitted explicitly.



Related Topics



Leave a reply



Submit