Issue
In PyQt5, I am using the model view to display a table. The model is the QAbstractTableModel, and I want to background color say row 0. The coloring works, but all rows get colored, instead of the row that I specified. Also, when I change to Qt.Background role, I get some "tickbox" in my cell that I don't want. I guess, that it is my understanding of what actually happen in QAbstractTableModel's def data part that prevents me from achieve the desired effect.
This is my code snippet part as I have already tried. Note the status 1 and status 2 are actually True or False in my case. If True, this entire row should be colored as background green, else it should stay white.
#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()
Then in my QAbstractTableModel class, I have the following in def data
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
I have googled on similar examples and studied this one in particular https://python-forum.io/Thread-Change-color-of-a-row-of-a-QTableView
What they seems to do is
if role == QtCore.Qt.BackgroundRole and "something more":
#then do something
It is this "something more" that I don't know how to parse into the def data method. Ideally it should be my row data status 1 that either can be True or False, but my understanding is that def data part is actually returning the data for the viewer?
Also, I get confused about that in my code, when I executed the print, it seems that even though I stated in my data that only one cell at QModelIndex (0,0) is set to green, the next row is also set to green. What is the reason for this behaviour?
# -*- 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_())
The expected outcome is only the specified row get colored. Also, the tick mark at each cell that I assume is related to Qt.Background is removed.
Thanks!
Solution
The logic of the data() method is to filter the information, in this case I will use a dictionary also considering that you want the complete row to be painted then save a QPersistentModel associated to the chosen row but to column 0, finally the method setData() does nothing by default so you have to override.
The error that indicates about the checkboxes is because you are returning the text for any other role like the Qt::CheckStateRole, instead you must filter the information as I already indicated
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_())
Answered By - eyllanesc
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.