PyQt5 中 QAbstractTableModel 给定行号的特定 table 行的背景颜色

Background color a specific table row given row number for QAbstractTableModel in PyQt5

在 PyQt5 中,我使用模型视图来显示 table。该模型是 QAbstractTableModel,我想用第 0 行作为背景颜色。着色有效,但所有行都会着色,而不是我指定的行。此外,当我更改为 Qt.Background 角色时,我的单元格中出现了一些我不想要的 "tickbox"。我想,正是我对 QAbstractTableModel 的 def 数据部分中实际发生的事情的理解阻止了我达到预期的效果。

这是我已经尝试过的代码片段部分。请注意,在我的例子中,状态 1 和状态 2 实际上是 True 或 False。如果为真,则整行的背景颜色应为绿色,否则应保持白色。

#Make some dummy data

tabledata = list()
tabledata.append(('item 1', 'amount 1', 'price 1', 'status 1'))
tabledata.append(('item 2', 'amount 2', 'price 2', 'status 2'))

#The self.model below is QAbstractTableModel subclassed

self.model.modelTableData = tabledata

#try set data for just one cell
self.model.setData(self.model.index(0,0), QtCore.Qt.BackgroundRole)
self.model.layoutChanged.emit()

然后在我的 QAbstractTableModel class 中,我在 def 数据中有以下内容

class TableModel(QtCore.QAbstractTableModel):
    def __init__(self, parent, header, tabledata):
        #inhert from QAbstractTableModel
        QtCore.QAbstractTableModel.__init__(self, parent)
        self.modelTableData = tabledata
        self.header = header

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

    def columnCount(self, parent):
        return len(self.header)

    def data(self, index, role):
        if not index.isValid():
            return None

        if role == QtCore.Qt.BackgroundRole:
            print('Qt.BackgroundRole at ' + str(index.row()))
            return QtCore.QVariant(QtGui.QColor(QtCore.Qt.green))

        print('Not Qt.BackgroundRole at ' + str(index.row()))
        return self.modelTableData[index.row()][index.column()]

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

我在谷歌上搜索了类似的例子,并特别研究了这个 https://python-forum.io/Thread-Change-color-of-a-row-of-a-QTableView

他们似乎在做的是

if role == QtCore.Qt.BackgroundRole and "something more":
    #then do something

就是这个"something more"不知道怎么解析成def数据的方法。理想情况下,我的行数据状态 1 应该是 True 或 False,但我的理解是 def 数据部分实际上是为查看器返回数据?

此外,我在我的代码中对此感到困惑,当我执行打印时,似乎即使我在我的数据中声明 QModelIndex (0,0) 处只有一个单元格设置为绿色,下一个行也设置为绿色。这种行为的原因是什么?

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'main_v00.ui'
#
# Created by: PyQt5 UI code generator 5.9.2
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets
import POSTools as tool
import json 

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1680, 1050)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.cb_OrderList = QtWidgets.QComboBox(self.centralwidget)
        self.cb_OrderList.setGeometry(QtCore.QRect(160, 30, 111, 31))
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(2)
        sizePolicy.setHeightForWidth(self.cb_OrderList.sizePolicy().hasHeightForWidth())
        self.cb_OrderList.setSizePolicy(sizePolicy)
        self.cb_OrderList.setObjectName("cb_OrderList")
        self.le_NewTable = QtWidgets.QLineEdit(self.centralwidget)
        self.le_NewTable.setGeometry(QtCore.QRect(30, 30, 113, 35))
        self.le_NewTable.setObjectName("le_NewTable")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(30, 10, 60, 16))
        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(14)
        self.label.setFont(font)
        self.label.setObjectName("label")
        self.label_2 = QtWidgets.QLabel(self.centralwidget)
        self.label_2.setGeometry(QtCore.QRect(170, 10, 60, 16))
        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(14)
        self.label_2.setFont(font)
        self.label_2.setObjectName("label_2")
        self.le_ItemName = QtWidgets.QLineEdit(self.centralwidget)
        self.le_ItemName.setGeometry(QtCore.QRect(30, 100, 171, 35))
        self.le_ItemName.setObjectName("le_ItemName")


        self.le_ItemAmount = QtWidgets.QLineEdit(self.centralwidget)
        self.le_ItemAmount.setGeometry(QtCore.QRect(220, 100, 113, 35))
        self.le_ItemAmount.setObjectName("le_ItemAmount")
        self.le_UnitPrice = QtWidgets.QLineEdit(self.centralwidget)
        self.le_UnitPrice.setGeometry(QtCore.QRect(350, 100, 113, 35))
        self.le_UnitPrice.setObjectName("le_UnitPrice")
        self.label_3 = QtWidgets.QLabel(self.centralwidget)
        self.label_3.setGeometry(QtCore.QRect(30, 70, 101, 21))
        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(14)
        self.label_3.setFont(font)
        self.label_3.setObjectName("label_3")
        self.label_4 = QtWidgets.QLabel(self.centralwidget)
        self.label_4.setGeometry(QtCore.QRect(220, 70, 101, 21))
        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(14)
        self.label_4.setFont(font)
        self.label_4.setObjectName("label_4")
        self.label_5 = QtWidgets.QLabel(self.centralwidget)
        self.label_5.setGeometry(QtCore.QRect(350, 70, 101, 21))
        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(14)
        self.label_5.setFont(font)
        self.label_5.setObjectName("label_5")
        self.label_6 = QtWidgets.QLabel(self.centralwidget)
        self.label_6.setGeometry(QtCore.QRect(480, 70, 101, 21))
        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(14)
        self.label_6.setFont(font)
        self.label_6.setObjectName("label_6")
        self.tableView = QtWidgets.QTableView(self.centralwidget)
        self.tableView.setGeometry(QtCore.QRect(30, 150, 711, 461))
        self.tableView.setObjectName("tableView")
        self.pb_remove = QtWidgets.QPushButton(self.centralwidget)
        self.pb_remove.setGeometry(QtCore.QRect(750, 250, 151, 101))
        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(18)
        self.pb_remove.setFont(font)
        self.pb_remove.setObjectName("pb_remove")
        self.pb_receipt = QtWidgets.QPushButton(self.centralwidget)
        self.pb_receipt.setGeometry(QtCore.QRect(590, 620, 151, 101))
        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(18)
        self.pb_receipt.setFont(font)
        self.pb_receipt.setObjectName("pb_receipt")
        self.label_TotalPrice = QtWidgets.QLabel(self.centralwidget)
        self.label_TotalPrice.setGeometry(QtCore.QRect(480, 100, 121, 35))
        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(18)
        self.label_TotalPrice.setFont(font)
        self.label_TotalPrice.setObjectName("label_TotalPrice")
        self.le_Discount = QtWidgets.QLineEdit(self.centralwidget)
        self.le_Discount.setGeometry(QtCore.QRect(610, 100, 131, 35))
        self.le_Discount.setObjectName("le_Discount")
        self.label_7 = QtWidgets.QLabel(self.centralwidget)
        self.label_7.setGeometry(QtCore.QRect(610, 70, 101, 21))
        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(14)
        self.label_7.setFont(font)
        self.label_7.setObjectName("label_7")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(750, 150, 151, 101))
        font = QtGui.QFont()
        font.setFamily("Arial")
        font.setPointSize(18)
        self.pushButton.setFont(font)
        self.pushButton.setObjectName("pushButton")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 1680, 22))
        self.menubar.setObjectName("menubar")
        self.menuMore_Options = QtWidgets.QMenu(self.menubar)
        self.menuMore_Options.setObjectName("menuMore_Options")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.actionCount_Up = QtWidgets.QAction(MainWindow)
        self.actionCount_Up.setObjectName("actionCount_Up")
        self.menuMore_Options.addSeparator()
        self.menuMore_Options.addAction(self.actionCount_Up)
        self.menubar.addAction(self.menuMore_Options.menuAction())

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
        MainWindow.setTabOrder(self.le_NewTable, self.cb_OrderList)
        MainWindow.setTabOrder(self.cb_OrderList, self.le_ItemName)
        MainWindow.setTabOrder(self.le_ItemName, self.le_ItemAmount)
        MainWindow.setTabOrder(self.le_ItemAmount, self.le_UnitPrice)
        MainWindow.setTabOrder(self.le_UnitPrice, self.le_Discount)
        MainWindow.setTabOrder(self.le_Discount, self.pushButton)
        MainWindow.setTabOrder(self.pushButton, self.pb_remove)
        MainWindow.setTabOrder(self.pb_remove, self.pb_receipt)
        MainWindow.setTabOrder(self.pb_receipt, self.tableView)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.label.setText(_translate("MainWindow", "Order"))
        self.label_2.setText(_translate("MainWindow", "Order List"))
        self.label_3.setText(_translate("MainWindow", "Item Name"))
        self.label_4.setText(_translate("MainWindow", "Item Amount"))
        self.label_5.setText(_translate("MainWindow", "Unit Price"))
        self.label_6.setText(_translate("MainWindow", "Price"))
        self.pb_remove.setText(_translate("MainWindow", "Remove"))
        self.pb_receipt.setText(_translate("MainWindow", "Receipt"))
        self.label_TotalPrice.setText(_translate("MainWindow", "0"))
        self.label_7.setText(_translate("MainWindow", "Discount [%]"))
        self.pushButton.setText(_translate("MainWindow", "Print Kitchen"))
        self.menuMore_Options.setTitle(_translate("MainWindow", "More Options"))
        self.actionCount_Up.setText(_translate("MainWindow", "Count Up"))

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(MainWindow,self).__init__()
        self.setupUi(self)
        #Start by reading in the menu items
        self.URL_MenuDB = "somewhere"
        self.path_OrderDB = "somewhere"
        header, menudb = tool.load_MenuDB(self.URL_MenuDB)
        self.MenuHeader = header

        #Prepare the completer by first creating the model
        self.completermodel = QtGui.QStandardItemModel()
        for item in menudb:
            row = list()
            for col in item:
                cell = QtGui.QStandardItem(str(col))
                row.append(cell)
            self.completermodel.appendRow(row)

        self.completer = QtWidgets.QCompleter()
        self.completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.completer.setCompletionColumn(0)
        self.completer.setModel(self.completermodel)
        self.completer.setFilterMode(QtCore.Qt.MatchContains)
        self.completer.activated[QtCore.QModelIndex].connect(self.onActivated)
        self.le_ItemName.setCompleter(self.completer)

        #Setup model view
        self.TableViewHeader = ['Item', 'Qty', 'Price', 'Print status']
        self.TableData = list()
        self.model = TableModel(self, header = self.TableViewHeader, tabledata = self.TableData)
        self.model.layoutChanged.emit()
        self.tableView.setModel(self.model)

        #Upon program starts up, check if OrderDB.txt exists
        status, AllTables = tool.importOrderDB(self.path_OrderDB)
        if status is True: #OrderDB.txt exists
            #check if there is incomplete orders
            self.AllTables = AllTables
            #A list of all active tables
            tablenames = tool.getincompleteOrder(AllTables)
            if tablenames:
                #update the list of tablenames into drop down list
                self.cb_OrderList.clear()
                self.cb_OrderList.addItems(tablenames)
                #get the index of the current active tablename
                self.cb_OrderList.currentText()

        #Define what happens when the user press enter og return for item amount
        self.le_ItemAmount.returnPressed.connect(self.ItemAmountEnterKeyPress)

        #If enter is pressed at unit price, it also connects with self.ItemAmountEnterKeyPress
        self.le_UnitPrice.returnPressed.connect(self.ItemAmountEnterKeyPress)

        #Define what happens when input table edit field is activated
        self.le_NewTable.returnPressed.connect(self.input_newTable)

    def input_newTable(self): #When the user create a new order
        if not self.le_NewTable.text():
            return
        else: 
            #check if OrderDB already exists, if not one will be created. If exists is True, AllTables will be returned
            status, AllTables, tablename, nameclash = tool.ExistOrderDB(self.path_OrderDB, self.le_NewTable.text().strip())

            if nameclash is True:
                tool.msgbox(self,'Bord navn eksisterer. Valg et nyt!')
                self.le_NewTable.clear()
                return

            if status is False: #OrderDB.txt has just been created, and AllTables containing the tableName is returned
                self.AllTables = AllTables
                #Sort all the incomplete tables from All Tables and return the sorted tablename as pandas DataFrame
                tablename = tool.getincompleteOrder(AllTables)
                #insert the tablename as list to drop down list
                self.cb_OrderList.clear()
                self.cb_OrderList.addItems(tablename)
                self.le_NewTable.clear()

            else: #OrderDB.txt exists, continue to create the new table
                #create the tabledict
                tabledict = tool.CreateTableDict(self.le_NewTable.text())
                self.AllTables.append(tabledict)
                #save to data base
                tool.saveOrderDB(self.path_OrderDB, self.AllTables)

                tablename = tabledict["Name"]
                #get a list of all incomplete order names
                ordernames = tool.getincompleteOrder(self.AllTables)
                self.cb_OrderList.clear()
                self.cb_OrderList.addItems(ordernames)
                index = self.cb_OrderList.findText(tablename, QtCore.Qt.MatchFixedString)
                #set the drop down list to the current active index
                self.cb_OrderList.setCurrentIndex(index)

            #Set focus to item field
            self.le_ItemName.setFocus()
            self.le_NewTable.clear()

    def ItemAmountEnterKeyPress(self): #insert the item into the table and update the data base behind the scene
        if not self.cb_OrderList.currentText():
            tool.msgbox(self, 'Select an order first')
            return
        else:
            #Update the selected item into the AllTable
            #Do a match to see if self.selected matches the fields
            inputtext = self.le_ItemName.text()
            if inputtext.strip() == self.selected[0]:
                #the selected is the same as what appears in the field. Check the remaining fields
                qty = tool.isfloat(self.le_ItemAmount.text())
                price = tool.isfloat(self.le_UnitPrice.text())
                if qty is not False and price is not False:
                    #submit the fields to be input into the modelview
                    index = tool.getTableDict(self)

                    #Do the visualization
                    price = [item for item in self.AllTables[index]["orderPrice"]]

                    totalprice = sum(price)
                    self.TableData = list(
                        zip(self.AllTables[index]['itemName'],
                        self.AllTables[index]['orderQty'],
                        price,
                        self.AllTables[index]['PrintStatus_send2kitchen']
                    ))
                    #Update into the model
                    tabledata = list()
                    tabledata.append(('item 1', 'amount 1', 'price 1', 'status 1'))
                    tabledata.append(('item 2', 'amount 2', 'price 2', 'status 2'))

                    self.model.modelTableData = tabledata
                    #self.model.modelTableData = self.TableData

                    #try set data for just one cell
                    self.model.setData(self.model.index(0,0), 
                    QtCore.Qt.BackgroundRole)

                    self.model.layoutChanged.emit()

                    #tool.plotTable(self, totalprice)

                    #clear
                    self.clearInputFields()
                    self.le_ItemName.setFocus()

                else: #the item in the fields is different from self.selected, ask the user to try again
                    tool.msgbox(self, 'Item er ikke korrekt valgt, prøv igen.')
                    self.clearInputFields(self)
                    self.le_ItemName.setFocus()
                    return

            else: #User has not selected from the list
                tool.msgbox(self,'Prøv igen. Du skal vælge fra listen når du indsætter item')
                self.clearInputFields(self)
                self.le_ItemName.setFocus()
                return

    def clearInputFields(self):
        self.le_ItemName.clear()
        self.le_ItemAmount.clear()
        self.le_UnitPrice.clear()

    @QtCore.pyqtSlot(QtCore.QModelIndex)
    def onActivated(self, index):
        self.selected = list()
        for i in range(0, len(self.MenuHeader) +1):
            self.selected.append(index.sibling(index.row(),i).data())

        #display the selected item in the editfield
        self.le_UnitPrice.setText(self.selected[3])

        #change focus to amount
        self.le_ItemAmount.setFocus()

class TableModel(QtCore.QAbstractTableModel):
    def __init__(self, parent, header, tabledata):
        #inhert from QAbstractTableModel
        QtCore.QAbstractTableModel.__init__(self, parent)
        self.modelTableData = tabledata
        self.header = header

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

    def columnCount(self, parent):
        return len(self.header)

    def data(self, index, role):
        if not index.isValid():
            return None

        if role == QtCore.Qt.BackgroundRole:
            return QtCore.QVariant(QtGui.QColor(QtCore.Qt.green))

        return self.modelTableData[index.row()][index.column()]

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

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())


预期的结果是只有指定的行被着色。此外,我假设与 Qt.Background 相关的每个单元格的刻度线被删除。

谢谢!

data() 方法的逻辑是过滤信息,在这种情况下,我将使用一个字典,同时考虑到您想要绘制完整的行,然后保存一个与所选行关联但与列关联的 QPersistentModel 0,最后 setData() 方法默认不执行任何操作,因此您必须重写。

关于复选框的错误指示是因为您要返回任何其他角色的文本,例如 Qt::CheckStateRole,而您必须按照我已经指示的方式过滤信息

from PyQt5 import QtCore, QtGui, QtWidgets


class TableModel(QtCore.QAbstractTableModel):
    def __init__(self, parent, header, tabledata):
        QtCore.QAbstractTableModel.__init__(self, parent)
        self.modelTableData = tabledata
        self.header = header

        self.background_colors = dict()

    def rowCount(self, parent=QtCore.QModelIndex()):
        return len(self.modelTableData)

    def columnCount(self, parent=QtCore.QModelIndex()):
        return len(self.header)

    def data(self, index, role):
        if not index.isValid():
            return None
        if (
            0 <= index.row() < self.rowCount()
            and 0 <= index.column() < self.columnCount()
        ):
            if role == QtCore.Qt.BackgroundRole:
                ix = self.index(index.row(), 0)
                pix = QtCore.QPersistentModelIndex(ix)
                if pix in self.background_colors:
                    color = self.background_colors[pix]
                    return color
            elif role == QtCore.Qt.DisplayRole:
                return self.modelTableData[index.row()][index.column()]

    def setData(self, index, value, role):
        if not index.isValid():
            return False
        if (
            0 <= index.row() < self.rowCount()
            and 0 <= index.column() < self.columnCount()
        ):
            if role == QtCore.Qt.BackgroundRole and index.isValid():
                ix = self.index(index.row(), 0)
                pix = QtCore.QPersistentModelIndex(ix)
                self.background_colors[pix] = value
                return True
        return False

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


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = QtWidgets.QTableView()
    header = ["Item", "Qty", "Price", "Print status"]

    tabledata = [
        ("item 1", "amount 1", "price 1", "status 1"),
        ("item 2", "amount 2", "price 2", "status 2"),
    ]
    model = TableModel(None, header, tabledata)
    model.setData(
        model.index(0, 0), QtGui.QColor(QtCore.Qt.green), QtCore.Qt.BackgroundRole
    )
    w.setModel(model)
    w.show()
    sys.exit(app.exec_())

@eyllanesc 非常感谢给我另一个学习机会。它帮助我意识到我需要过滤信息。另外,我意识到我实际上不需要 setData 来让它工作。以下是我如何根据您的评论和示例重写代码。我只剩下一个问题,我在下面实现 def 数据的方式,它不会使代码非常昂贵,因为需要执行枚举器循环来识别每次 def 数据时哪些行需要着色叫什么?

class TableModel(QtCore.QAbstractTableModel):
    def __init__(self, parent, header, tabledata):
        #inhert from QAbstractTableModel
        QtCore.QAbstractTableModel.__init__(self, parent)
        self.modelTableData = tabledata
        self.header = header

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

    def columnCount(self, parent):
        return len(self.header)

    def data(self, index, role):
        if not index.isValid():
            return None

        if role == QtCore.Qt.CheckStateRole:
            return None

        #get rows to be colored
        rowscolor = list()
        for item in enumerate(self.modelTableData):
            if item[1][-1] == False:
                rowscolor.append(item[0])

        if role == QtCore.Qt.BackgroundRole and index.row() in rowscolor:
            return QtCore.QVariant(QtGui.QColor(QtCore.Qt.green))

        return self.modelTableData[index.row()][index.column()]

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