Search code examples
pythonpyqtpyqt5qtableviewqabstracttablemodel

PushButton in Qt Model/View Table


I'm writing a custom TableModel in PyQt5, inheriting from QtCore.QAbstractTableModel. I want my Table to have one column with CheckBoxes only, no text, and one column with a PushButton in each row.

I tried to return a QPushButton object in the data method for the Qt.Display role, but apparently this is not possible.

So my question is: Can I implement in the Model itself that it returns certain widgets for certain cells? In my opinion, that is the job of the model, but how do I achieve this?

My second question would be how I have to assign the slots so that I know from which of the buttons (from which row) the action occurred?


Solution

  • Explanation:

    Can I implement in the Model itself that it returns certain widgets for certain cells? In my opinion, that is the job of the model, but how do I achieve this?

    It could be that the model provides the widgets but it is not common. In general, the model provides the information that will be displayed through a delegate, and is the delegate that can provide widgets. There is also the alternative of setting widgets on the unlinked view of the model using the setIndexWidget method.

    Considering the first case, the solution is to implement a delegate.

    how I have to assign the slots so that I know from which of the buttons (from which row) the action occurred?

    In general the widgets that are used as editors so you must update the model, and then the model can notify other elements through the dataChanged signal as for example if the user wrote a text or changed the status of a checkbox, but in the case of the clicked not notify a permanent change but temporary. Considering this, the delegate could present a signal.

    Solution:

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class PushButtonDelegate(QtWidgets.QStyledItemDelegate):
        clicked = QtCore.pyqtSignal(QtCore.QModelIndex)
    
        def paint(self, painter, option, index):
            if (
                isinstance(self.parent(), QtWidgets.QAbstractItemView)
                and self.parent().model() is index.model()
            ):
                self.parent().openPersistentEditor(index)
    
        def createEditor(self, parent, option, index):
            button = QtWidgets.QPushButton(parent)
            button.clicked.connect(lambda *args, ix=index: self.clicked.emit(ix))
            return button
    
        def setEditorData(self, editor, index):
            editor.setText(index.data(QtCore.Qt.DisplayRole))
    
        def setModelData(self, editor, model, index):
            pass
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
    
        w = QtWidgets.QTableView()
        model = QtGui.QStandardItemModel(0, 2)
        for i in range(10):
            it1 = QtGui.QStandardItem()
            it1.setData(QtCore.Qt.Checked, QtCore.Qt.CheckStateRole)
            it1.setFlags(it1.flags() | QtCore.Qt.ItemIsUserCheckable)
            it2 = QtGui.QStandardItem()
            it2.setData("text-{}".format(i), QtCore.Qt.DisplayRole)
            model.appendRow([it1, it2])
    
        # pass the view as parent
        delegate = PushButtonDelegate(w)
        w.setItemDelegateForColumn(1, delegate)
    
        def on_clicked(index):
            print(index.data())
    
        delegate.clicked.connect(on_clicked)
    
        w.setModel(model)
        w.show()
        sys.exit(app.exec_())