Search code examples
pythonpyqtqtableviewqabstracttablemodel

How to color cells after creat a Qtableview using a custom QAbstractTableModel


I create a class 'pandasModel' based on QAbstractTableModel, shown below:

import sys
from PyQt5.QtGui     import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore    import *
class pandasModel(QAbstractItemModel):

    def __init__(self, data, parent=None):
        QAbstractItemModel.__init__(self, parent)
        self._data = data

    def rowCount(self, parent=None):
        return self._data.index.size

    def columnCount(self, parent=None):
        return self._data.columns.size

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

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

    def flags(self, index):
        flags = super(self.__class__, self).flags(index)
        flags |= Qt.ItemIsEditable
        flags |= Qt.ItemIsSelectable
        flags |= Qt.ItemIsEnabled
        flags |= Qt.ItemIsDragEnabled
        flags |= Qt.ItemIsDropEnabled
        return flags

    def sort(self, Ncol, order):
        """Sort table by given column number.
        """
        try:
            self.layoutAboutToBeChanged.emit()
            self._data = self._data.sort_values(self._data.columns[Ncol], ascending=not order)
            self.layoutChanged.emit()
        except Exception as e:
            print(e)

Also I create a QTableView to show the Model, shown below:

class TableWin(QWidget):
    pos_updown = -1
    pos_save = []

    def __init__(self):
        super(TableWin, self).__init__()
        self.resize(200, 100)
        self.table = QTableView(self)
        self.v_layout = QVBoxLayout()
        self.v_layout.addWidget(self.table)
        self.setLayout(self.v_layout)
        self.showdata()

    def showdata(self):
        data = pd.DataFrame([[1,2,3,4],[5,6,7,8]])
        model = pandasModel(data)
        self.table.setModel(model)

    def set_cell_color(self, row, column)
        '''
          Pass two arguments to this function, which is called to set
          the background color of the cell corresponding to the row and column
        '''
if __name__ == '__main__':
    app = QApplication(sys.argv)
    tableView = TableWin()
    # I want to change cell's color by call function 'set_cell_color' here
    # tableView.set_cell_color(row=1,column=1) 
    tableView.show()
    sys.exit(app.exec_())

We can show data in QTableview now, but question is how can i call function 'set_cell_color' to set the background color for cell with given row and column, so could you please tell me how to finish the code in def set_cell_color?

once i want to set cell's color by using 'model.item(row, col).setBackground(QColor(240, 255, 240))' just like QStandardItemModel, but raise error ''model' has no attribute 'item''

this link shows a method to set cell's color

code shows below:

import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *


class Model(QAbstractTableModel):
    def __init__(self, parent=None):
        super(Model, self).__init__(parent)
        self._data = [[['%d - %d' % (i, j), False] for j in range(10)] for i in range(10)]

    def rowCount(self, parent):
        return len(self._data)

    def columnCount(self, parent):
        return len(self._data[0])

    def flags(self, index):
        return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable

    def data(self, index, role):
        if index.isValid():
            data, changed = self._data[index.row()][index.column()]

        if role in [Qt.DisplayRole, Qt.EditRole]:
            return data

        if role == Qt.BackgroundRole and data == "In error":        # <---------
            return QBrush(Qt.red) 

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

if __name__ == '__main__':
    app = QApplication(sys.argv)
    tableView = QTableView()
    m = Model(tableView)
    tableView.setModel(m)
    tableView.show()
    sys.exit(app.exec_())

Use 'return QBrush(Qt.red)' in 'data' function upon can set the background-color of cells with value 'In error', but the background-color was already set when the Qtableview finish created, i just want to set cell's background color when i call function 'set_cell_color' ,that means i can control the cell's background even after Qtableview already been created, i will really appreciate for your help.


Solution

  • The logic is to save the information in the model associating the item's position and the item's color, and to update it, the dataChanged signal must be emitted.

    Note:Your model is of type table so you must inherit from QAbstractTableModel and not from QAbstractItemModel

    Considering the above, the solution is:

    class pandasModel(QAbstractTableModel):
        def __init__(self, data, parent=None):
            QAbstractItemModel.__init__(self, parent)
            self._data = data
    
            self.colors = dict()
    
        def rowCount(self, parent=None):
            return self._data.index.size
    
        def columnCount(self, parent=None):
            return self._data.columns.size
    
        def data(self, index, role=Qt.DisplayRole):
            if index.isValid():
                if role == Qt.DisplayRole:
                    return str(self._data.iloc[index.row(), index.column()])
                if role == Qt.EditRole:
                    return str(self._data.iloc[index.row(), index.column()])
                if role == Qt.BackgroundRole:
                    color = self.colors.get((index.row(), index.column()))
                    if color is not None:
                        return color
            return None
    
        def headerData(self, rowcol, orientation, role):
            if orientation == Qt.Horizontal and role == Qt.DisplayRole:
                return self._data.columns[rowcol]
            if orientation == Qt.Vertical and role == Qt.DisplayRole:
                return self._data.index[rowcol]
            return None
    
        def flags(self, index):
            flags = super(self.__class__, self).flags(index)
            flags |= Qt.ItemIsEditable
            flags |= Qt.ItemIsSelectable
            flags |= Qt.ItemIsEnabled
            flags |= Qt.ItemIsDragEnabled
            flags |= Qt.ItemIsDropEnabled
            return flags
    
        def sort(self, Ncol, order):
            """Sort table by given column number.
            """
            try:
                self.layoutAboutToBeChanged.emit()
                self._data = self._data.sort_values(
                    self._data.columns[Ncol], ascending=not order
                )
                self.layoutChanged.emit()
            except Exception as e:
                print(e)
    
        def change_color(self, row, column, color):
            ix = self.index(row, column)
            self.colors[(row, column)] = color
            self.dataChanged.emit(ix, ix, (Qt.BackgroundRole,))
    
    
    class TableWin(QWidget):
        pos_updown = -1
        pos_save = []
    
        def __init__(self):
            super(TableWin, self).__init__()
            self.resize(200, 100)
            self.table = QTableView(self)
            self.v_layout = QVBoxLayout()
            self.v_layout.addWidget(self.table)
            self.setLayout(self.v_layout)
            self.showdata()
    
        def showdata(self):
            data = pd.DataFrame([[1, 2, 3, 4], [5, 6, 7, 8]])
            self.model = pandasModel(data)
            self.table.setModel(self.model)
    
        def set_cell_color(self, row, column):
            self.model.change_color(row, column, QBrush(Qt.red))