Making a list of widgets should be simple, yet it is dauntingly complex.
-
What one would expect is the QListWidget approach. You make a widget and add it to the list. But this is not how one is supposed to do as this support only static widgets. So QListView and custom delegate.
I'm trying to do something relatively simple :
Just a list with a thumbnail, a title, a combobox and a button. Sounds easy right? Well 2 days in I'm still banging my head on the wall.
Why this complexity is not abstracted for the user? I understand QListWidget is not efficient because it creates many widget so for big lists it's inefficient. But it shouldn't need to. Qt could just generate the paint function from the widget you supply as template + the model data.
My second question is just how can I make it? Do I need to :
- Paint manually the thumbnail, text (those 2 are not too hard) but the combobox? Do I need to draw a rectangle the text and the arrow myself? The button?
- Make a widget class for my items anyway because it's needed for the createEditor for when the user click the item such that the thing can actually be modified and not just painted?
How am I ever going to get the editor and the painting to be the exactly the same ? Especially with the different stylesheets and so on.
I remember there's a star delegate example, but I also remember it didn't help much last time. Is there not a simple example with regular widgets like combobox checkboxes and buttons?
Thanks very much.
Below my 2 attemps :class FileListDelegate(QStyledItemDelegate): thumbnail_size = QtCore.QSize(150, 100) def __init__(self, parent=None): super(FileListDelegate, self).__init__(parent) self.combobox, self.button = self.createEditor(option.widget, option, index) def createEditor(self, parent, option, index): # Create combobox and button editors combobox = QComboBox(parent) button = QPushButton("Details", parent) return combobox, button def setEditorData(self, editor, index): # Retrieve data for the current index and update editor accordingly fileData = index.data(QtCore.Qt.DisplayRole) if isinstance(editor, QComboBox): editor.clear() versions = fileData["versions"] for version in versions: editor.addItem(version) index = editor.findText(fileData["CurrentVersion"]) if index >= 0: editor.setCurrentIndex(index) def setModelData(self, editor, model, index): # Update the model with the selected version from the combobox if isinstance(editor, QComboBox): fileData = index.data(QtCore.Qt.DisplayRole) fileData["currentVersion"] = editor.currentText() model.setData(index, fileData, QtCore.Qt.EditRole) def paint(self, painter, option, index): # Get the data for the current index fileData = index.data(QtCore.Qt.DisplayRole) # Draw the thumbnail thumbnail_rect = QtCore.QRect(option.rect.topLeft(), self.thumbnail_size) thumbnail = QtGui.QPixmap(f"{modPath}/thumbnail.png") #QtGui.QPixmap(fileData["localUrl"]) painter.drawPixmap(thumbnail_rect, thumbnail) # Draw the name label name_rect = QtCore.QRect(option.rect.left(), thumbnail_rect.bottom() + 10, option.rect.width(), 20) painter.drawText(name_rect, QtCore.Qt.AlignCenter, fileData["custFileName"]) # Set the geometries of the combobox and button self.combobox.setGeometry(option.rect.left(), name_rect.bottom() + 10, option.rect.width() // 2, 20) self.button.setGeometry(self.combobox.geometry().translated(self.combobox.geometry().width() + 10, 0)) # Draw the combobox and button self.combobox.show() self.button.show() def sizeHint(self, option, index): item_height = self.thumbnail_size.height() + 10 + 20 + 10 # Thumbnail height + spacing + name label height + spacing item_width = option.rect.width() return QtCore.QSize(item_width, item_height) def editorEvent(self, event, model, option, index): # Handle any custom editor event handling, such as button clicks pass
Attemp 2 :
class FileItemWidget(QWidget): def __init__(self, fileData, parent=None): super(FileItemWidget, self).__init__(parent) self.thumbnail_label = QLabel() self.name_label = QLabel() self.combobox = QComboBox() self.button = QPushButton("Details") h_layout = QHBoxLayout() h_layout.addWidget(self.combobox) h_layout.addWidget(self.button) layout = QVBoxLayout() layout.addWidget(self.thumbnail_label) layout.addWidget(self.name_label) layout.addLayout(h_layout) self.setLayout(layout) self.setFileData(fileData) def setFileData(self, fileData): # Update the widget with the file data self.thumbnail_label.setPixmap(QPixmap(f"{modPath}/thumbnail.png")) # Set thumbnail pixmap self.name_label.setText(fileData["custFileName"]) # Set file name self.combobox.clear() versions = fileData["versions"] for version in versions: self.combobox.addItem(version) index = self.combobox.findText(fileData["CurrentVersion"]) if index >= 0: self.combobox.setCurrentIndex(index) def getVersionName(self): return self.combobox.currentText() class FileListDelegate(QStyledItemDelegate): def __init__(self, parent=None): super(FileListDelegate, self).__init__(parent) def createEditor(self, parent, option, index): fileData = index.data(QtCore.Qt.DisplayRole) # Create the custom FileItemWidget as the editor editor = FileItemWidget(fileData, parent) return editor def setEditorData(self, editor, index): # Retrieve data for the current index and update editor accordingly fileData = index.data(QtCore.Qt.DisplayRole) editor.show() editor.setText("fsdfsdf") if isinstance(editor, FileItemWidget): editor.setFileData(fileData) def setModelData(self, editor, model, index): # Update the model with the selected version from the combobox if isinstance(editor, FileItemWidget): fileData = index.data(QtCore.Qt.DisplayRole) fileData["currentVersion"] = FileItemWidget.getVersionName() model.setData(index, fileData, QtCore.Qt.EditRole) def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect) def sizeHint(self, option, index): return QtCore.QSize(200, 150)
-
@Paddle said in Making a list of widgets should be simple, yet it is dauntingly complex.:
Paint manually the thumbnail, text (those 2 are not too hard) but the combobox? Do I need to draw a rectangle the text and the arrow myself? The button?
No, there is this, which provides everything to style/paint a comboBox.
Make a widget class for my items anyway because it's needed for the createEditor for when the user click the item such that the thing can actually be modified and not just painted?
The delegate only uses the real widgets, when you "activate" it (in editing mode), otherwise it's just "flat", so it doesn't need too much resources.
Here's another delegate example: