Search code examples
python-3.xpyqt5delegates

How to pass a parameter to createEditor functions of QStyledItemDelegate


A software tool has a table filled with two columns: parameter and data. "Data" column have comboBox widget. How to pass the listData to the createEditor function as a parameter, that I can assign listCombo to the listData?

import sys
from PyQt5.QtWidgets import *
from PyQt5 import QtCore
import pandas as pd

class DataDelegate(QStyledItemDelegate):
    def createEditor(self, parent, opt, index):
        comboBox = QComboBox(parent)
        listCombo = []
        comboBox.addItems(listCombo)
        comboBox.setCurrentIndex(1)
        comboBox.currentTextChanged.connect(lambda: self.commitData.emit(comboBox))
        return comboBox

class Window(QWidget):
    singleton: 'Window' = None

    def __init__(self):
        super(Window, self).__init__()
        self.setWindowTitle("Software tool")
        self.setGeometry(50, 50, 1800, 900)
        self.mainLayout=QHBoxLayout()
        self.setLayout(self.mainLayout)
        self.UI()
        self.table.itemChanged.connect(self._print)

    def UI(self):
        self.sublayouts = {}
        self.buttons = {}
        self._view()
        self._fillTableWidget()
        self.show()

    def _view(self):
        self.table = QTableWidget(0, 2)
        self.table.setHorizontalHeaderLabels(['Parameter', 'Data'])
        self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
        self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
        self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.table.setEditTriggers(QTableWidget.NoEditTriggers)

        self.sublayouts['table'] = QGridLayout()
        self.sublayouts['table'].addWidget(self.table, 1, 0, 4, 4)
        self.sublayouts['table'].setRowStretch(4, 1)

        self.mainLayout.addLayout(self.sublayouts['table'])

        self.table.setItemDelegateForColumn(1, DataDelegate(self.table))

    def _fillTableWidget(self):
        listCol = {
            'Parameters': ['a', 'b', 'c', 'd', 'e'],
            'Data': ['data1', 'data2', 'data3', 'data4', 'data5']}
        self.df = pd.DataFrame(listCol)
        listData = self.df['Data'].to_list()
        print(listData)
        for parameter in self.df['Parameters']:
            rowPosition = self.table.rowCount()
            self.table.insertRow(rowPosition)
            self.table.setItem(rowPosition, 0, QTableWidgetItem(parameter))
            dataItem = QTableWidgetItem()
            self.table.setItem(rowPosition, 1, dataItem)
            self.table.openPersistentEditor(self.table.item(rowPosition, 1))

    def _tableCell(self, text):
        item = QTableWidgetItem()
        item.setText(text)
        return item

    def _print(self):
        print('Item changed:')

def main():
    App=QApplication(sys.argv)
    window =Window()
    sys.exit(App.exec_())

if __name__ == '__main__':
    main()

Solution

  • A possible solution is to set a default list as an instance attribute of the delegate, use that list within createEditor() and overwrite it when the model is retrieved:

    class DataDelegate(QStyledItemDelegate):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.items = []
    
        def setItems(self, items):
            self.items[:] = items
    
        def createEditor(self, parent, opt, index):
            comboBox = QComboBox(parent)
            comboBox.addItems(self.items)
            comboBox.setCurrentIndex(1)
            comboBox.currentTextChanged.connect(
                lambda: self.commitData.emit(comboBox))
            return comboBox
    
    class Window(QWidget):
        # ...
    
        def _view(self):
            # ...
            self.dataDelegate = DataDelegate(self.table)
            self.table.setItemDelegateForColumn(1, self.dataDelegate)
    
        def _fillTableWidget(self):
            listCol = {
                'Parameters': ['a', 'b', 'c', 'd', 'e'],
                'Data': ['data1', 'data2', 'data3', 'data4', 'data5']}
            self.df = pd.DataFrame(listCol)
            listData = self.df['Data'].to_list()
            self.dataDelegate.setItems(listData)
            # ...
    

    Note: the currentTextChanged signal should be used when you actually need it (which is normally required for editable comboboxes, and might not be fired in certain situations); while that signal is usually emitted for uneditable combo boxes even when the same text exists in different items, that behavior might change in the future, and the currentIndexChanged is usually preferred, especially for model editors, since the data should only be updated when the editor is actually submitted.