Insert a widget in model view table cell

I need to add a list of widgets to a table cell. I tried to add QLineEdit in DisplayRloe, does not work:

from PySide2 import QtCore, QtWidgets

class Project:
    def __init__(self, id, project_name):
        self.id = id
        self.name = project_name


class TableModel(QtCore.QAbstractTableModel):
    def __init__(self, data, parent=None):
        QtCore.QAbstractTableModel.__init__(self, parent)
        self._data = data
        self.header = ['ID', 'Name']

    def headerData(self, col, orientation, role):
        if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
            return self.header[col]

    def rowCount(self, parent):
        return len(self._data)

    def columnCount(self, parent):
        return 2

    def data(self, index, role):

        if not index.isValid():
            return

        row = index.row()
        column = index.column()


        if role == QtCore.Qt.DisplayRole:
            if column == 0:
                return self._data[row].id
            else:
                box = QtWidgets.QLineEdit()
                box.setText('X')

                return [box]

app = QtWidgets.QApplication([])

table = QtWidgets.QTableView()
data = [Project(0, 'Project_A'), Project(1, 'Project_B')]
table_model = TableModel(data)
table.setModel(table_model)

table.show()
app.exec_()

Wouldn’t you use QtCore.Qt.EditRole instead of DisplayRole? (I’m just getting back up to speed with the abstract model stuff after not touching it for like 8 years so my memory is super fuzzy).

Thanks, Bob!
Tried to set up via EditRole in both, setData and data methods, did not work as well…

Generally they way you would use model/view architecture is the model stores all your data and the view would actually display that data. You would not store a widget in the model, you would store your data for that cell in the model, but how that data shows up is up to the view. Reading the help file

The items shown in a table view, like those in the other item views, are rendered and edited using standard delegates. However, for some tasks it is sometimes useful to be able to insert widgets in a table instead. Widgets are set for particular indexes with the setIndexWidget() function, and later retrieved with indexWidget().

So if need a particular widget to show up in a specific cell I would look at the TableView class instead. If you just want to be able to edit the info in the cell have a look at this example

    import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt


class TableModel(QtCore.QAbstractTableModel):
	def __init__(self, data):
		super().__init__()
		self._data = data

	
	def data(self, index, role):
		if role == Qt.DisplayRole or role == Qt.EditRole:
			# See below for the nested-list data structure.
			# .row() indexes into the outer list,
			# .column() indexes into the sub-list
			return self._data[index.row()][index.column()]
	
	def setData(self, index, value, role):
		self._data[index.row()][index.column()] = value
		return super(TableModel, self).setData(index, value, role)

	def rowCount(self, index):
		# The length of the outer list.
		return len(self._data)

	def columnCount(self, index):
		# The following takes the first sub-list, and returns
		# the length (only works if all rows are an equal length)
		return len(self._data[0])

	def flags(self, index):  # Qt was imported from PyQt4.QtCore
		if index.column() < 2:
			return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
		else:
			return Qt.ItemIsEnabled | Qt.ItemIsSelectable

class MyDelegate(QtWidgets.QItemDelegate):
	def __init__(self):
		super().__init__()

	def createEditor(self, parent, option, index):
		if index.column() < 2:
			return super(MyDelegate, self).createEditor(parent, option, index)
		else:
			return None

	def setEditorData(self, editor, index):
		if index.column() < 2:
			# Gets display text if edit data hasn't been set.
			text = (index.data(Qt.EditRole) or index.data(Qt.DisplayRole))
			editor.setText(text)		 
   

class MainWindow(QtWidgets.QMainWindow):
	def __init__(self):
		super().__init__()

		self.table = QtWidgets.QTableView()

		data = [
		  ["4", "9", "2"],
		  ["4", "9", "2"],
		  ["4", "9", "2"],
		  ["4", "9", "2"],
		  ["4", "9", "2"]
		]

		self.model = TableModel(data)
		self.table.setModel(self.model)
		self.delegate = MyDelegate()
		self.table.setItemDelegate(self.delegate)
		self.setCentralWidget(self.table)


app=QtWidgets.QApplication(sys.argv)
window=MainWindow()
window.show()
app.exec_()
1 Like

This is exactly what I am looking for, insert a check-box into the cell. But I don`t understand what does “would look at the TableView class” in particular means… I have a custom class “TableModel” inherited from the QAbstractTableModel.

So, before I answer your question, I want to ask: Are you doing this to learn, or are you doing this to get something working right now? If you’re doing this to learn, then skip to the next section after the line break.

If, however, you’re doing this to get something working, then you should either use a QTableWidget (which has a built-in already-created model for you to use), or use your QTableView with the QStandardItemModel. Both the QTableWidgetItem and QStandardItem objects have .setCheckState methods that already handle putting checkboxes in your cells.
When writing tools, I have only come across one case that I couldn’t handle by using the QStandardItemModel and subclassing QStandardItem (and even then, I could have still forced it to work)


So the answer to this can be handled in like 3 ways that I know of.

First, and easiest IMO is to set the Qt.ItemIsUserCheckable flag in the flags method, and then set the data for a Qt.CheckState role on the item you want to be checkable to Qt.Checked. The default mechanism for drawing editors already handles this. This only works for checkboxes, but it’s built-in, so I say use it.

Second is to work with a QStyledItemDelegate
From its documentation here https://doc.qt.io/qt-5/qstyleditemdelegate.html

When displaying data from models in Qt item views, e.g., a QTableView, the individual items are drawn by a delegate. Also, when an item is edited, it provides an editor widget, which is placed on top of the item view while editing takes place. QStyledItemDelegate is the default delegate for all Qt item views, and is installed upon them when they are created.

So you can subclass the QStyledItemDelegate, and set the delegate on the items/rows/columns you need. Then I would probably make the editors persistent for those items so the editor widgets don’t go away when you select something else.

Finally, You could use the QItemEditorFactory. This is how Qt natively creates the editor widgets for the item view. I have never messed with this, so I can’t really explain much other than to give you a usage example I found: https://doc.qt.io/archives/4.6/itemviews-coloreditorfactory.html

1 Like

I concur with everything said above, and would add a forth way of doing this. You can use QTableWidget and use setCellWidget method to add a widget to a particular cell as well, this could be a button or progress bar or whatever. From there you can connect whatever widget you created via a signal, like you would any widget. Because that new widget becomes a child of the table you can grab the table object from that widget via sender().parent().parent() , I think it really all depends on the use case. I believe for ease of use the first method mentioned above is the easiest to implement and manage, the other two are a bit more involved and there is too much reimplementing and rewriting of the code that is already included internally.

1 Like

Oh snap, I forgot you could do that. Good call

Hi, Tyler
If I use QStandardItemModel instead of QAbstractTableModel how should I set and retrieve data from the table?

class TableModel(QtGui.QStandardItemModel):
    def __init__(self, data, parent=None):
        QtGui.QStandardItemModel.__init__(self, parent)
        self._data = data
        self.header = ['ID', 'Name']

The data method is no longer working and my table appears empty.

The QStandardItemModel is the model that displays data stored in QStandardItems.
So you don’t set the data on the model, you set it on the items themselves.
https://doc.qt.io/qt-5/qstandarditem.html#setData

You can probably get away without even subclassing either of them. If you do subclass, you’ll probably subclass the item, not the model.

So you’ll need a loop that builds the QStandardItems and then set them to your model using QStandardItemModel.setItem https://doc.qt.io/qt-5/qstandarditemmodel.html#setItem

Double clicking the cell will create and display a QComboBox widget so user can select the item from this widget.

import sys
from PySide import QtCore
from PySide import QtGui


class Delegate(QtGui.QItemDelegate):
    def __init__(self, parent=None):
        QtGui.QItemDelegate.__init__(self, parent)
        self._data = [ 'Project_A', 'Project_B', 'Project_C']

    def createEditor(self, parent, option, index):
        model_value = index.model().data(index, QtCore.Qt.DisplayRole)

        editor = QtGui.QComboBox(parent)
        editor.addItems(self._data)

        return editor

    def setEditorData(self, editor, index):

        model_value = index.model().data(index, QtCore.Qt.EditRole)

        current_index = editor.findText(model_value)
        if current_index > 0:
            editor.setCurrentIndex(current_index)

    def setModelData(self, editor, model, index):
        editor_value = editor.currentText()
        model.setData(index, editor_value, QtCore.Qt.EditRole)


class Project:
    def __init__(self, id, project_name):
        self.id = id
        self.name = project_name


class TableModel(QtCore.QAbstractTableModel):
    def __init__(self, data, parent=None):
        QtCore.QAbstractTableModel.__init__(self, parent)
        self._data = data
        self.header = ['ID', 'Name']

    def flags(self, index):

        column = index.column()
        if column == 1:
            return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable

        return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable

    def headerData(self, col, orientation, role):
        if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
            return self.header[col]

    def rowCount(self, parent):
        return len(self._data)

    def columnCount(self, parent):
        return 2

    def data(self, index, role):

        if not index.isValid():
            return

        row = index.row()
        column = index.column()


        if role == QtCore.Qt.DisplayRole:
            if column == 0:
                return self._data[row].id

            if column == 1:
                return self._data[row].name

    def setData(self, index, cell_data, role=QtCore.Qt.EditRole):
        """
        When table cell is edited
        """

        row = index.row()
        column = index.column()

        if role == QtCore.Qt.EditRole:

            if column == 1:
                self._data[row].name = cell_data

            return True

app = QtGui.QApplication([])

table = QtGui.QTableView()
data = [Project(0, 'Project_A'), Project(1, 'Project_B')]
table_model = TableModel(data)
table.setModel(table_model)
delegate =  Delegate(table)
table.setItemDelegateForColumn(1, delegate)
table.show()

app.exec_()