pythonselectpyqt5qtablewidgetqcombobox

Selecting a table row, coloring a combobox based on item selection


I have this table, and what I want is a few things:

Table

Status

Non-divine code:

import sys


from PyQt5 import QtCore, QtGui, QtWidgets,uic
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton,QTableWidget,QStyledItemDelegate

class MyQComboBox(QtWidgets.QComboBox):
    def __init__(self, scrollWidget=None, *args, **kwargs):
        super(MyQComboBox, self).__init__(*args, **kwargs)  
        self.scrollWidget=scrollWidget
        self.setFocusPolicy(QtCore.Qt.StrongFocus)

    def wheelEvent(self, *args, **kwargs):
        return

class StatusItemDelegate(QtWidgets.QStyledItemDelegate):
    def __init__(self, parent=None):
        QtWidgets.QStyledItemDelegate.__init__(self, parent=parent)

    def paint(self, painter, option, index):
        if index.row() == 0 :
            if option.state & QtWidgets.QStyle.State_Selected: # highligh background if selected
                painter.fillRect(option.rect, option.palette.highlight())           
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(228, 221, 131))) # Initializing...
            painter.restore()
        elif index.row() == 1 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(185, 183, 159))) # Not Started
            painter.restore()
        elif index.row() == 2 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(65, 203, 53))) # Track
            painter.restore()
        elif index.row() == 3 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(38, 121, 31))) # Track Apporval
            painter.restore()
        elif index.row() == 4 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(31, 121, 104))) # For Solve
            painter.restore()
        elif index.row() == 5 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(48, 216, 226))) # Solve
            painter.restore()
        elif index.row() == 6 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(226, 167, 48))) # For GeoBuild
            painter.restore()
        elif index.row() == 7 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(226, 142, 48))) # GeoBuild
            painter.restore()
        elif index.row() == 8 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(243, 222, 108))) # For Rotomation
            painter.restore()
        elif index.row() == 9 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(243, 141, 108))) # Rotomation
            painter.restore()
        elif index.row() == 10 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(154, 76, 79))) # Waiting Assets
            painter.restore()
        elif index.row() == 11 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(76, 112, 154))) # For Packing
            painter.restore()
        elif index.row() == 12 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(53, 89, 201))) # Packing
            painter.restore()
        elif index.row() == 13 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(114, 228, 118))) # Preview
            painter.restore()
        elif index.row() == 14 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(178, 114, 228))) # Ready
            painter.restore()
        elif index.row() == 15 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(147, 169, 236))) # For Delivery
            painter.restore()
        elif index.row() == 16 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(81, 50, 199))) # Delivered
            painter.restore()
        elif index.row() == 17 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(84, 95, 239))) # Approved
            painter.restore()
        elif index.row() == 18 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(239, 84, 84))) # Cancelled
            painter.restore()
        elif index.row() == 19 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(98, 81, 81))) # On Hold
            painter.restore()
        elif index.row() == 20 :
            painter.save()
            painter.fillRect(option.rect, QtGui.QBrush(QtGui.QColor(98, 1, 1))) # Got Issue
            painter.restore()
        QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.UI=uic.loadUi('VertigoCentral.ui', self)
        self.show()
        self.TYPE = ("UV","LD")
        self.STATUS = ("Initializing ...","Not Started","Track","Track Approval",
        "For Solving", "Solve", "For GeoBuild", "GeoBuild", "For Rotomation",
        "Rotomation","Waiting Assets",
        "For Packing", "Packing","Preview","Ready",
        "For Delivery","Delivered","Approved",
        "Cancelled","On Hold","Got Issue!")

app = QApplication(sys.argv)

window = MainWindow()
window.setFixedSize(1920, 1080)

for row in range(window.ShotTable.rowCount()):
    window.ShotTable.setRowHeight(row,36)
    scrollArea = QtWidgets.QScrollArea()
    frmScroll = QtWidgets.QFrame(scrollArea)
    cmbOption = MyQComboBox(frmScroll)
    Status = cmbOption
    Status.setObjectName("Status")
    for index,it in enumerate(window.STATUS):
        Status.addItem("")
        Status.setItemText(index, it)



    Status.setItemDelegate(StatusItemDelegate())
    window.ShotTable.setCellWidget(row, 5, Status)
    view = Status.view()
    view.setFixedHeight(570)

window.ShotTable.setFocusPolicy(Qt.NoFocus)
window.show()
app.exec()

Solution

  • There are some issues with your implementation, starting with the creation of the index widget (both the scrollArea and frmScroll parents are useless), and painting issues (calling the base paint() implementation will potentially paint over the previous fillRect()).

    Also, since you probably need to correlate the combo with the actual model data, using an index widget is not the more appropriate choice, and you should instead use a delegate on the table that will create the combo as editor for the cell.

    A QComboBox uses a Qt model for its contents, and these models allow setting background and foreground roles that automatically draw items with the specified colors, if set.

    While you can just use the combo setItemData() function to set the colors, since you're going to use multiple instances of the same combo class, a better solution is to use a shared custom model.

    Then, in order to also draw the combo with the selected color, you have two possibilities: either you override its paintEvent() and "mimic" the default behavior by altering the palette, or you just use a specific stylesheet. For this case, I'd suggest the latter, as properly implementing the painting might be unnecessary difficult.

    Finally, once the delegate for the table is implemented (by overriding its createEditor(), setEditorData() and setModelData() functions) and set, you must call openPersistentEditor() for each row of the model. For obvious reasons, whenever you need to add new rows, you must remember to call openPersistentEditor() again (which could be done automatically if you connect to the table's model rowsInserted signal).

    from PyQt5.QtCore import *
    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import *
    
    StatusData = [
        ("Initializing ...", QColor(228, 221, 131)),
        ("Not Started", QColor(185, 183, 159)), 
        ("Track", QColor(65, 203, 53)), 
        ("Track Approval", QColor(38, 121, 31)), 
        ("For Solving", QColor(31, 121, 104)), 
        ("Solve", QColor(48, 216, 226)), 
        ("For GeoBuild", QColor(226, 167, 48)), 
        ("GeoBuild", QColor(226, 142, 48)), 
        ("For Rotomation", QColor(243, 222, 108)), 
        ("Rotomation", QColor(243, 141, 108)), 
        ("Waiting Assets", QColor(154, 76, 79)), 
        ("For Packing", QColor(76, 112, 154)), 
        ("Packing", QColor(53, 89, 201)), 
        ("Preview", QColor(114, 228, 118)), 
        ("Ready", QColor(178, 114, 228)), 
        ("For Delivery", QColor(147, 169, 236)), 
        ("Delivered", QColor(81, 50, 199)), 
        ("Approved", QColor(84, 95, 239)), 
        ("Cancelled", QColor(239, 84, 84)), 
        ("On Hold", QColor(98, 81, 81)), 
        ("Got Issue!", QColor(98, 1, 1)), 
    ]
    
    StatusRole = Qt.UserRole + 1
    
    
    def textColorForBackground(bgd):
        '''
            A simple helper function that returns a foreground color that will
            always contrast to the background.
        '''
        if isinstance(bgd, QBrush):
            bgd = bgd.color()
        r, g, b, a = bgd.getRgb()
        if (r * .3 + g * .5 + b * .1) < 100:
            return QColor(Qt.white)
        return QColor(Qt.black)
    
    
    class MyQComboBox(QComboBox):
        _model = None # a single model shared between instances
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            if not self._model:
                self.__class__._model = QStandardItemModel(0, 1)
                for row, (text, color) in enumerate(StatusData):
                    item = QStandardItem(text)
                    item.setData(color, Qt.BackgroundRole)
                    item.setData(textColorForBackground(color), Qt.ForegroundRole)
                    self._model.appendRow(item)
            self.setModel(self._model)
    
            self.setFocusPolicy(Qt.StrongFocus)
            self.currentIndexChanged.connect(self.updateBackground)
            self.updateBackground(self.currentIndex())
    
        def updateBackground(self, index):
            if index < 0:
                self.setStyleSheet('')
                return
            self.setStyleSheet('''
                MyQComboBox {{
                    color: {foreground};
                    background: {background};
                }}
            '''.format(
                foreground=self.itemData(index, Qt.ForegroundRole).name(), 
                background=self.itemData(index, Qt.BackgroundRole).name()
            ))
    
        def wheelEvent(self, event):
            return
    
        def showPopup(self):
            maxHeight = min(
                s.availableGeometry().height() for s in QApplication.screens())
            view = self.view()
            model = view.model()
            margins = view.parent().contentsMargins()
            spacing = view.spacing()
            heightHint = margins.top() + margins.bottom() - spacing
            for row in range(self.count()):
                itemHeight = view.visualRect(model.index(row, 0)).height() + spacing
                if heightHint + itemHeight > maxHeight:
                    break
                heightHint += itemHeight
            view.parent().setFixedHeight(heightHint)
            super().showPopup()
    
    
    class StatusDelegate(QStyledItemDelegate):
        def createEditor(self, parent, option, index):
            def updateModel(comboIndex):
                current = pIndex.data(StatusRole) or 0
                if comboIndex != current:
                    self.commitData.emit(editor)
    
            pIndex = QPersistentModelIndex(index)
            editor = MyQComboBox(parent)
            editor.clearFocus()
            editor.currentIndexChanged.connect(updateModel)
            return editor
    
        def setEditorData(self, editor, index):
            statusIndex = index.data(StatusRole)
            if (
                statusIndex is not None
                and editor.currentIndex() != statusIndex
            ):
                editor.setCurrentIndex(statusIndex)
    
        def setModelData(self, editor, model, index):
            current = index.data(StatusRole) or 0
            editorIndex = editor.currentIndex()
            if editorIndex != current:
                model.setData(index, editorIndex, StatusRole)
                if editorIndex > 0:
                    background = editor.itemData(editorIndex, Qt.BackgroundRole)
                else:
                    background = None
                model.setData(index, background, Qt.BackgroundRole)
    
    
    class MainWindow(QMainWindow):
        def __init__(self):
            super(MainWindow, self).__init__()
            self.UI=uic.loadUi('VertigoCentral.ui', self)
    
            self.ShotTable.setFocusPolicy(Qt.NoFocus)
            self.ShotTable.verticalHeader().setDefaultSectionSize(36)
            self.ShotTable.horizontalHeader().setSectionResizeMode(
                5, QHeaderView.ResizeToContents)
            self.ShotTable.setItemDelegateForColumn(
                5, StatusDelegate(self.ShotTable))
    
            for row in range(self.ShotTable.rowCount()):
                item = self.ShotTable.item(row, 5)
                if not item:
                    item = QTableWidgetItem()
                    self.ShotTable.setItem(row, 5, item)
                self.ShotTable.openPersistentEditor(item)
    
    
    if __name__ == '__main__':
        import sys
        app = QApplication(sys.argv)
    
        window = MainWindow()
        window.show()
    
        sys.exit(app.exec())