Optimize PyQt5 QAbstractView for Pandas Model - python

I am currently writing a SQL query interface through Python.
When writing the data into a QTableView, I am using a QAbstractTableModel to write the query result.
This works fine for smaller queries, but becomes extremely slow when trying to present many rows and columns. Is there anyway to increase the speed that the dataframe is loaded into the QTableView?
Here is the code for my QAbtractTableModel class:
class SQLConnection_PandasModel(QtCore.QAbstractTableModel):
def __init__(self, reason, df, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self._df = df.copy()
self.original_df = df
self.reason = reason
# PyQt5 Slots and Signals
if self.reason == 'Read':
self.conSig = sqlWindow()
self.conSig.dataChanged.connect(self.conSig.sql_table_updated)
# set the shortcut ctrl+F for find in menu
self.find_list_row = []
# setup menu options
def toDataFrame(self):
return self._df.copy()
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
if orientation == QtCore.Qt.Horizontal:
try:
return self._df.columns.tolist()[section]
except (IndexError,):
return QtCore.QVariant()
elif orientation == QtCore.Qt.Vertical:
try:
# return self.df.index.tolist()
return self._df.index.tolist()[section]
except (IndexError,):
return QtCore.QVariant()
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return QtCore.QVariant()
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
#print(type(self._df.iloc[index.row(), index.column()]))
if isinstance(self._df.iloc[index.row(), index.column()], bytes):
return QtCore.QVariant('(BLOB)')
else:
return QtCore.QVariant(str(self._df.iloc[index.row(), index.column()]))
def setData(self, index, value, role):
row = self._df.index[index.row()]
col = self._df.columns[index.column()]
if hasattr(value, 'toPyObject'):
# PyQt4 gets a QVariant
value = value.toPyObject()
else:
# PySide gets an unicode
dtype = self._df[col].dtype
if dtype != object:
if ((np.issubdtype(dtype, np.integer)) or isinstance(dtype, int)) and value.isnumeric():
value = None if value == '' else dtype.type(value)
self._df.loc[row, col] = value
# This is the signal to my RDB class that a cell has changed and to update the rdb_DF.
if role == QtCore.Qt.EditRole and self.reason == 'Read':
#print('Emitting 1',row , col)
self.conSig.dataChanged.emit(row, col, value)
return True
elif ((np.issubdtype(dtype, np.integer)) or isinstance(dtype, int)) and not(value.isnumeric()):
return False
else:
value = None if value == '' else dtype.type(value)
self._df.loc[row, col] = value
# This is the signal to my RDB class that a cell has changed and to update the rdb_DF.
if role == QtCore.Qt.EditRole and self.reason == 'Read':
#print('Emitting 2', row, col)
self.conSig.dataChanged.emit(row, col, value)
return True
else:
self._df.loc[row, col] = value
# This is the signal to my RDB class that a cell has changed and to update the rdb_DF.
if role == QtCore.Qt.EditRole and self.reason == 'Read':
#print('Emitting 3', row, col)
self.original_df.at[row, col] = value
#print(self.original_df)
self.conSig.dataChanged.emit(row, col, self.original_df)
return True
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self._df.index)
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self._df.columns)
def sort(self, column, order):
colname = self._df.columns.tolist()[column]
self.layoutAboutToBeChanged.emit()
self._df.sort_values(colname, ascending=order == QtCore.Qt.AscendingOrder, inplace=True)
self._df.reset_index(inplace=True, drop=True)
self.layoutChanged.emit()
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
I would appreciate any assistance I could get in increasing the speed that the dataframe is loaded.
Thank you.

Well I answered my own question. For those in the future that are running into an issue where you are trying to populate QTableViews using a QAbstractTableModel with large datasets...here you go:
class SQLConnection_PandasModel(QtCore.QAbstractTableModel):
def __init__(self, reason, df, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self._df = np.array(df.values)
self.original_df = df.copy()
self._cols = df.columns
self.r, self.c = np.shape(self._df)
self.reason = reason
# PyQt5 Slots and Signals
if self.reason == 'Read':
self.conSig = sqlWindow()
self.conSig.dataChanged.connect(self.conSig.sql_table_updated)
# set the shortcut ctrl+F for find in menu
self.find_list_row = []
def rowCount(self, parent=None):
return self.r
def columnCount(self, parent=None):
return self.c
def data(self, index, role=QtCore.Qt.DisplayRole):
if index.isValid():
if role == QtCore.Qt.DisplayRole:
if isinstance(self._df[index.row(), index.column()], bytes):
row = self._df[index.row()][0] - 1
col = self._df[index.column()][0] -1
self._df[row, col] = '(BLOB)'
return str('(BLOB)')
return str(self._df[index.row(),index.column()])
return None
def setData(self, index, value, role):
row = self._df[index.row()]
col = self._df[index.column()]
if hasattr(value, 'toPyObject'):
# PyQt4 gets a QVariant
value = value.toPyObject()
else:
# PySide gets an unicode
dtype = self._df.dtype
if dtype != object:
value = None if value == '' else dtype.type(value)
table_row = row[0]-1
table_col = col[0]-1
self._df[table_row, table_col] = value
# This is the signal to my RDB class that a cell has changed and to update the rdb_DF.
if role == QtCore.Qt.EditRole and self.reason == 'Read':
column_name = self.original_df.columns[table_col]
self.original_df.loc[table_row ,column_name] = value
my_df = pd.DataFrame(self._df)
my_df.columns = self.original_df.columns
self.conSig.dataChanged.emit(table_row, table_col, my_df)
return True
def headerData(self, p_int, orientation, role):
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
return self._cols[p_int]
elif orientation == QtCore.Qt.Vertical:
return p_int
return None
def sort(self, column, order):
colname = self._df.columns.tolist()[column]
self.layoutAboutToBeChanged.emit()
self._df.sort_values(colname, ascending=order == QtCore.Qt.AscendingOrder, inplace=True)
self._df.reset_index(inplace=True, drop=True)
self.layoutChanged.emit()
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
I converted the pandas df into a numpy array and that really sped it up. Finally, I convert the numpy back to pandas when I need to print out. This increased the speed drastically.
Pandas loading time for 120k rows and 10 columns: ~280 seconds.
Numpy loading time for 120k rows and 10 columns: 6.634 seconds.

Related

Is it possible to add dict() or list() to insertRows() in QTreeView

I have this TreeItem:
class QJsonTreeItem(object):
def __init__(self, data, parent=None):
self._parent = parent
self._key = ""
self._value = ""
self._type = None
self._children = list()
self.itemData = data
...
def data(self, column):
if column is 0:
return self.key
elif column is 1:
return self.value
def setData(self, column, value):
if column is 0:
self.key = value
if column is 1:
self.value = value
...
def insertChildren(self, position, rows, columns):
if position < 0 or position > len(self._children):
return False
for row in range(rows):
data = [None for v in range(columns)]
item = QJsonTreeItem(data, self)
self._children.insert(position, item)
return True
...
And custom QAbstractItemModel:
class QJsonTreeModel(QAbstractItemModel):
def __init__(self, parent=None):
super(QJsonTreeModel, self).__init__(parent)
self._rootItem = QJsonTreeItem(["Key", "Value"])
self._headers = ("Key", "Value")
...
def data(self, index, role):
if not index.isValid():
return None
if role != Qt.DisplayRole and role != Qt.EditRole:
return None
item = index.internalPointer()
if role == Qt.DisplayRole or role == Qt.EditRole:
if index.column() == 0:
return item.data(index.column())
if index.column() == 1:
return item.value
return None
def getItem(self, index):
if index.isValid():
item = index.internalPointer()
if item:
return item
return self._rootItem
def setData(self, index, value, role):
if role == Qt.EditRole:
item = index.internalPointer()
item.setData(index.column(), value)
self.dataChanged.emit(index, index, [Qt.EditRole])
return True
return False
def parent(self, index):
if not index.isValid():
return QModelIndex()
childItem = index.internalPointer()
parentItem = childItem.parent()
if parentItem == self._rootItem:
return QModelIndex()
return self.createIndex(parentItem.row(), 0, parentItem)
...
def insertRows(self, position, rows, parent, *args, **kwargs):
parentItem = self.getItem(parent)
self.beginInsertRows(parent, position, position + rows - 1)
success = parentItem.insertChildren(position, rows, self._rootItem.columnCount())
self.endInsertRows()
return success
And in my MainWindow file there is a button for adding new items in QTreeView which looks like this:
self.treeView = QTreeView()
self.model = QJsonTreeModel()
self.treeView.setModel(self.model)
...
rightClickMenu = QMenu()
actionAddItem = rightClickMenu.addAction(self.tr("Add Item"))
actionAddItem.triggered.connect(partial(self.treeAddItem))
...
def treeAddItem(self):
try:
index = self.treeView.selectionModel().currentIndex()
parent = index.parent()
if self.model.data(parent, Qt.EditRole) == None:
if not self.model.insertRow(index.row() + 1, parent):
return
for column in range(self.model.columnCount(parent)):
child = self.model.index(index.row() + 1, column, parent)
self.model.setData(child, "[No data]", Qt.EditRole)
else:
pass
except Exception as exception:
QMessageBox.about(self, "Exception", "Exception in treeAddItem() function: " + str(exception))
return
The question is can I somehow add not an "[No data]" string for QtreeView, but fo example an empty dict() or list()? As far as I understand it only adds an empty strings to the QTreeView, but my task still needs dictionaries and list. If it`s not possible my main idea is to get full tree back to dictionary and to work directly with dictionary items and then load back changed dict to tree, but it seems kinda "bad style".
Can someone help me with this task or offer another idea?
The soultion I did is this:
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.EditRole:
item = index.internalPointer()
item.setData(index.column(), value)
self.dataChanged.emit(index, index, [Qt.EditRole])
return True
if role == Qt.DisplayRole:
item = index.internalPointer()
item.setData(index.column(), dict())
self.dataChanged.emit(index, index, [Qt.EditRole])
return True
if role == Qt.ToolTipRole:
item = index.internalPointer()
item.setData(index.column(), list())
self.dataChanged.emit(index, index, [Qt.EditRole])
return True
return False

How to undo a change in QAbstractTableModel?

I have this simple example: a value in last column of my QAbstractTableModel equals value in column 1 multiplied by 2. So every time a change is made to value in column 1 - it results to a change in column 2.
When the value in the last column has changed - a message box is shown asking whether user wishes confirm action.
Imagine user have changed value in column 1 and sees this message: I wish my app to undo changes if cancel is clicked (return both old values in column 1 and 2), can you help me with that? I need to define my 'go_back()' function:
class Mainwindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView()
self.setCentralWidget(self.table)
self.data = [
[1, 0.18, 0.36],
[2, 0.25, 0.50],
[3, 0.43, 0.86],
]
self.model = MyModel(self.data)
self.table.setModel(self.model)
self.table.setSelectionBehavior(self.table.SelectRows)
self.table.setSelectionMode(self.table.SingleSelection)
self.model.dataChanged.connect(lambda index: self.count_last_column(index))
self.model.dataChanged.connect(lambda index: self.if_last_column_changed(index))
def calculations(self, position):
value = self.model.list_data[position][1] * 2
return value
def count_last_column(self, index):
if index.column() == 1:
position = index.row()
self.model.setData(self.model.index(position,2), self.calculations(position))
def if_last_column_changed(self, index):
if index.column() == 2:
message_box, message_box_button = self.show_message_box()
if message_box_button == 'Ok':
pass
elif message_box_button == 'Cancel':
self.go_back()
def show_message_box(self):
self.message_box = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Warning, 'Action', 'Value in column 3 has changed, confirm action?')
self.message_box.Ok = self.message_box.addButton(QtWidgets.QMessageBox.Ok)
self.message_box.Cancel = self.message_box.addButton(QtWidgets.QMessageBox.Cancel)
self.message_box.exec()
if self.message_box.clickedButton() == self.message_box.Ok:
return (self.message_box, 'Ok')
elif self.message_box.clickedButton() == self.message_box.Cancel:
return (self.message_box, 'Cancel')
def go_back(self):
pass #################
class MyModel(QtCore.QAbstractTableModel):
def __init__(self, list_data = [[]], parent = None):
super(MyModel, self).__init__()
self.list_data = list_data
def rowCount(self, parent):
return len(self.list_data)
def columnCount(self, parent):
return len(self.list_data[0])
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
row = index.row()
column = index.column()
value = self.list_data[row][column]
return value
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
value = self.list_data[row][column]
return value
def setData(self, index, value, role = QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
self.list_data[row][column] = value
self.dataChanged.emit(index, index)
return True
return False
def flags(self, index):
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable
if __name__ == '__main__':
app = QtWidgets.QApplication([])
application = Mainwindow()
application.show()
sys.exit(app.exec())
One option is to use a QUndoStack with the item model, and push QUndoCommand objects onto the stack in setData. The benefit of this approach is that it makes it simple to implement more undo/redo controls going forward, if you want to.
In MyModel, just create a stack in the constructor and add a line to push a command onto the stack right before you modify the list data (so the previous value can be stored in the command). The rest of the class is unchanged.
class MyModel(QtCore.QAbstractTableModel):
def __init__(self, list_data = [[]], parent = None):
super(MyModel, self).__init__()
self.list_data = list_data
self.stack = QtWidgets.QUndoStack()
def setData(self, index, value, role = QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
self.stack.push(CellEdit(index, value, self))
self.list_data[row][column] = value
self.dataChanged.emit(index, index)
return True
return False
Create the QUndoCommand with the index, value, and model passed to the constructor so the desired cell can be modified with calls to undo or redo.
class CellEdit(QtWidgets.QUndoCommand):
def __init__(self, index, value, model, *args, **kwargs):
super().__init__(*args, **kwargs)
self.index = index
self.value = value
self.prev = model.list_data[index.row()][index.column()]
self.model = model
def undo(self):
self.model.list_data[self.index.row()][self.index.column()] = self.prev
def redo(self):
self.model.list_data[self.index.row()][self.index.column()] = self.value
And now all that needs to be done in go_back is to call the undo method twice, for both cells that were modified.
def go_back(self):
self.model.stack.undo()
self.model.stack.undo()

How to add a checkbox column to Pandas data frame table in PyQt5

I wrote some code to display a pandas data frame as a table in my Qt app. What I want to do is add checkbox column to this table. The user can then check mark some of the rows. They can then edit the values inside rows they check marked.
Basically I want my table to look like this:
This is the code I have right now to populate the model with dataframe.
from PyQt5 import QtCore, QtGui, QtWidgets
import pandas as pd
class PandasModel(QtCore.QAbstractTableModel):
def __init__(self, df = pd.DataFrame(), parent=None):
QtCore.QAbstractTableModel.__init__(self, parent=parent)
self._df = df
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
if orientation == QtCore.Qt.Horizontal:
try:
return self._df.columns.tolist()[section]
except (IndexError, ):
return QtCore.QVariant()
elif orientation == QtCore.Qt.Vertical:
try:
# return self.df.index.tolist()
return self._df.index.tolist()[section]
except (IndexError, ):
return QtCore.QVariant()
def data(self, index, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
if not index.isValid():
return QtCore.QVariant()
return QtCore.QVariant(str(self._df.ix[index.row(), index.column()]))
def setData(self, index, value, role):
row = self._df.index[index.row()]
col = self._df.columns[index.column()]
if hasattr(value, 'toPyObject'):
# PyQt4 gets a QVariant
value = value.toPyObject()
else:
# PySide gets an unicode
dtype = self._df[col].dtype
if dtype != object:
value = None if value == '' else dtype.type(value)
self._df.set_value(row, col, value)
return True
Model = PandasModel(data_frame)
self.apollo_table.setModel(Model)

Saving updated dataframe from edited PyQt5 QTableView object

I am currently trying to load a dataframe into a PyQt QTableView to allow the re-naming of desired columns. Once the re-naming is complete, save the new dataframe as a .csv in a local folder. I cannot get the updated QTableView model to save. The workflow can be seen below.
Read the .csv I would like to modify
Load the dataframe into the QTableView with a Combobox in every column for the first row
Be able to select different options to rename the column
Select the desired name for desired columns
It would also be helpful if a certain option was selected in the Combobox (for instance, "Default"), it would make the desired column name to be the same as the original column name.
Save the final dataframe as a file to a local folder
Note: Only columns that have a value in the combobox are kept in the final dataset. Columns that are specified as "Default" are kept with the original column name.
Example of the Code below:
form_class = uic.loadUiType("DataProcessing.ui")[0]
class MyWindowClass(QtWidgets.QMainWindow, form_class):
def __init__(self, parent=None):
super().__init__()
self.setupUi(self)
self.PushButtonDisplay.clicked.connect(self.IP_Data_Display)
self.PushButtonImport.clicked.connect(self.IP_File_Import)
def IP_Data_Display(self):
DT_Disp = self.CBdisplay.currentText()
data = pd.read_csv('Example.csv')
data.loc[-1] = pd.Series([np.nan])
data.index = data.index + 1
data = data.sort_index()
model = PandasModel(data)
self.TView.setModel(model)
for column in range(model.columnCount()):
c = QtWidgets.QComboBox()
c.addItems(['','Option 1','Option 2','Option 3','Option 4','Default'])
i = self.TView.model().index(0,column)
self.TView.setIndexWidget(i,c)
def IP_File_Import(self):
newModel = self.TView.model()
data = []
for row in range(newModel.rowCount()):
rowRes = []
for column in range(newModel.columnCount()):
index = newModel.index(row, column)
item = newModel.data(index)
if item != '':
rowRes.append(item)
data.append(rowRes)
dataFrame = pd.DataFrame(data)
dataFrame.to_csv('Test.csv')#, index=False, header=False)
class PandasModel(QtCore.QAbstractTableModel):
def __init__(self, df = pd.DataFrame(), parent=None):
QtCore.QAbstractTableModel.__init__(self, parent=parent)
self._df = df
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
if orientation == QtCore.Qt.Horizontal:
try:
return self._df.columns.tolist()[section]
except (IndexError, ):
return QtCore.QVariant()
elif orientation == QtCore.Qt.Vertical:
try:
return self._df.index.tolist()[section]
except (IndexError, ):
return QtCore.QVariant()
def data(self, index, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
if not index.isValid():
return QtCore.QVariant()
return QtCore.QVariant(str(self._df.iloc[index.row(), index.column()]))
def setData(self, index, value, role):
row = self._df.index[index.row()]
col = self._df.columns[index.column()]
if hasattr(value, 'toPyObject'):
value = value.toPyObject()
else:
dtype = self._df[col].dtype
if dtype != object:
value = None if value == '' else dtype.type(value)
self._df.set_value(row, col, value)
return True
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self._df.index)
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self._df.columns)
def sort(self, column, order):
colname = self._df.columns.tolist()[column]
self.layoutAboutToBeChanged.emit()
self._df.sort_values(colname, ascending= order == QtCore.Qt.AscendingOrder, inplace=True)
self._df.reset_index(inplace=True, drop=True)
self.layoutChanged.emit()
if __name__ == '__main__':
app = QtWidgets.QApplication.instance()
if app is None:
app = QtWidgets.QApplication(sys.argv)
else:
print('QApplication instance already exists: %s' % str(app))
main = MyWindowClass(None)
main.show()
sys.exit(app.exec_())
Instead of using setItemWidget it is best to create a delegate that has a permanently open editor since it has access to the QModelIndex, on the other hand a row that will have the data of the header is added. And finally use _df to get the pandas.
import sys
from PyQt5 import QtCore, QtWidgets, uic
import pandas as pd
import numpy as np
form_class = uic.loadUiType("DataProcessing.ui")[0]
class PandasModel(QtCore.QAbstractTableModel):
def __init__(self, df = pd.DataFrame(), parent=None):
QtCore.QAbstractTableModel.__init__(self, parent=parent)
self._df = df
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
if orientation == QtCore.Qt.Horizontal:
try:
return self._df.columns.tolist()[section]
except (IndexError, ):
return QtCore.QVariant()
return super(PandasModel, self).headerData(section, orientation, role)
def data(self, index, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
if not index.isValid():
return QtCore.QVariant()
if index.row() == 0:
return QtCore.QVariant(self._df.columns.values[index.column()])
return QtCore.QVariant(str(self._df.iloc[index.row()-1, index.column()]))
def setData(self, index, value, role):
if index.row() == 0:
if isinstance(value, QtCore.QVariant):
value = value.value()
if hasattr(value, 'toPyObject'):
value = value.toPyObject()
self._df.columns.values[index.column()] = value
self.headerDataChanged.emit(QtCore.Qt.Horizontal, index.column(), index.column())
else:
col = self._df.columns[index.column()]
row = self._df.index[index.row()-1]
if isinstance(value, QtCore.QVariant):
value = value.value()
if hasattr(value, 'toPyObject'):
value = value.toPyObject()
else:
dtype = self._df[col].dtype
if dtype != object:
value = None if value == '' else dtype.type(value)
self._df.set_value(row, col, value)
return True
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self._df.index) +1
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self._df.columns)
def sort(self, column, order):
colname = self._df.columns.tolist()[column]
self.layoutAboutToBeChanged.emit()
self._df.sort_values(colname, ascending= order == QtCore.Qt.AscendingOrder, inplace=True)
self._df.reset_index(inplace=True, drop=True)
self.layoutChanged.emit()
class ComboBoxDelegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
editor = QtWidgets.QComboBox(parent)
value = index.data()
options = [value, 'Option 1','Option 2','Option 3','Option 4','Default']
editor.addItems(options)
editor.currentTextChanged.connect(self.commitAndCloseEditor)
return editor
#QtCore.pyqtSlot()
def commitAndCloseEditor(self):
editor = self.sender()
self.commitData.emit(editor)
class MyWindowClass(QtWidgets.QMainWindow, form_class):
def __init__(self, parent=None):
super().__init__()
self.setupUi(self)
self.PushButtonDisplay.clicked.connect(self.IP_Data_Display)
self.PushButtonImport.clicked.connect(self.IP_File_Import)
delegate = ComboBoxDelegate(self.TView)
self.TView.setItemDelegateForRow(0, delegate)
def IP_Data_Display(self):
DT_Disp = self.CBdisplay.currentText()
data = pd.read_csv('Example.csv')
data = data.sort_index()
model = PandasModel(data)
self.TView.setModel(model)
for i in range(model.columnCount()):
ix = model.index(0, i)
self.TView.openPersistentEditor(ix)
def IP_File_Import(self):
newModel = self.TView.model()
dataFrame = newModel._df.copy()
dataFrame.to_csv('Test.csv') #, index=False, header=False)
if __name__ == '__main__':
app = QtWidgets.QApplication.instance()
if app is None:
app = QtWidgets.QApplication(sys.argv)
else:
print('QApplication instance already exists: %s' % str(app))
main = MyWindowClass(None)
main.show()
sys.exit(app.exec_())

Pyqt5 QAbstractTableModel dataChanged not updating data

I am trying to update my QTableView after I receive a notice via pydispatcher of a change in the system. I did create the following functions
def rowCount(self, parent=None):
return len(self.m_list)
def columnCount(self, parent=None):
return len(self.table_def)
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.table_def[col]['Header']
return QVariant()
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None
if index.row() >= len(self.m_list) or index.row() < 0:
return None
row = index.row()
col = index.column()
print("Called for (%s, %s, %s)" % (row, col, role))
if role == Qt.DisplayRole:
return self.table_def[index.column()]['Function'](index.row())
elif role == Qt.BackgroundRole:
batch = (index.row() // 100) % 2
if batch == 0:
return QApplication.palette().base()
return QApplication.palette().alternateBase()
else:
return None
def flags(self, index):
if not index.isValid():
return None
return Qt.ItemIsEnabled
def update_model(self, data):
print('update_model')
index_1 = self.index(0, 0)
index_2 = self.index(0, 1)
self.dataChanged.emit(index_1, index_2, [Qt.DisplayRole])
The line self.dataChanged.emit(index_1, index_2, [Qt.DisplayRole]) does not seems to do anything; i.e. data(self, index, role=Qt.DisplayRole) is not called.
If I click on the table, then data(self, index, role=Qt.DisplayRole) is called and the table update.
The fix that I have right now is to call beginResetModel() and endResetModel(). That works, but it is not how it should work.
Any idea what could be happening?
I had the same problem and I fixed it just by calling self.headerDataChanged.emit instead. So, to do that, once you change something in the table, call the following:
self.headerDataChanged.emit(Qt.Horizontal, idx1, idx2)
where self._data is your data within the class. idx1 and idx2 are the first and last indexes of changed data, respectively. Qt.Horizontal is an example and it could be vertical depending on your table content.

Categories

Resources