I am attempting to use a QStyledItemDelegate with my QListView to display rich text in the items.
The first time the item is painted its height is too small. If I then mouse over the item it gets repainted with the correct height. Below are screenshots of the initial paint and repaint.
How can I get the initial paint to be the right height?
Example code that demonstrates the issue:
from PySide2 import QtCore, QtGui, QtWidgets
class RichTextItemDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None):
super(RichTextItemDelegate, self).__init__(parent)
self.doc = QtGui.QTextDocument(self)
def paint(self, painter, option, index):
painter.save()
self.initStyleOption(option, index)
self.doc.setHtml(option.text)
option_no_text = QtWidgets.QStyleOptionViewItem(option)
option_no_text.text = ''
style = QtWidgets.QApplication.style() if option_no_text.widget is None else option_no_text.widget.style()
style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, option_no_text, painter)
margin_top = (option.rect.height() - self.doc.size().height()) // 2
text_rect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText, option_no_text, None)
text_rect.setTop(text_rect.top() + margin_top)
painter.translate(text_rect.topLeft())
painter.setClipRect(text_rect.translated(-text_rect.topLeft()))
context = QtGui.QAbstractTextDocumentLayout.PaintContext()
self.doc.documentLayout().draw(painter, context)
painter.restore()
def sizeHint(self, option, index):
other = super().sizeHint(option, index)
w = min(self.doc.idealWidth(), other.width())
h = max(self.doc.size().height(), other.height())
return QtCore.QSize(w, h)
class ExampleWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
item = QtGui.QStandardItem()
item.setText('Example<br><span style="font-size: 14pt; font-weight: bold;">Example<br>Example<br>Example</span>', )
model = QtGui.QStandardItemModel()
model.appendRow(item)
self.listview = QtWidgets.QListView(parent=self)
self.listview.setModel(model)
delegate = RichTextItemDelegate(self.listview)
self.listview.setItemDelegate(delegate)
app = QtWidgets.QApplication([])
example = ExampleWidget()
example.resize(320, 240)
example.show()
app.exec_()
So I believe the initial paint was sized wrong because the sizeHint() function can't return good values until it knows what the item text is. In my original code the text wasn't set until paint() was called.
I used the following for sizeHint() and it seems to work:
def sizeHint(self, option: QtWidgets.QStyleOptionViewItem, index: QtCore.QModelIndex) -> QtCore.QSize:
self.doc.setHtml(index.data())
doc_size = self.doc.size()
doc_width = int(math.ceil(doc_size.width()))
doc_height = int(math.ceil(doc_size.height()))
item_size = super().sizeHint(option, index)
w = min(doc_width, item_size.width())
h = max(doc_height, item_size.height())
return QtCore.QSize(w, h)
Be warned... I do not know what the implications of calling self.doc.setHtml() inside of sizeHint() are. Also, for some reason super().sizeHint() returns a very large width value for me, and I'm not sure why, which is why I call min() instead of max().
Related
I am using a QListView and a QFileSystemModel to display the contents of a directory. I'm trying to emulate the Windows File Explorer, where if the file/folder text is long enough it wraps to display the full name of the object.
As it looks in File Explorer
On my view I've tried setGridSize(QtCore.QSize(80, 80)) to give me enough space, setWordWrap(True), and setTextElideMode(QtCore.Qt.ElideNone)
But the text still gets cropped.
I've looked into using a QStyledItemDelegate in order to wrap the text, but I am unsure how to go about getting the behavior I want.
How can I set the view to show the text wrapping and not cropping any of the text?
Here's the code I've created so far...
import sys
from PySide2 import QtCore
from PySide2 import QtWidgets
from shiboken2 import wrapInstance
class TreeViewDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(TreeViewDialog, self).__init__(parent)
self.setMinimumSize(500, 400)
self.create_widgets()
self.create_layout()
def create_widgets(self):
root_path = r"C:\Users\Documents\Test"
self.model = QtWidgets.QFileSystemModel()
self.model.setRootPath(root_path)
self.list_view = QtWidgets.QListView()
self.list_view.setViewMode(QtWidgets.QListView.IconMode)
self.list_view.setResizeMode(QtWidgets.QListView.Adjust)
self.list_view.setFlow(QtWidgets.QListView.LeftToRight)
self.list_view.setMovement(QtWidgets.QListView.Snap)
self.list_view.setModel(self.model)
self.list_view.setRootIndex(self.model.index(root_path))
self.list_view.setGridSize(QtCore.QSize(80, 80))
self.list_view.setUniformItemSizes(True)
self.list_view.setWordWrap(True)
self.list_view.setTextElideMode(QtCore.Qt.ElideNone)
def create_layout(self):
main_layout = QtWidgets.QHBoxLayout(self)
main_layout.setContentsMargins(2, 2, 2, 2)
main_layout.addWidget(self.list_view)
if __name__ == "__main__":
app = QtWidgets.QApplication.instance()
if not app:
app = QtWidgets.QApplication(sys.argv)
tree_view_dialog = TreeViewDialog()
tree_view_dialog.show()
sys.exit(app.exec_())
So I was able to get the behavior I was looking for by implementing a custom QStyledItemDelegate and implementing the paint() and sizeHint() methods and setting setItemDelegate() on my QListView.
delegate = FileNameDelegate(self)
self.list_view.setItemDelegate(delegate)
Here's my delegate class.
class FileNameDelegate(QtWidgets.QStyledItemDelegate):
"""Delegate to wrap filenames."""
def paint(self, painter, option, index):
if not index.isValid():
return
painter.save()
# Selected
if option.state & QtWidgets.QStyle.State_Selected:
painter.fillRect(option.rect, option.palette.highlight())
# Icon
icon = index.data(QtCore.Qt.DecorationRole)
mode = QtGui.QIcon.Normal
state = QtGui.QIcon.On if option.state & QtWidgets.QStyle.State_Open else QtGui.QIcon.Off
icon_rect = QtCore.QRect(option.rect)
icon_rect.setSize(QtCore.QSize(option.rect.width(), 40))
icon.paint(painter, icon_rect, alignment=QtCore.Qt.AlignCenter | QtCore.Qt.AlignVCenter, mode=mode, state=state)
# Text
text = index.data(QtCore.Qt.DisplayRole)
font = QtWidgets.QApplication.font()
font_metrics = QtGui.QFontMetrics(font)
padding = 8
rect = font_metrics.boundingRect(option.rect.left()+padding/2, option.rect.bottom()-icon_rect.height()+padding/2,
option.rect.width()-padding, option.rect.height()-padding,
QtCore.Qt.AlignHCenter | QtCore.Qt.AlignTop | QtCore.Qt.TextWrapAnywhere,
text)
color = QtWidgets.QApplication.palette().text().color()
pen = QtGui.QPen(color)
painter.setPen(pen)
painter.setFont(font)
painter.drawText(rect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop | QtCore.Qt.TextWrapAnywhere, text)
painter.restore()
def sizeHint(self, option, index):
if not index.isValid():
return super(FileNameDelegate, self).sizeHint(option, index)
else:
text = index.data()
font = QtWidgets.QApplication.font()
font_metrics = QtGui.QFontMetrics(font)
rect = font_metrics.boundingRect(0, 0, option.rect.width(), 0,
QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop | QtCore.Qt.TextWrapAnywhere,
text)
size = QtCore.QSize(option.rect.width(), option.rect.height()+rect.height())
return size
I now get this when running the code.
A custom widget (class name MyLabel, inherits QLabel) has a fixed aspect ratio 16:9.
When I resize my window, the label is top-left aligned unless the window happens to be 16:9, in which case it fills the window perfectly.
How do I get the label to be centered? I have looked at size policies, alignments, using spaceitems and stretch, but I cannot seem to get it working as desired.
Here is a minimal reproducible example:
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow
from PyQt5.QtCore import QSize, Qt
from PyQt5.Qt import QVBoxLayout, QWidget
class MyLabel(QLabel):
def __init__(self, text, parent=None):
super().__init__(text, parent)
self.setStyleSheet("background-color: lightgreen") # Just for visibility
def resizeEvent(self, event):
# Size of 16:9 and scale it to the new size maintaining aspect ratio.
new_size = QSize(16, 9)
new_size.scale(event.size(), Qt.KeepAspectRatio)
self.resize(new_size)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__(None)
# Main widget and layout, and set as centralWidget
self.main_layout = QVBoxLayout()
self.main_widget = QWidget()
self.main_widget.setLayout(self.main_layout)
self.setCentralWidget(self.main_widget)
# Add button to main_layout
label = MyLabel("Hello World")
self.main_layout.addWidget(label)
self.show()
app = QApplication(sys.argv)
ex = MainWindow()
sys.exit(app.exec_())
Examples of desired outcome:
Examples of actual outcome:
Qt unfortunately doesn't provide a straight forward solution for widgets that require a fixed aspect ratio.
There are some traces in old documentation, but the main problem is that:
all functions related to aspect ratio (hasHeightForWidth() etc) for widgets, layouts and size policies are only considered for the size hint, so no constraint is available if the widget is manually resized by the layout;
as the documentation reports changing the geometry of a widget within the moveEvent() or resizeEvent() might lead to infinite recursion;
it's not possible to (correctly) control the size growth or shrinking while keeping aspect ratio;
For the sake of completeness, here's a partial solution to this issue, but be aware that QLabel is a very peculiar widget that has some constraints related to its text representation (most importantly, with rich text and/or word wrap).
class MyLabel(QLabel):
lastRect = None
isResizing = False
def __init__(self, text, parent=None):
super().__init__(text, parent)
self.setStyleSheet("background-color: lightgreen")
self.setScaledContents(True)
def restoreRatio(self, lastRect=None):
if self.isResizing:
return
rect = QRect(QPoint(),
QSize(16, 9).scaled(self.size(), Qt.KeepAspectRatio))
if not lastRect:
lastRect = self.geometry()
rect.moveCenter(lastRect.center())
if rect != lastRect:
self.isResizing = True
self.setGeometry(rect)
self.isResizing = False
self.lastRect = None
def hasHeightForWidth(self):
return True
def heightForWidth(self, width):
if self.pixmap():
return width * self.pixmap().height() / self.pixmap().width()
return width * 9 / 16
def sizeHint(self):
if self.pixmap():
return self.pixmap().size()
return QSize(160, 90)
def moveEvent(self, event):
self.lastRect = self.geometry()
def resizeEvent(self, event):
self.restoreRatio(self.lastRect)
Since the purpose is to display an image, another possibility is to manually paint everything on your own, for which you don't need a QLabel at all, and you can just override the paintEvent of a QWidget, but for performance purposes it could be slightly better to use a container widget with a child QLabel: this would theoretically make things a bit faster, as all the computation is completely done in Qt:
class ParentedLabel(QWidget):
def __init__(self, pixmap=None):
super().__init__()
self.child = QLabel(self, scaledContents=True)
if pixmap:
self.child.setPixmap(pixmap)
def setPixmap(self, pixmap):
self.child.setPixmap(pixmap)
self.updateGeometry()
def updateChild(self):
if self.child.pixmap():
r = self.child.pixmap().rect()
size = self.child.pixmap().size().scaled(
self.size(), Qt.KeepAspectRatio)
r = QRect(QPoint(), size)
r.moveCenter(self.rect().center())
self.child.setGeometry(r)
def hasHeightForWidth(self):
return bool(self.child.pixmap())
def heightForWidth(self, width):
return width * self.child.pixmap().height() / self.child.pixmap().width()
def sizeHint(self):
if self.child.pixmap():
return self.child.pixmap().size()
return QSize(160, 90)
def moveEvent(self, event):
self.updateChild()
def resizeEvent(self, event):
self.updateChild()
Finally, another possibility is to use a QGraphicsView, which is probably the faster approach of all, with a small drawback: the image shown based on the given size hint will probably be slightly smaller (a couple of pixels) than the original, with the result that it will seem a bit "out of focus" due to the resizing.
class ViewLabel(QGraphicsView):
def __init__(self, pixmap=None):
super().__init__()
self.setStyleSheet('ViewLabel { border: 0px solid none; }')
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scene = QGraphicsScene()
self.setScene(scene)
self.pixmapItem = QGraphicsPixmapItem(pixmap)
self.pixmapItem.setTransformationMode(Qt.SmoothTransformation)
scene.addItem(self.pixmapItem)
def setPixmap(self, pixmap):
self.pixmapItem.setPixmap(pixmap)
self.updateGeometry()
self.updateScene()
def updateScene(self):
self.fitInView(self.pixmapItem, Qt.KeepAspectRatio)
def hasHeightForWidth(self):
return not bool(self.pixmapItem.pixmap().isNull())
def heightForWidth(self, width):
return width * self.pixmapItem.pixmap().height() / self.pixmapItem.pixmap().width()
def sizeHint(self):
if not self.pixmapItem.pixmap().isNull():
return self.pixmapItem.pixmap().size()
return QSize(160, 90)
def resizeEvent(self, event):
self.updateScene()
I'm using a QDelegate to display an image in QTableView with rounded corners. The basic implementation is working, but as soon as I resize the whole QMainWindow where the table is located the image stays fixed at it's original position and the grid lines are moved over the image.
How to mount the image to the cell instead of the table?
I've tried QLabel and QPixmap / QPaint with no result. The working example is enclosed.
import pathlib
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
class TableModel(QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def data(self, index, role):
if role == Qt.DisplayRole:
return self._data[index.row()][index.column()]
def rowCount(self, index):
return len(self._data)
def columnCount(self, index):
return len(self._data[0])
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.resize(800,600)
self.table = QTableView()
data = [
['Line 1', 'c:/daten/dummy/image1.png', 19],
['Line 2', 'c:/daten/dummy/image2.png', 29],
['Line 3', 'c:/daten/dummy/image3.png', 39],
['Line 4', 'c:/daten/dummy/image4.png', 49]
]
self.model = TableModel(data)
self.table.setModel(self.model)
self.setCentralWidget(self.table)
self.table.setRowHeight(0, 200)
self.table.setRowHeight(1, 200)
self.table.setRowHeight(2, 200)
self.table.setRowHeight(3, 200)
imageDelegate = ImageDelegate (self.table)
self.table.setItemDelegateForColumn(1, imageDelegate)
header = self.table.horizontalHeader ()
header.setSectionResizeMode (1, QHeaderView.Stretch)
class ImageDelegate(QStyledItemDelegate):
def __init__(self, parent ):
QStyledItemDelegate.__init__ (self, parent)
def paint(self, painter, option, index):
image = str(pathlib.Path(index.data(Qt.DisplayRole)))
imagetoshow = QImage(image)
pixmap = QPixmap.fromImage(imagetoshow)
pixmap = pixmap.scaled(400, 400, Qt.KeepAspectRatio)
#pixmap.set
option.widget.setRowHeight(index.row(), pixmap.height())
option.widget.setColumnWidth(index.row(), 400)
##
brush = QBrush(pixmap)
painter.setRenderHint (QPainter.Antialiasing, True)
painter.setBrush (brush)
painter.drawRoundedRect (option.rect.x(), option.rect.y(), pixmap.width(), pixmap.height(), 20, 20)
app=QApplication(sys.argv)
window=MainWindow()
window.show()
app.exec_()
The main problem is that you are using the pixmap as a brush, and in that case it's treated as a texture, which normally has an origin at (0, 0).
To avoid that you can use setBrushOrigin() with the top left point of the option.rect.
Note that you should always prefer to save() and restore() the state of the painter when changing its properties (font, pen, brush, brush origin, background, render hints, clipping and transformations), and the state must be always restored (every save() has to be matched by a corresponding restore()). This is very important for functions that receive an already constructed QPainter, like in this case with the paint() function.
brush = QBrush(pixmap)
painter.save()
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setBrush(brush)
painter.setBrushOrigin(option.rect.topLeft())
painter.drawRoundedRect(option.rect.x(), option.rect.y(),
pixmap.width(), pixmap.height(), 20, 20)
painter.restore()
On the other hand, you can also use clipping and directly draw the pixmap:
path = QPainterPath()
path.addRoundedRect(option.rect.x(), option.rect.y(),
pixmap.width(), pixmap.height(), 20, 20)
painter.save()
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setClipPath(path)
painter.drawPixmap(option.rect.topLeft(), pixmap)
painter.restore()
Finally, you should never change view properties within the paint event, most importantly because it could cause recursion (and in the best case it will at least cause unnecessary redraws, like in your case), but also because it's not the good way to do so.
Also, it doesn't make much sense that you use setColumnWidth with the index row.
Since you're already using the 400 pixel limit, you can set the second column width Fixed and resize the column for the header, then use ResizeToContents for the vertical header and implement the sizeHint() for the delegate. Alternatively, you can use the ResizeToContents mode for the column too.
class MainWindow(QMainWindow):
def __init__(self):
# ...
header = self.table.horizontalHeader()
header.setSectionResizeMode(1, QHeaderView.Fixed)
header.resizeSection(1, 400)
self.table.verticalHeader().setSectionResizeMode(
QHeaderView.ResizeToContents)
class ImageDelegate(QStyledItemDelegate):
# ...
def sizeHint(self, opt, index):
image = str(pathlib.Path(index.data(Qt.DisplayRole)))
imagetoshow = QImage(image)
pixmap = QPixmap.fromImage(imagetoshow)
pixmap = pixmap.scaled(400, 400, Qt.KeepAspectRatio)
return pixmap.size()
I want to design a custom ListView widget which has custom items similar to this:
https://i.stack.imgur.com/iTNbN.png
However, the qt documentation and some stackoverflow posts state that one should ideally use a QStyleItemDelegate. I never worked with 'delegates' before but as far as I understood from my research they are called by the ListView for drawing / rendering each item.
I found a delegate example in another project (https://github.com/pyblish/pyblish-lite/blob/master/pyblish_lite/delegate.py) and they draw everything by hand / are essentially rebuilding entire widgets by painting rectangles.
This seems a bit impractical for me as most of the time custom item widgets can be compounds of existing widgets. Take a look at the screenshot above. It essentially contains a Qlabel, QPixmap, and four DoubleSpinBoxes.
Question: How would you use the painting / rendering methods that already exist in them instead of manually painting everything on your own?
That way you can profit from existing member methods and can use layouts for structuring your widget.
For example the first ListViewItem should pass the model data to the delegate so that the text of the self.lightGroupName QLabel can be set to "Light1".
Any help is greatly appreciated, since I have no idea how to go on from here:
from PySide2 import QtCore, QtGui, QtWidgets
class LightDelagate(QtWidgets.QStyledItemDelegate): #custom item view
def __init__(self, parent=None):
super(LightDelagate, self).__init__(parent)
self.setupUI()
def setupUI(self):
self.masterWidget = QtWidgets.QWidget()
#Light Group Header
self.hlayLightHeader = QtWidgets.QHBoxLayout()
self.lightGroupName = QtWidgets.QLabel("Checker")
self.hlayLightHeader.addWidget(self.lightGroupName)
#Light AOV Preview
self.lightPreview = QtWidgets.QLabel()
#set size
self.aovThumbnail = QtGui.QPixmap(180, 101)
#self.lightPreview.setPixmap(self.aovThumbnail.scaled(self.lightPreview.width(), self.lightPreview.height(), QtCore.Qt.KeepAspectRatio))
# #Color Dials
# self.hlayColorDials = QtWidgets.QHBoxLayout()
# self.rgbDials = QtWidgets.QHBoxLayout()
# self.rDial = QtWidgets.QDoubleSpinBox()
# self.rDial.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
# self.gDial = QtWidgets.QDoubleSpinBox()
# self.gDial.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
# self.bDial = QtWidgets.QDoubleSpinBox()
# self.bDial.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
# self.rgbDials.addWidget(self.rDial)
# self.rgbDials.addWidget(self.gDial)
# self.rgbDials.addWidget(self.bDial)
# #Exposure
# self.hlayExposureDials = QtWidgets.QHBoxLayout()
# self.exposureDial = QtWidgets.QDoubleSpinBox()
# self.exposureDial.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
# self.hlayExposureDials.addWidget(self.exposureDial)
# self.hlayColorDials.addLayout(self.rgbDials)
# self.hlayColorDials.addLayout(self.hlayExposureDials)
#entire layout
self.vlayWidget = QtWidgets.QVBoxLayout()
self.vlayWidget.addLayout(self.hlayLightHeader)
self.vlayWidget.addWidget(self.lightPreview)
# self.vlayWidget.addLayout(self.hlayColorDials)
self.vlayWidget.setContentsMargins(2,2,2,2)
self.vlayWidget.setSpacing(2)
self.masterWidget.setLayout(self.vlayWidget)
def paint(self, painter, option, index):
rowData = index.model().data(index, QtCore.Qt.DisplayRole)
self.lightGroupName.setText(rowData[0])
print (option.rect)
painter.drawRect(option.rect)
painter.drawText()
def sizeHint(self, option, index):
return QtCore.QSize(200, 150)
class LightListModel(QtCore.QAbstractListModel): #data container for list view
def __init__(self, lightList= None):
super(LightListModel, self).__init__()
self.lightList = lightList or []
#reimplement
def rowCount(self, index):
return len(self.lightList)
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
lightGroupData = self.lightList[index.row()]
return lightGroupData
class LightListView(QtWidgets.QListView): #
def __init__(self):
super(LightListView, self).__init__()
self.setFlow(QtWidgets.QListView.LeftToRight)
self.setItemDelegate(LightDelagate(self))
self.setMinimumWidth(1880)
lightListTest = [
('Light1' , {'lightList' : [], 'lightColor': (0,0,0), 'mod_exposure': 1, 'mod_color' : (0,0,0)}),
('Light2' , {'lightList' : [], 'lightColor': (0,0,0), 'mod_exposure': 1, 'mod_color' : (0,0,0)}),
('Light3' , {'lightList' : [], 'lightColor': (0,0,0), 'mod_exposure': 1, 'mod_color' : (0,0,0)}),
('Light4' , {'lightList' : [], 'lightColor': (0,0,0), 'mod_exposure': 1, 'mod_color' : (0,0,0)})
]
app = QtWidgets.QApplication([])
LLV = LightListView()
model = LightListModel(lightList=lightListTest)
LLV.setModel(model)
LLV.show()
LLV.setSe
app.exec_()
Instead of QListView, could you use QListWidget and override itemWidget? The idea would be that this lets you return a QWidget (with children as per your screenshot) instead of having to implement a QStyledItemDelegate that calls each child widget's paint method.
I build a QListwidget with custom itemwidget this list. The idea I want to change the icon of the item depends on condition. I read about the MVC model, but I couldn't know how to built QStyledItemDelegate to update them.
Now, I delete all items in the list and read them, that works if the list small but when I have a lot of item it takes time.
This code of CostmItemWidget:
class CustomQWidget(QWidget):
def __init__(self, file, parent=None):
super(CustomQWidget, self).__init__(parent)
if file["l_file"]:
pathname = os.path.join(parent.parent.main_script_path, "icons/correct.png")
else:
pathname = os.path.join(parent.parent.main_script_path, "icons/wrong.png")
pixmap = QtGui.QPixmap(pathname)
button = QPushButton()
button.setStyleSheet("padding: 0px;")
button.setFixedSize(16, 16)
# resize pixmap
pixmap = pixmap.scaled(button.size(), QtCore.Qt.KeepAspectRatioByExpanding, QtCore.Qt.SmoothTransformation)
cropOffsetX = (pixmap.width() - button.size().width()) / 2
pixmap = pixmap.copy(cropOffsetX, 0, button.size().width(), button.size().height())
button.setIcon(QtGui.QIcon(pixmap))
button.setIconSize(button.size())
button.setFlat(True)
label = QLabel(file["n_file"])
layout = QHBoxLayout()
layout.addWidget(button, 0)
layout.addWidget(label, 0)
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
And this code of widget content list:
class FileListWidget(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
loadUi(os.path.join(".", "UIFiles", 'filelist_widget.ui'), self)
self.parent = parent
self.refresh_list()
self.list_view.setCurrentRow(0)
self.list_view.itemClicked.connect(self.selected_file)
self.list_view.setStyleSheet("QListWidget::item { padding: 0px; }")
def refresh_list(self):
self.list_view.clear()
if len(self.parent.files) == 0:
return
for index, file in self.parent.files.iterrows():
self.add_item_list(file)
self.parent.image_deleted = False
def add_item_list(self, file):
item = QListWidgetItem(self.list_view)
item.setSizeHint(QSize(item.sizeHint().width(), 20))
item_widget2 = CustomQWidget(file, self)
self.list_view.addItem(item)
self.list_view.setItemWidget(item, item_widget2)
I looking to find a way of applying QStyledItemDelegate and change the icon by a certain signal. The icon of the button in the CustomQWidget and I want to change it when the value of "l_file" from the dictionary is True.
This image of the list I have
I created this delegate to handle put icon inside the custom Widget of the QListItemWidget.
class FileListDelegate(QStyledItemDelegate):
def __init__(self, parent, list_view):
super(FileListDelegate, self).__init__(parent)
# pointer to list
self.list_view = list_view
def paint(self, painter: QtGui.QPainter, option: QStyleOptionViewItem, index: QtCore.QModelIndex) -> None:
painter.save()
item = self.list_view.itemFromIndex(index)
widget = self.list_view.itemWidget(item)
layout = widget.layout()
button = layout.itemAt(0).widget()
if self.list_view.parent().parent().parent.files.loc[widget.index, 'l_file']:
pathname = os.path.join(widget.main_script_path, "icons/correct.png")
else:
pathname = os.path.join(widget.main_script_path, "icons/wrong.png")
pixmap = QtGui.QPixmap(pathname)
button.setIcon(QtGui.QIcon(pixmap))
button.setIconSize(button.size())
painter.restore()