Elias
Elias

Reputation: 535

PyQt: Using QTextEdit as editor in a QStyledItemDelegate

I am looking for the clean way to use a multi line text edit in the my QStyledItemDelegate. The createEditor implementation is pretty straight forward by returning a QTextEdit instance

def createEditor(self, parent, option, index):
    return QtGui.QTextEdit("Some text")

But setModelData expects a Edit Widget derived from QWidget as a parameter instead of QTextEdits base QScrollArea. The Qt Documentation also tells me (at least in the PyQt Doc) that the setModelData function tries to get the data from the QWidget UserData field. But without having an Edit Widget derived from QWidget there is no option in setting the data. Currently it throws a AttributeError because it can't find text() on the editor.

Is there some proven way to use a non-QWidget editor? Or am I just missing some Widget to do that?

Currently I quick fixed the issue by instanciating a QLineEdit with the QTextEdit data from toPlainText() and passing this to setModelData. Very Hacky!! I could also use duck typing and just implement a text() method on a QTextEdit derivate. But still not a nice way, isn't it? What is a way to do this in C++?

Upvotes: 3

Views: 3196

Answers (2)

ekhumoro
ekhumoro

Reputation: 120568

The minimum requirements for an item-delegate are very simple (see the Model/View Overview for more details). Just create a subclass of QStyledItemDelegate and reimplement the methods createEditor, setEditorData, and setModelData:

class Delegate(QStyledItemDelegate):

    def createEditor(self, parent, options, index):
        return QtGui.QTextEdit(parent)

    def setEditorData(self, editor, index):
        editor.setText(index.data())

    def setModelData(self, editor, model, index):
        model.setData(index, editor.toPlainText())

With this approach, it's easy to support several different types of editor via the same delegate. For example, an isinstance check could be used to decide which type of editor to use. Below is a basic demo that shows how to do this:

screenshot

import random
from PyQt5 import QtCore, QtGui, QtWidgets

class ItemDelegate(QtWidgets.QStyledItemDelegate):
    def createEditor(self, parent, options, index):
        column = index.column()
        if column == 0:
            return QtWidgets.QTextEdit(parent)
        elif column == 1:
            return QtWidgets.QComboBox(parent)
        elif column == 2:
            return QtWidgets.QSpinBox(parent)

    def setEditorData(self, editor, index):
        if isinstance(editor, QtWidgets.QTextEdit):
            editor.setText(index.data())
        elif isinstance(editor, QtWidgets.QComboBox):
            editor.addItems('Red Blue Green'.split())
            editor.setCurrentIndex(editor.findText(index.data()))
        elif isinstance(editor, QtWidgets.QSpinBox):
            editor.setValue(int(index.data()))

    def setModelData(self, editor, model, index):
        if isinstance(editor, QtWidgets.QTextEdit):
            model.setData(index, editor.toPlainText())
        elif isinstance(editor, QtWidgets.QComboBox):
            model.setData(index, editor.currentText())
        elif isinstance(editor, QtWidgets.QSpinBox):
            model.setData(index, editor.value())

class Window(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.table = QtWidgets.QTableWidget(5, 3)
        self.table.setItemDelegate(ItemDelegate(self))
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.table)
        colours = 'Red Blue Green'.split()
        for row in range(5):
            for column in range(4):
                item = QtWidgets.QTableWidgetItem()
                if column == 1:
                    item.setData(QtCore.Qt.EditRole, random.choice(colours))
                elif column == 2:
                    item.setData(QtCore.Qt.EditRole, random.randint(0, 99))
                self.table.setItem(row, column, item)

if __name__ == '__main__':

    app = QtWidgets.QApplication(['Test'])
    window = Window()
    window.setGeometry(600, 100, 375, 225)
    window.show()
    app.exec()

Upvotes: 7

Elias
Elias

Reputation: 535

So I took some time and digged deeper into the problem. There are in fact multiple cleaner solutions:

  1. Implementing a Property I got it to work properly by implementing a QProperty on a subclass of QTextEdit which adds the text property as USER property to the object. Here is my code:

    class DelegatableTextEdit(QtGui.QTextEdit):
    
        @pyqtProperty(str, user=True)
        def text(self):
    
            return self.toPlainText()
    
        @text.setter
        def text(self, text):
    
            self.setText(text)
    
  2. Using the Factory There seems to be an way to solve this by using the Delegates setDefaultFactory() method which then takes a QItemEditorFactory which has registered a custom editor creator by using the registerEditor() function. You can register a custom implementation of the QItemEditorCreatorBase class there which has a overriden createWidget method (and if needed a valuePropertyName function as well). But I haven't tried this one yet

I decided to take the first solution which only needs the custom QTextEdit and the overriden createEditor() function. Both should work in C++

Upvotes: 1

Related Questions