Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. Using multiple item delegates in QTableView columns
Forum Updated to NodeBB v4.3 + New Features

Using multiple item delegates in QTableView columns

Scheduled Pinned Locked Moved Solved Qt for Python
6 Posts 3 Posters 2.6k Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • B Offline
    B Offline
    BamboozledBaboon
    wrote on 3 Oct 2022, 18:05 last edited by BamboozledBaboon 10 Mar 2022, 18:12
    #1

    I've made two different QItemDelegate subclasses, one for presenting a combobox to edit values, the other to present a checkbox.

    Problem is when using .tableView.setItemDelegateForColumn I can set a column to use one subclass OR the other, but I cannot set two different columns to a different delegate at the same time. When I try I just get a empty window that flashes on the screen then the program quits.

    I've tried to string together 5 different .py files into one working file pasted below. Apologies in advance if I screwed this up:

    import sys
    from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
        QMetaObject, QObject, QPoint, QRect,
        QSize, QTime, QUrl, Qt, QAbstractTableModel, QModelIndex)
    from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
        QFont, QFontDatabase, QGradient, QIcon,
        QImage, QKeySequence, QLinearGradient, QPainter,
        QPalette, QPixmap, QRadialGradient, QTransform)
    from PySide6.QtWidgets import (QApplication, QHeaderView, QMainWindow, QMenuBar,
        QSizePolicy, QStatusBar, QTableView, QWidget, QItemDelegate)
    
    from PySide6 import QtWidgets
    
    class Ui_MainWindow(object):
        def setupUi(self, MainWindow):
            if not MainWindow.objectName():
                MainWindow.setObjectName(u"MainWindow")
            MainWindow.resize(942, 787)
            self.centralwidget = QWidget(MainWindow)
            self.centralwidget.setObjectName(u"centralwidget")
            self.tableView = QTableView(self.centralwidget)
            self.tableView.setObjectName(u"tableView")
            self.tableView.setGeometry(QRect(70, 60, 781, 591))
            MainWindow.setCentralWidget(self.centralwidget)
            self.menubar = QMenuBar(MainWindow)
            self.menubar.setObjectName(u"menubar")
            self.menubar.setGeometry(QRect(0, 0, 942, 22))
            MainWindow.setMenuBar(self.menubar)
            self.statusbar = QStatusBar(MainWindow)
            self.statusbar.setObjectName(u"statusbar")
            MainWindow.setStatusBar(self.statusbar)
    
            self.retranslateUi(MainWindow)
    
            QMetaObject.connectSlotsByName(MainWindow)
        # setupUi
    
        def retranslateUi(self, MainWindow):
            MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None))
        # retranslateUi
    
    
    class ComboBoxDelegate(QItemDelegate):
    
        def __init__(self, parent=None):
            super(ComboBoxDelegate, self).__init__(parent)
            self.items = ['one', 'two', 'three']
    
        def setItems(self, items):
            self.items = items
    
        def createEditor(self, widget, option, index):
            editor = QComboBox(widget)
            editor.addItems(self.items)
            return editor
    
        def setEditorData(self, editor, index):
            value = index.model().data(index, Qt.EditRole)
            if value:
                editor.setCurrentIndex(int(value))
    
        def setModelData(self, editor, model, index):
            model.setData(index, editor.currentIndex(), Qt.EditRole)
    
        def updateEditorGeometry(self, editor, option, index):
            editor.setGeometry(option.rect)
    
        def paint(self, painter, option, index):
            text = self.items[index.row()]
            option.text = text
            QtWidgets.QApplication.style().drawControl(QtWidgets.QStyle.CE_ItemViewItem, option, painter)
    
    
    class CheckBoxDelegate(QtWidgets.QItemDelegate):
        """
        A delegate that places a fully functioning QCheckBox cell of the column to which it's applied.
        """
        def __init__(self, parent=None):
            QtWidgets.QItemDelegate.__init__(self, parent)
    
        def createEditor(self, parent, option, index):
            """
            Important, otherwise an editor is created if the user clicks in this cell.
            """
            return None
    
        def paint(self, painter, option, index):
            """
            Paint a checkbox without the label.
            """
            self.drawCheck(painter, option, option.rect, QtCore.Qt.Unchecked if int(index.data()) == 0 else QtCore.Qt.Checked)
    
        def editorEvent(self, event, model, option, index):
            '''
            Change the data in the model and the state of the checkbox
            if the user presses the left mousebutton and this cell is editable. Otherwise do nothing.
            '''
            if not int(index.flags() & QtCore.Qt.ItemIsEditable) > 0:
                return False
    
            if event.type() == QtCore.QEvent.MouseButtonRelease and event.button() == QtCore.Qt.LeftButton:
                # Change the checkbox-state
                self.setModelData(None, model, index)
                return True
    
            return False
    
    
        def setModelData (self, editor, model, index):
            '''
            The user wanted to change the old state in the opposite.
            '''
            model.setData(index, 1 if int(index.data()) == 0 else 0, QtCore.Qt.EditRole)
    
    
    class ExampleTableModel(QAbstractTableModel):
        def __init__(self, data):
            super(ExampleTableModel, self).__init__()
            self._data = data
    
    
        def rowCount(self, parent=QModelIndex()):
            return len(self._data)
    
    
        def columnCount(self, parent=QModelIndex()):
            try:
                return len(self._data[0])
            except:
                return 0
    
    
        def data(self, index, role=Qt.DisplayRole):
            if index.isValid():
                if role == Qt.DisplayRole:
                    val = self._data[index.row()][index.column()]
                    return str(val)
            return None
    
    
        def headerData(self, section, orientation, role=Qt.DisplayRole):
            if role == Qt.DisplayRole:
                if orientation == Qt.Horizontal:
                    return f'Col {section}'
    
                if orientation == Qt.Vertical:
                    return f'Row {section}'
    
    
        def setData(self, index, value, role=Qt.EditRole):
            if index.isValid():
                if role==Qt.EditRole:
                    self._data[index.row()][index.column()] = value
                    self.dataChanged.emit(index, index)
                    return True
            return False
    
    
        def flags(self, index):
            if index.isValid():
                return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
            return Qt.ItemIsEnabled
    
    
        def insertRows(self, row, count, parent=QModelIndex()):
            self.beginInsertRows(QModelIndex(), row, row + count - 1)
    
            for n in range(count):
                self._data.insert(row + n, [None] * len(self._data[0]))
    
            self.endInsertRows()
            return True
    
    
        def insertColumns(self, column, count, parent=QModelIndex()):
            self.beginInsertColumns(QModelIndex(), column, column + count - 1)
    
            for r in range(len(self._data)):
                for c in range(count):
                    self._data[r].insert(column + c, None)
    
            self.endInsertColumns()
            return True
    
    
        def removeRows(self, row, count, parent=QModelIndex()):
            self.beginRemoveRows(QModelIndex(), row, row + count - 1)
    
            del self._data[row:row+count]
    
            self.endRemoveRows()
            return True
    
    
        def removeColumns(self, column, count, parent=QModelIndex()):
            self.beginRemoveColumns(QModelIndex(), column, column + count - 1)
    
            for r in range(len(self._data)):
                del self._data[r][column:column+count]
    
            self.endRemoveColumns()
            return True
    
    
    class MainWindow(QMainWindow):
        def __init__(self):
            super(MainWindow, self).__init__()
            self.ui = Ui_MainWindow()
            self.ui.setupUi(self)
    
            self.model = ExampleTableModel([['0, 0', 0, 'one'],
                                            ['1, 0', 1, 'two'],
                                            ['2, 0', 1, 'three']])
    
    
            d1 = CheckBoxDelegate()
            d2 = ComboBoxDelegate()
            # HERE IS THE PROBLEM - I can comment either one of the two following lines and the script runs as expected.
            # but I cannot run both lines:
            self.ui.tableView.setItemDelegateForColumn(1, d1)
            self.ui.tableView.setItemDelegateForColumn(2, d2)
    
            self.ui.tableView.setModel(self.model)
    
    
    
    if __name__ == '__main__':
        app = QApplication()
        win = MainWindow()
        win.show()
        sys.exit(app.exec())
    
    1 Reply Last reply
    0
    • SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on 3 Oct 2022, 19:27 last edited by
      #2

      Hi,

      self.d1 = CheckBoxDelegate()
      self.d2 = ComboBoxDelegate()
      self.ui.tableView.setItemDelegateForColumn(1, self.d1)
      self.ui.tableView.setItemDelegateForColumn(2, self.d2)
      

      You also need to add:

      from PySide6 import QtCore
      from PySide6.QtWidget import QComboBox
      

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      1 Reply Last reply
      1
      • B Offline
        B Offline
        BamboozledBaboon
        wrote on 3 Oct 2022, 19:56 last edited by BamboozledBaboon 10 Mar 2022, 19:57
        #3

        Thank you @SGaist , that works!

        Can you elaborate on what's going on here that the delegates need to be initialized as part of the instance and why worked previously with just one delegate but not with two?

        The examples I had been working with just called something like self.ui.tableView.setItemDelegateForColumn(1, CheckBoxDelegate()) for example. I just so happened to assign them to d1 and d2 when I was screwing with it.

        JonBJ 1 Reply Last reply 3 Oct 2022, 20:37
        0
        • B BamboozledBaboon
          3 Oct 2022, 19:56

          Thank you @SGaist , that works!

          Can you elaborate on what's going on here that the delegates need to be initialized as part of the instance and why worked previously with just one delegate but not with two?

          The examples I had been working with just called something like self.ui.tableView.setItemDelegateForColumn(1, CheckBoxDelegate()) for example. I just so happened to assign them to d1 and d2 when I was screwing with it.

          JonBJ Offline
          JonBJ Offline
          JonB
          wrote on 3 Oct 2022, 20:37 last edited by JonB 10 Mar 2022, 20:38
          #4

          @BamboozledBaboon
          It's not that it needs to be "initialized". This is a Python issue. By assigning the self.d2 = ComboBoxDelegate() to a member variable @SGaist is tying its lifetime to the instance. With only a local variable Python destroys it at the end of your constructor code.

          The key here is at PySide6.QtWidgets.QAbstractItemView.setItemDelegateForColumn(column, delegate)

          QAbstractItemView does not take ownership of delegate.

          Note that not. This means you are still responsible for keeping the delegate alive.

          As for your "one delegate works but not two", I suspect you will find that is "coincidence". Maybe the second one made the problem show up more.

          B 1 Reply Last reply 3 Oct 2022, 21:04
          1
          • JonBJ JonB
            3 Oct 2022, 20:37

            @BamboozledBaboon
            It's not that it needs to be "initialized". This is a Python issue. By assigning the self.d2 = ComboBoxDelegate() to a member variable @SGaist is tying its lifetime to the instance. With only a local variable Python destroys it at the end of your constructor code.

            The key here is at PySide6.QtWidgets.QAbstractItemView.setItemDelegateForColumn(column, delegate)

            QAbstractItemView does not take ownership of delegate.

            Note that not. This means you are still responsible for keeping the delegate alive.

            As for your "one delegate works but not two", I suspect you will find that is "coincidence". Maybe the second one made the problem show up more.

            B Offline
            B Offline
            BamboozledBaboon
            wrote on 3 Oct 2022, 21:04 last edited by
            #5

            @JonB said in Using multiple item delegates in QTableView columns:

            As for your "one delegate works but not two", I suspect you will find that is "coincidence". Maybe the second one made the problem show up more.

            It's not quite so random though. I can run either subclass individually without problems, or I could even run one instance of a delegate in multiple columns. The issue was when I tried to run the second subclass or a second instance of the same subclass.

            JonBJ 1 Reply Last reply 3 Oct 2022, 21:16
            0
            • B BamboozledBaboon
              3 Oct 2022, 21:04

              @JonB said in Using multiple item delegates in QTableView columns:

              As for your "one delegate works but not two", I suspect you will find that is "coincidence". Maybe the second one made the problem show up more.

              It's not quite so random though. I can run either subclass individually without problems, or I could even run one instance of a delegate in multiple columns. The issue was when I tried to run the second subclass or a second instance of the same subclass.

              JonBJ Offline
              JonBJ Offline
              JonB
              wrote on 3 Oct 2022, 21:16 last edited by
              #6

              @BamboozledBaboon
              This may be due to the vagaries of Python and when it actually destroys unreferenced objects. I believe it is still not correct with even a single instance used once. If you attach a slot to d1.destroyed.connect() that should indicate when it gets destroyed.

              1 Reply Last reply
              1

              1/6

              3 Oct 2022, 18:05

              • Login

              • Login or register to search.
              1 out of 6
              • First post
                1/6
                Last post
              0
              • Categories
              • Recent
              • Tags
              • Popular
              • Users
              • Groups
              • Search
              • Get Qt Extensions
              • Unsolved