1

I want to have a QListView which displays custom widgets. I guess the best way to do this would be a QItemDelegate. Unfortunately I don't quite understand how to subclass it correctly and how to implement the paint() method, which seems to be the most important one. I couldn't find anything about using a delegate to create another widget.

I already tried to implement something similar without a delegate, but that didn't work out that well, because QListView is not supposed to display widgets.

import sys
from PyQt4 import QtCore
from PyQt4 import QtGui

class Model(QtCore.QAbstractListModel):

    def __init__(self, parent=None):
        super(QtCore.QAbstractListModel, self).__init__(parent)
        self._widgets = []


    def headerData(self, section, orientation, role):
        """ Returns header for columns """
        return "Header"


    def rowCount(self, parentIndex=QtCore.QModelIndex()):
        """ Returns number of interfaces """
        return len(self._widgets)


    def data(self, index, role):
        """ Returns the data to be displayed """
        if role == QtCore.Qt.DisplayRole:
            row = index.row()
            return self._widgets[row]


    def insertRow(self, widget, parentIndex=QtCore.QModelIndex()):
        """ Inserts a row into the model """
        self.beginInsertRows(parentIndex, 0, 1)
        self._widgets.append(widget)
        self.endInsertRows()


class Widget(QtGui.QWidget):

    def __init__(self, parent=None, name="None"):
        super(QtGui.QWidget, self).__init__(parent)
        self.layout = QtGui.QHBoxLayout()
        self.setLayout(self.layout)
        self.checkbox = QtGui.QCheckBox()
        self.button = QtGui.QPushButton(self)
        self.label = QtGui.QLabel(self)
        self.label.setText(name)
        self.layout.addWidget(self.checkbox)
        self.layout.addWidget(self.button)
        self.layout.addWidget(self.label)

class Window(QtGui.QMainWindow):

    def __init__(self, parent=None):
        super(QtGui.QMainWindow, self).__init__(parent)
        self.view = QtGui.QListView(self)
        self.model = Model()
        self.view.setModel(self.model)
        self.setCentralWidget(self.view)

        self.model.insertRow(
            widget=Widget(self)
        )
        self.model.insertRow(
            widget=Widget(self)
        )
        self.model.insertRow(
            widget=Widget(self)
        )
        self.model.insertRow(
            widget=Widget(self)
        )

        self.show()


app = QtGui.QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())

So, how would I need to implement a delegate in order to do what I want?

5
  • 1
    How about a table widget with 3 columns, one for the checkbox, one for the button and one for the label ? I like to avoid delegates when I can.
    – Mel
    Commented Dec 8, 2015 at 14:06
  • Well, that's a good idea, haven't thought about that yet. But can I still use a model when doing it like that?
    – vicco
    Commented Dec 8, 2015 at 14:16
  • The model would be inside the table widget, and you wouldn't have to subclass anything. Why do you need the model for ?
    – Mel
    Commented Dec 8, 2015 at 14:21
  • How can it be in the widget? Actually I don't really need it, I just thought it would be nicer because that way I can use the data inside the model and display somewhere else, too.
    – vicco
    Commented Dec 8, 2015 at 14:25
  • 1
    Table widget, list widget and tree widget are convenience class with model+view inside a same widget. You can still keep your widget in a list somewhere: have an "insert widget" method that appends to the list and to the table widget.
    – Mel
    Commented Dec 8, 2015 at 14:41

1 Answer 1

1

Here's an example of a QTableWidget with a button and text on each row. I defined an add_item method to add a whole row at once: insert a new row, put a button in column 0, put a regular item in column 1.

import sys
from PyQt4 import QtGui,QtCore

class myTable(QtGui.QTableWidget):      
    def __init__(self,parent=None):
        super(myTable,self).__init__(parent)
        self.setColumnCount(2)

    def add_item(self,name):
        #new row
        row=self.rowCount()
        self.insertRow(row)

        #button in column 0
        button=QtGui.QPushButton(name)
        button.setProperty("name",name)
        button.clicked.connect(self.on_click)
        self.setCellWidget(row,0,button)

        #text in column 1
        self.setItem(row,1,QtGui.QTableWidgetItem(name))

    def on_click(self):
        # find the item with the same name to get the row
        text=self.sender().property("name")
        item=self.findItems(text,QtCore.Qt.MatchExactly)[0]
        print("Button click at row:",item.row())

if __name__=='__main__':
    app = QtGui.QApplication(sys.argv)      
    widget = myTable()
    widget.add_item("kitten")
    widget.add_item("unicorn")
    widget.show()
    sys.exit(app.exec_())

Bonus: how to know on which button did the user clicked ? A button doesn't have a row property, but we can create one when we instantiate the buttons, like so:

button.setProperty("row",row)

Problem is, if you sort your table or delete a row, the row numbers will not match any more. So instead we set a "name" property, same as the text of the item in column 1. Then we can use findItems to get the row (see on_click).

Not the answer you're looking for? Browse other questions tagged or ask your own question.