I have a model-based QTreeView which is fed by a QSortFilterProxyModel.
I am displaying rows from the database here. With a double-click I am executing a modal QDialog to edit the data (the user can do CRUD operations like create new rows into my SQLite data base, updates, delete etc.).
On closing the edit-dialog the focus gets back to the underlying QTreeView. Now I would like to refresh the changed data from the database. One way to do this would be to build up my complete model and refetch all rows, but then the user-selected cell disappears and it is very slow and also the TreeView flickers (because I do some column adjustings concerning the width.)
Emiting dataChanged signal on the QSortFilterProxyModel somehow seems to do ... nothing.
Can I somehow tell my model to refresh or update the data, without fetching alls rows again?
full working code example:
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
import sqlite3
import re
def _human_key(key):
key = key.lower()
key = key.replace("ä", "ae").replace("ö", "oe").replace("ü", "ue").replace("ß", "ss")
parts = re.split(r'(\d*\.\d+|\d+)', key, re.IGNORECASE)
return parts
class HumanProxyModel(QtCore.QSortFilterProxyModel):
def lessThan(self, source_left, source_right):
data_left = source_left.data()
data_right = source_right.data()
if type(data_left) == type(data_right) == str:
return _human_key(data_left) < _human_key(data_right)
return super(HumanProxyModel, self).lessThan(source_left, source_right)
class winMain(QtWidgets.QMainWindow):
COL_ID, COL_NAME = range(2)
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi()
# create and assign model
self.model = self.createModel(self)
proxy = HumanProxyModel(self)
proxy.setSourceModel(self.model)
self.treeView.setModel(proxy)
# create db with some data
self.conn = self.setupDb()
# format and sort the treeView
self.treeView.setRootIsDecorated(False)
self.treeView.setSortingEnabled(True)
self.treeView.sortByColumn(self.COL_ID, QtCore.Qt.AscendingOrder)
self.treeView.setAlternatingRowColors(True)
self.treeView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.treeView.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
# display the main window
self.show()
def setupUi(self):
self.setWindowTitle("PyQt5 QTreeView refresh problem demo")
# add a data fetch button
pbFetch = QtWidgets.QPushButton(self)
pbFetch.setText("Fetch data")
pbFetch.move(350, 20)
pbFetch.clicked.connect(self.on_pbFetch_clicked)
# add a reset button
pbReset = QtWidgets.QPushButton(self)
pbReset.setText("Reset")
pbReset.move(450, 20)
pbReset.clicked.connect(self.on_pbReset_clicked)
# add QTreeView-Control
self.treeView = QtWidgets.QTreeView(self)
self.treeView.move(50, 100)
self.treeView.resize(700, 300)
self.treeView.doubleClicked.connect(self.on_treeView_doubleClicked)
self.resize(800,450) # resize main window
def on_pbFetch_clicked(self):
self.fetchData()
def on_pbReset_clicked(self):
print("on_pbReset_clicked() called.")
self.reset()
def setupDb(self):
# create db and table
con = sqlite3.connect(":memory:")
con.execute("create table person(id int, name text)")
# insert some example data
con.execute("insert into person(id, name) values (1, 'anders')");
con.execute("insert into person(id, name) values (2, 'Arachno')");
con.execute("insert into person(id, name) values (3, 'Zabel')");
con.execute("insert into person(id, name) values (4, 'Ötztürk')");
con.execute("insert into person(id, name) values (5, 'de Hahn')");
# commit the transaction
con.commit()
return con
def reset(self):
print("reset called.")
self.model.beginResetModel()
self.model.endResetModel()
def fetchData(self):
self.reset()
cur = self.conn.cursor()
sSql = "SELECT id, name FROM person"
rows = cur.execute(sSql)
for row in rows:
self.addPerson(self.model, row[0], row[1])
def refresh_treeView(self):
print("refresh_treeView() was called.")
self.model.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())
def createModel(self, parent):
model = QtGui.QStandardItemModel(0, 2, parent)
model.setHeaderData(self.COL_ID, QtCore.Qt.Horizontal, "ID")
model.setHeaderData(self.COL_NAME, QtCore.Qt.Horizontal, "Name")
return model
def addPerson(self, model, personID, personName):
model.insertRow(0)
model.setData(model.index(0, self.COL_ID), personID)
model.setData(model.index(0, self.COL_NAME), personName)
def on_treeView_doubleClicked(self,index):
row = index.row()
ix = self.treeView.model().index(row, self.COL_ID)
person_id = ix.data()
print(f"id of double_clicked person: {person_id}")
# create CRUD dialog for selected person
self.disp_dlg_edit(person_id)
def disp_dlg_edit(self, person):
dlgEdit = QtWidgets.QDialog(self)
lblId = QtWidgets.QLabel(dlgEdit)
lblId.setText("ID:")
efId = QtWidgets.QLineEdit(dlgEdit)
efId.setReadOnly(True)
efId.setEnabled(False)
efId.move(50, 0)
lblName = QtWidgets.QLabel(dlgEdit)
lblName.setText("Name:")
lblName.move(0, 25)
efName = QtWidgets.QLineEdit(dlgEdit)
efName.move(50, 25)
pbUpdate = QtWidgets.QPushButton(dlgEdit)
pbUpdate.setText("Update")
pbUpdate.move(200,0)
pbUpdate.resize(100,50)
# fetch the data of given person
cur = self.conn.cursor()
sSql = "SELECT id, name FROM person WHERE id = ?"
cur.execute(sSql, (person,))
result = cur.fetchone()
person_id = result[0]
efId.setText(str(person_id))
person_name = result[1]
efName.setText(person_name)
pbUpdate.clicked.connect(lambda: self.update_person(person_id, efName.text()))
dlgEdit.resize(300, 50)
dlgEdit.exec_()
self.refresh_treeView()
def update_person(self, person_id, person_name):
print(f"update_person({person_id}, {person_name}) called.")
cur = self.conn.cursor()
sSql = "UPDATE person set name=? WHERE ID=?"
cur.execute(sSql, (person_name,person_id,))
self.conn.commit()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = winMain()
sys.exit(app.exec_())