So I have a table with 1 row and multiple columns and I use the custom delegate function for the image thumbnail. I also included the set background colour function when I click it. However, it doesn't change the colour of the item background. I have to use the custom delegate function so that my icon image is sticking with each other from different cell without any additional spaces between.
my code is as below
import random
from PyQt5 import QtCore, QtGui, QtWidgets
imagePath2 = "arrowREDHead.png"
imagePath = "arrowREDBody.png"
class IconDelegate(QtWidgets.QStyledItemDelegate):
def paint(self, painter, option, index):
icon = index.data(QtCore.Qt.DecorationRole)
mode = QtGui.QIcon.Normal
if not (option.state & QtWidgets.QStyle.State_Enabled):
mode = QtGui.QIcon.Disabled
elif option.state & QtWidgets.QStyle.State_Selected:
mode = QtGui.QIcon.Selected
state = (
QtGui.QIcon.On
if option.state & QtWidgets.QStyle.State_Open
else QtGui.QIcon.Off
)
pixmap = icon.pixmap(option.rect.size(), mode, state)
painter.drawPixmap(option.rect, pixmap)
def sizeHint(self, option, index):
return QtCore.QSize(20, 20)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
table = QtWidgets.QTableWidget(1, 10)
delegate = IconDelegate(table)
table.setItemDelegate(delegate)
self.setCentralWidget(table)
for j in range(table.columnCount()):
if(j % 2 == 0):
pixmap = QtGui.QPixmap(imagePath)
icon = QtGui.QIcon(pixmap)
it = QtWidgets.QTableWidgetItem()
it.setIcon(icon)
table.setItem(0, j, it)
else:
pixmap = QtGui.QPixmap(imagePath2)
icon = QtGui.QIcon(pixmap)
it = QtWidgets.QTableWidgetItem()
it.setIcon(icon)
table.setItem(0, j, it)
table.item(0, 1).setBackground(QtGui.QColor(100,100,150))
table.resizeRowsToContents()
table.resizeColumnsToContents()
table.setShowGrid(False)
table.itemClicked.connect(self.sequenceClicked)
def sequenceClicked(self, item):
self.itemClicked = item
print("item", item)
item.setBackground(QtGui.QColor(255, 215, 0))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
This is the image with the gap between icons and it form not a perfect looking arrow thats why I used the custom delegate function to stick the icon together and let it look like a perfect arrow
This is the image that has the perfect look of the arrow as it doesnt has any gap between the cells by using the delegate function. However, it doesnt allow me to change the background colour for the cell.
This is the image of the arrowHead
This is the image of the arrowBody
As with any overridden function, if the base implementation is not called, the default behavior is ignored.
paint()
is the function responsible of drawing any aspect of the item, including its background. Since in your override you are just drawing the pixmap, everything else is missing, so the solution would be to call the base implementation and then draw the pixmap.
In this specific case, though, this wouldn't be the right choice, as the base implementation already draws the item's decoration, and in certain conditions (assuming that the pixmap has an alpha channel), it will probably result in having the images overlapping.
There are three possible solutions.
In this case, we do what the base implementation would, which is calling the style drawControl
function with the CE_ItemViewItem
, but we modify the option before that, removing anything related to the icon:
def paint(self, painter, option, index):
# create a new option (as the existing one should not be modified)
# and initialize it for the current index
option = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(option, index)
# remove anything related to the icon
option.features &= ~option.HasDecoration
option.decorationSize = QtCore.QSize()
option.icon = QtGui.QIcon()
if option.widget:
style = option.widget.style()
else:
style = QtWidgets.QApplication.style()
# call the drawing function for view items
style.drawControl(style.CE_ItemViewItem, option, painter, option.widget)
# proceed with the custom drawing of the pixmap
icon = index.data(QtCore.Qt.DecorationRole)
# ...
A QStyledItemDelegate always calls initStyleOption
at the beginning of most its functions, including paint
and sizeHint
. By overriding that function, we can change some aspects of the drawing, including the icon alignment and positioning.
In this specific case, knowing that we have only two types of icons, and they must be right or left aligned depending on the image, it's enough to update the proper decoration size (based on the optimal size of the icon), and then the alignment based on the column.
class IconDelegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
option.decorationSize = option.icon.actualSize(option.rect.size())
if index.column() & 1:
option.decorationPosition = option.Left
option.decorationAlignment = QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter
else:
option.decorationPosition = option.Right
option.decorationAlignment = QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter
Note: this approach only works as long as the images all have the same ratio, and the row height is not manually changed.
Qt uses roles to store and retrieve various aspects of each index (font, background, etc.), and user roles can be defined to store custom data.
setIcon()
actually sets a QIcon in the item using the DecorationRole
, and the delegate uses it to draw it. If the data returned for that role is empty/invalid, no icon is drawn.
If we don't use setIcon()
but store the icon in the item with a custom role instead, the default drawing functions will obviously not draw any decoration, but we can still access it from the delegate.
# we usually define roles right after the imports, as they're constants
ImageRole = QtCore.Qt.UserRole + 1
class IconDelegate(QtWidgets.QStyledItemDelegate):
def paint(self, painter, option, index):
super().paint(painter, option, index)
icon = index.data(ImageRole)
# ...
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
# ...
it = QtWidgets.QTableWidgetItem()
it.setData(ImageRole, pixmap)