pyside

PySide\Pyqt Unable to update QTableView - index (tuple) is perceived as a number


Simple table model. I'm trying to update one cell via spinbox. But the index passed is perceived as a number: TypeError: descriptor 'index' for '...QAbstractTableModel' objects doesn't apply to a 'int' object. See the __handleSpinBox method at the bottom of the code. I tried to manipulate the method setData but nothing worked out. Another odd thing is that there is almost nothing about this error in Google search.

from PySide6.QtCore import *
from PySide6.QtWidgets import QWidget, QHBoxLayout, QTableView, QApplication, QAbstractItemView, QSpinBox
from PySide6.QtGui import *
import sys
import numpy as np
import pandas as pd
import copy



class PandasModel(QAbstractTableModel):
    def __init__(self, table):
        super(PandasModel, self).__init__()
        data = pd.DataFrame(table)
        self._data = data

    def rowCount(self, parent=None):
        return self._data.shape[0]

    def columnCount(self, parent=None):
        return self._data.shape[1]

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return None
        if role == Qt.DisplayRole or role == Qt.EditRole:
            return str(self._data.iloc[index.row(), index.column()])
        return None

    def setData(self, index, value, role: Qt.EditRole):
        if not index.isValid():
            return False
        if role == Qt.EditRole:
            row = index.row()  
            column = index.column() 
            self._data[row][column] = value
            self.dataChanged.emit(index)
            return True
        return False

    def headerData(self, col, orientation, role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return self._data.columns[col]
        return None


class MyTable(QTableView):
    def __init__(self, model:QAbstractTableModel, data, parent=None):
        super().__init__(parent)
        self.data = data
        self.horizontalHeader().setVisible(True)
        self.verticalHeader().setVisible(False)
        self.model = model(pd.DataFrame(data, columns=['Col1', 'Col2', 'Col3']))
        self.setModel(self.model)


class MainWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.spinBox = QSpinBox()
        self.spinBox.setRange(-100, 100)
        self.spinBox.setValue(0)
        self.spinBox.valueChanged.connect(self.__handleSpinBox)
        self.pandasModel = PandasModel
        table = MyTable(PandasModel, [[0,0,0], [0,0,0]])
        hbox = QHBoxLayout()
        hbox.addWidget(self.spinBox, 1)
        hbox.addWidget(table, 1)
        self.setLayout(hbox)

    def __handleSpinBox(self, value):
        self.pandasModel.setData(self.pandasModel.index(1,1), result, Qt.EditRole) #TypeError:
        # descriptor 'index' for 'PySide6.QtCore.QAbstractTableModel' objects doesn't apply to a 'int' object




if __name__ == '__main__':
    app = QApplication(sys.argv)
    MW = MainWidget()
    MW.show()
    sys.exit(app.exec())     

Solution

  • There are reasons to define QTableView in a separate class. Only one cell changes (1,1).

    from PySide6.QtCore import *
    from PySide6.QtWidgets import QWidget, QHBoxLayout, QTableView, Application, QAbstractItemView, QSpinBox
    from PySide6.QtGui import *
    import sys
    import pandas as pd
    
    
    
    externalData = [[0,0,0], [0,0,0], [0,0,0]]
    
    class PandasModel(QAbstractTableModel):
        def __init__(self, data):
            super(PandasModel, self).__init__()
            self._data = pd.DataFrame(data, columns=['Col1', 'Col2', 'Col3'])
    
        def rowCount(self, parent=None):
            return self._data.shape[0]
    
        def columnCount(self, parent=None):
            return self._data.shape[1]
    
        def data(self, index, role=Qt.DisplayRole):
            if not index.isValid():
                return None
            if role == Qt.DisplayRole or role == Qt.EditRole:
                return str(self._data.iloc[index.row(), index.column()])
            return None
    
        def setData(self, index, value, role: Qt.EditRole):
            if role == Qt.EditRole:
                self._data.iat[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]
            return None
    
    
    class MyTable(QTableView):
        def __init__(self):
            super(MyTable, self).__init__()
            self.horizontalHeader().setVisible(True)
            self.verticalHeader().setVisible(False)
    
    
    class MainWidget(QWidget):
        def __init__(self, data):
            super().__init__()
            self.resize(500,200)
            self.data = data
            self.spinBox = QSpinBox()
            self.spinBox.setRange(-100, 100)
            self.spinBox.setValue(0)
            self.spinBox.valueChanged.connect(self.__handleSpinBox)
            self.model = PandasModel(self.data)
            self.table = MyTable()
            self.table.setModel(self.model)
            hbox = QHBoxLayout()
            hbox.addWidget(self.spinBox, 1)
            hbox.addWidget(self.table, 3)
            self.setLayout(hbox)
    
        def __handleSpinBox(self, value):
            self.model.setData(self.model.index(1, 1), value, Qt.EditRole)
            self.model.dataChanged.connect(self.table.update(self.model.index(1, 1)))
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        MW = MainWidget(externalData)
        MW.show()
        sys.exit(app.exec())```