So basically this is my PandasModel:
class pandasModel(QAbstractTableModel):
def __init__(self, data, editable=False):
QAbstractTableModel.__init__(self)
self._data = data
self.editable = editable
def rowCount(self, parent=None):
return self._data.shape[0]
def columnCount(self, parnet=None):
return self._data.shape[1]
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole:
return str(self._data.iloc[index.row(), index.column()])
elif role == Qt.TextAlignmentRole:
return Qt.AlignCenter
def setData(self, index, value, role):
if role == Qt.EditRole:
self._data.iloc[index.row(), index.column()] = value
return True
return False
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self._data.columns[col]
def flags(self, index):
if self.editable:
return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
else:
return Qt.ItemIsEnabled | Qt.ItemIsSelectable
def sort(self, col, order):
"""sort table by given column number col"""
self.layoutAboutToBeChanged.emit()
self._data = self._data.sort_values(
self._data.columns[col], ascending=order == Qt.AscendingOrder)
self.layoutChanged.emit()
And this is the way I'm updating my Table when the data is changed and received from the thread using API.
Basically my other Thread runs in a while loop constantly and if the data is changed it emits the new array of data to main thread and then the data is received and that is when the data is changed.
self.Table.setModel(pandasModel(self.myDataFrame))
I only wanted to update the new data but this updates whole table, due to that the selection is also lost!
Is there other way we can do it so that the values are updated but the table doesn't reset completely and also the selections of rows to be not lost???
P.S. the data is huge so cannot just pick one and update manually I want something dynamic
Related
After I change one or more cell contents of my qtableview, each changed cell loses its formatting.
Code:
...
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def data(self, index, role):
if index.isValid():
if role == Qt.DisplayRole or role == Qt.EditRole:
value =self._data[index.row()][index.column()]
return str(value)
if role == Qt.TextAlignmentRole:
value = self._data[index.row()][index.column()]
if isinstance(value, int):
return Qt.AlignVCenter + Qt.AlignRight
if role == Qt.ForegroundRole:
value = self._data[index.row()][index.column()]
if (isinstance(value, int)
and value < 0):
return QtGui.QColor('red')
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.EditRole:
self._data[index.row()][index.column()] = value
self.dataChanged.emit(index, index, (Qt.DisplayRole, Qt.TextAlignmentRole, Qt.ForegroundRole,))
return True
return False
def rowCount(self, index):
return len(self._data)
def columnCount(self, index):
return len(self._data[0])
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
How can i change the code in order to keep formatting after changing cell content?
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.
I have a pandas dataframe that I would like to present in a QtableView and make it editable. I have create the below model, but for some reason the output has checkboxes in every field. How can I get rid of them?
The outout looks like this:
And this this is the model that is used to make the pandas dataframe shown in a qtavleview and make it editable (I'm using PySide)
class PandasModelEditable(QtCore.QAbstractTableModel):
def __init__(self, data, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self._data = data
def rowCount(self, parent=None):
return len(self._data.values)
def columnCount(self, parent=None):
return self._data.columns.size
def data(self, index, role=QtCore.Qt.DisplayRole):
if index.isValid():
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
return unicode(self._data.iloc[index.row(), index.column()])
return unicode()
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return None
if orientation == QtCore.Qt.Horizontal:
try:
return '%s' % unicode(self._data.columns.tolist()[section])
except (IndexError,):
return unicode()
elif orientation == QtCore.Qt.Vertical:
try:
return '%s' % unicode(self._data.index.tolist()[section])
except (IndexError,):
return unicode()
def flags(self, index):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | \
QtCore.Qt.ItemIsEditable
def setData(self, index, value, role=QtCore.Qt.EditRole):
if index.isValid():
self._data.iloc[index.row(), index.column()] = value
if self.data(index, QtCore.Qt.DisplayRole) == value:
self.dataChanged.emit(index, index)
return True
return unicode()
Removing QtCore.Qt.ItemIsSelectable does not solve the problem as it doesn't seem to have any effet.
You are returning the wrong default values from data and setaData. The former should return None (so you could just remove the last line), whilst the latter should return False.
In my gui file I create a QTableView as follows (this is the part that is automatically generated by Qt Designer):
self.pnl_results = QtGui.QTableView(self.tab_3)
font = QtGui.QFont()
font.setPointSize(7)
self.pnl_results.setFont(font)
self.pnl_results.setFrameShape(QtGui.QFrame.StyledPanel)
self.pnl_results.setFrameShadow(QtGui.QFrame.Sunken)
self.pnl_results.setEditTriggers(QtGui.QAbstractItemView.AllEditTriggers)
self.pnl_results.setShowGrid(True)
self.pnl_results.setSortingEnabled(True)
self.pnl_results.setCornerButtonEnabled(True)
self.pnl_results.setObjectName("pnl_results")
I then define a model that enables me to link the pandas dataframe to the QTableView:
class PandasModel(QtCore.QAbstractTableModel):
"""
Class to populate a table view with a pandas dataframe
"""
def __init__(self, data, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self._data = data
def rowCount(self, parent=None):
return len(self._data.values)
def columnCount(self, parent=None):
return self._data.columns.size
def data(self, index, role=QtCore.Qt.DisplayRole):
if index.isValid():
if role == QtCore.Qt.DisplayRole:
return str(self._data.values[index.row()][index.column()])
return None
def headerData(self, col, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self._data.columns[col]
return None
def setData(self, index, value, role):
if not index.isValid():
return False
if role != QtCore.Qt.EditRole:
return False
row = index.row()
if row < 0 or row >= len(self._data.values):
return False
column = index.column()
if column < 0 or column >= self._data.columns.size:
return False
self._data.values[row][column] = value
self.dataChanged.emit(index, index)
return True
def flags(self, index):
flags = super(self.__class__,self).flags(index)
flags |= QtCore.Qt.ItemIsEditable
flags |= QtCore.Qt.ItemIsSelectable
flags |= QtCore.Qt.ItemIsEnabled
flags |= QtCore.Qt.ItemIsDragEnabled
flags |= QtCore.Qt.ItemIsDropEnabled
return flags
and finally a add my pandas dataframe (df) to the model:
model = PandasModel(df)
self.ui.pnl_results.setModel(model)
This correctly displays my pandas dataframe in the QTableView. However, for some reason when I edit the fileds the return to their original values (and also once I edit the field it is starting as empty). How can I make it editable and then write the reults back to the pandas dataframe?
Your model lacks setData method. The default implementation from QtCore.QAbstractTableModel does nothing and returns False. You need to implement this method in your model to make its items editable. If df is the actual container storing the data, you should simply change the value of the item stored in the container in setData method. It could look like this:
def setData(self, index, value, role):
if not index.isValid():
return False
if role != QtCore.Qt.EditRole:
return False
row = index.row()
if row < 0 or row >= len(self._data.values):
return False
column = index.column()
if column < 0 or column >= self._data.columns.size:
return False
self._data.values[row][column] = value
self.dataChanged.emit(index, index)
return True
You also need to implement flags method to return a value containing QtCore.Qt.ItemIsEditable.
Is there a way to edit specific item in qtableview given the row and col value? For example, I want to increment its value every second. Here is my tablemodel. Thanks
class MyTableModel(QAbstractTableModel):
def __init__(self, datain, headerdata, parent=None, *args):
""" datain: a list of lists
headerdata: a list of strings
"""
QAbstractTableModel.__init__(self, parent, *args)
self.arraydata = datain
self.headerdata = headerdata
def rowCount(self, parent):
return len(self.arraydata)
def columnCount(self, parent):
try:
return len(self.arraydata[0])
except:
return 0
def data(self, index, role):
if not index.isValid():
return QVariant()
elif role != Qt.DisplayRole:
return QVariant()
return QVariant(self.arraydata[index.row()][index.column()])
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return QVariant(self.headerdata[col])
return QVariant()
def sort(self, Ncol, order):
"""Sort table by given column number.
"""
self.emit(SIGNAL("layoutAboutToBeChanged()"))
self.arraydata = sorted(self.arraydata, key=operator.itemgetter(Ncol))
if order == Qt.DescendingOrder:
self.arraydata.reverse()
self.emit(SIGNAL("layoutChanged()"))
You could increment the value directly in your model, and emit a dataChanged signal from the model.
For example, add a method like this to the model class:
def incrementData(row, column):
self.arraydata[row][column] += 1
idx = self.index(row, column)
self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), idx, idx)