I am trying to update my model when a combo box is changed. I have a function connected to the combobox that updates the model but from this question - "the delegates should use the base functions of the model whenever they provide standard behavior (accessing index.model()._data is not good)". I also want the change of the combobox to affect what appears in a QLineEdit.
QItemDelegate:
class Delegate(QItemDelegate):
def __init__(self):
QItemDelegate.__init__(self)
self.type_items = ["1", "2"]
def createEditor(self, parent, option, index):
if index.column() == 0:
comboBox = QComboBox(parent)
for text in self.type_items:
comboBox.addItem(text, (index.row(), index.column()))
comboBox.currentIndexChanged.connect(lambda: self.comboBoxChanged(comboBox, index))
return comboBox
return super().createEditor(parent, option, index)
# updates model but does not update view - shows what I want to happen
def comboBoxChanged(self, comboBox, index):
if comboBox.currentText() == "1":
index.model()._data[index.row()][0] = "1"
index.model()._data[index.row()][1] = "One"
elif comboBox.currentText() == "2":
index.model()._data[index.row()][0] = "2"
index.model()._data[index.row()][1] = "Two"
# self.dataChanged.emit(index, index) # can't call this here
QAbstractTableModel:
class TableModel(QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def data(self, index, role):
if role in (Qt.DisplayRole, Qt.EditRole):
return self._data[index.row()][index.column()]
def rowCount(self, index=None):
return len(self._data)
def columnCount(self, index=None):
return len(self._data[0])
def flags(self, index):
return super().flags(index) | Qt.ItemIsEditable
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.EditRole:
self._data[index.row()][index.column()] = value
self.dataChanged.emit(index, index)
return True
return False
Main:
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
localWidget = QWidget()
self.table = QTableView()
data = [["1", "One"]]
self.model = TableModel(data)
self.table.setModel(self.model)
self.table.setItemDelegate(Delegate())
self.print_data = QPushButton("Print data")
self.print_data.clicked.connect(self.printData)
for row in range(self.model.rowCount()):
for column in range(self.model.columnCount()):
index = self.model.index(row, column)
self.table.openPersistentEditor(index)
layout_v = QVBoxLayout()
layout_v.addWidget(self.table)
layout_v.addWidget(self.print_data)
localWidget.setLayout(layout_v)
self.setCentralWidget(localWidget)
self.show()
def printData(self):
print(self.model._data)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
How can I register the change in the qcombobox to correctly update the model?
and
How do I make this trigger the change in qlineedit?
The data of a model should be preferably updated by using the existing setData function, which provides a centralized interface that avoids confusing bugs and mistakes.
In your case, you need to set the data of the sibling index, which is the index at the same row in the next column (no need to set the index value itself, as Qt is able to get it automatically from the combo, and it should be done in setModelData() anyway).
def comboBoxChanged(self, comboBox, index):
if comboBox.currentText() == "1":
value = "One"
elif comboBox.currentText() == "2":
value = "Two"
sibling = index.sibling(index.row(), 1)
index.model().setData(sibling, value)
Just to be clear, your code could have worked, but dataChanged is a signal of the model, not the delegate, so it should have been:
index.model().dataChanged.emit(index, index)
But, as already said, it's better to avoid using different ways of changing data in the model, especially if done from outside it. The major benefit of having a single function that takes care of that is that whenever something is going to change in the structure and implementation of the model the only thing left to check is all the occurrences of that function call (in case the column order changes, for example).
I am trying to add a row to QTableView with a QAbstractTableModel and QItemDelegate where the widgets appear in the added row. From what I've read I need to call .edit(index) on each item of the added row to call createEditor where the widgets are created however I am getting edit: editing failed
QItemDelegate:
class Delegate(QItemDelegate):
def __init__(self):
QItemDelegate.__init__(self)
self.type_items = ["1", "2", "3"]
def createEditor(self, parent, option, index):
# COMBOBOX, LINEEDIT, TIMEDIT
if index.column() == 0:
comboBox = QComboBox(parent)
for text in self.type_items:
comboBox.addItem(text, (index.row(), index.column()))
return comboBox
elif index.column() == 1:
lineEdit = QLineEdit(parent)
return lineEdit
elif index.column() == 2:
timeEdit = QTimeEdit(parent)
return timeEdit
def setEditorData(self, editor, index):
value = index.model()._data[index.row()][index.column()]
if index.column() == 0:
editor.setCurrentIndex(self.type_items.index(value))
elif index.column() == 1:
editor.setText(str(value))
elif index.column() == 2:
editor.setTime(value)
QAbstractTableModel:
class TableModel(QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def data(self, index, role):
pass
def rowCount(self, index=None):
return len(self._data)
def columnCount(self, index=None):
return len(self._data[0])
Main:
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
localWidget = QWidget()
self.table = QTableView(localWidget)
data = [["1", "Hi", QTime(2, 1)], ["2", "Hello", QTime(3, 0)]]
self.model = TableModel(data)
self.table.setModel(self.model)
self.table.setItemDelegate(Delegate())
self.add_row = QPushButton("Add Row", localWidget)
self.add_row.clicked.connect(self.addRow)
for row in range(self.model.rowCount()):
for column in range(self.model.columnCount()):
index = self.model.index(row, column)
self.table.openPersistentEditor(index)
layout_v = QVBoxLayout()
layout_v.addWidget(self.table)
layout_v.addWidget(self.add_row)
localWidget.setLayout(layout_v)
self.setCentralWidget(localWidget)
self.show()
def addRow(self):
new_row_data = ["3", "Howdy", QTime(9, 0)]
self.model.beginInsertRows(QModelIndex(), self.model.rowCount(), self.model.rowCount())
self.model._data.append(new_row_data)
self.model.endInsertRows()
for i in range(len(new_row_data)):
index = self.table.model().index(self.model.rowCount()-1, i)
self.table.edit(index)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
How can I trigger the QItemDelegate's createEditor to create the widgets for the added row's items and setEditorData to populate them?
First of all, you could use openPersistentEditor as you already did in the __init__.
There are two reasons for the error in self.table.edit():
in order to allow editing of an item through edit(), the flags() function must be overridden and provide the Qt.ItemIsEditable flag;
you cannot call edit() multiple times on different indexes within the same function;
Then, there are other important problems in your code:
you're not correctly using the model, as data() is not implemented;
the delegates should use the base functions of the model whenever they provide standard behavior (accessing index.model()._data is not good);
setEditorData is unnecessary, as long as the above aspects are respected: Qt automatically sets the data based on the data type and the editor class;
setData() should be implemented in order to correctly set the data on the model, otherwise the new data won't be accessible;
changes in the model structure (like creating a new row) should be done in the model class;
Here is a revised version of your code:
class Delegate(QItemDelegate):
def __init__(self):
QItemDelegate.__init__(self)
self.type_items = ["1", "2", "3"]
def createEditor(self, parent, option, index):
if index.column() == 0:
comboBox = QComboBox(parent)
for text in self.type_items:
comboBox.addItem(text, (index.row(), index.column()))
return comboBox
# no need to check for the other columns, as Qt automatically creates a
# QLineEdit for string values and QTimeEdit for QTime values;
return super().createEditor(parent, option, index)
# no setEditorData() required
class TableModel(QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def appendRowData(self, data):
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self._data.append(data)
self.endInsertRows()
def data(self, index, role=Qt.DisplayRole):
if role in (Qt.DisplayRole, Qt.EditRole):
return self._data[index.row()][index.column()]
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.EditRole:
self._data[index.row()][index.column()] = value
self.dataChanged.emit(index, index)
return True
return False
def rowCount(self, index=None):
return len(self._data)
def columnCount(self, index=None):
return len(self._data[0])
def flags(self, index):
# allow editing of the index
return super().flags(index) | Qt.ItemIsEditable
class MainWindow(QMainWindow):
# ...
def addRow(self):
row = self.model.rowCount()
new_row_data = ["3", "Howdy", QTime(9, 0)]
self.model.appendRowData(new_row_data)
for i in range(self.model.columnCount()):
index = self.model.index(row, i)
self.table.openPersistentEditor(index)
hi i've tried all i can think of and had looked at hundreds of stack overflow questions about tables and delegate's, and scratched my head for hours looking at the documentation trying to understand the c++ language and i have not read anything clearly stating that there's limits to the of amount delegate's a table view can take and not take, now i hope i can say i've got a firm understanding of the basic's in pyside2 and pyqt5 especially with tables and models but the delegates is a bit mind boggling, i've gotten this far based on people's questions mostly from stack overflow so this is my first attempt to ask any help..
import pandas as pd
from PySide2 import QtWidgets
from PySide2.QtCore import (Qt, QAbstractTableModel, QModelIndex, QEvent, QPersistentModelIndex,
QSortFilterProxyModel,
QTimer, Slot)
from PySide2.QtWidgets import QTableView, QAbstractItemView, QComboBox, QItemDelegate
class ScheduleModel(QAbstractTableModel):
def __init__(self, schedules_list=None, parent=None):
super(ScheduleModel, self).__init__(parent)
if schedules_list is None:
self.schedules_list = []
else:
self.schedules_list = schedules_list
def rowCount(self, index=QModelIndex()):
return self.schedules_list.shape[0]
def columnCount(self, index=QModelIndex()):
return self.schedules_list.shape[1]
def data(self, index, role=Qt.DisplayRole):
col = index.column()
if index.isValid():
if role == Qt.DisplayRole:
value = self.schedules_list.iloc[index.row(), index.column()]
return str(self.schedules_list.iloc[index.row(), index.column()])
return None
def headerData(self, section, orientation, role):
# section is the index of the column/row.
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.schedules_list.columns[section]
if orientation == Qt.Vertical:
return str(self.schedules_list.index[section])
def setData(self, index, value, role=Qt.EditRole):
if role != Qt.EditRole:
return False
if index.isValid() and 0 <= index.row() < len(self.schedules_list):
self.schedules_list.iloc[index.row(), index.column()] = value
if self.data(index, Qt.DisplayRole) == value:
self.dataChanged.emit(index, index, (Qt.EditRole,))
return True
return False
def flags(self, index):
if 1 <= index.column() <= 7:
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
if index.column() == 5:
return Qt.ItemIsEditable | Qt.ItemIsUserCheckable | Qt.ItemIsSelectable
elif index.column() == 1 and index.column() == 7:
return Qt.DecorationRole
else:
return Qt.ItemIsSelectable
class ClickDelegate(QtWidgets.QStyledItemDelegate):
blankText = '<Click here to add path>'
def openFileDialog(self, lineEdit):
if not self.blankText.startswith(lineEdit.text()):
currentPath = lineEdit.text()
else:
currentPath = ''
path, _ = QtWidgets.QFileDialog.getOpenFileName(lineEdit.window(),
'Select file', currentPath)
if path:
lineEdit.setText(path)
def createEditor(self, parent, option, index):
editor = QtWidgets.QWidget(parent)
layout = QtWidgets.QHBoxLayout(editor)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
editor.lineEdit = QtWidgets.QLineEdit(self.blankText)
layout.addWidget(editor.lineEdit)
editor.setFocusProxy(editor.lineEdit)
editor.lineEdit.installEventFilter(self)
button = QtWidgets.QToolButton(text='...')
layout.addWidget(button)
button.setFocusPolicy(Qt.NoFocus)
button.clicked.connect(lambda: self.openFileDialog(editor.lineEdit))
return editor
def setEditorData(self, editor, index):
if index.data():
editor.lineEdit.setText(str(index.data()))
editor.lineEdit.selectAll()
def setModelData(self, editor, model, index):
if not editor.lineEdit.text():
model.setData(index, None)
elif not self.blankText.startswith(editor.lineEdit.text()):
model.setData(index, editor.lineEdit.text())
def initStyleOption(self, option, index):
super(ClickDelegate, self).initStyleOption(option, index)
if not option.text:
option.text = self.blankText
def eventFilter(self, source, event):
if isinstance(source, QtWidgets.QLineEdit):
if (event.type() == QEvent.MouseButtonPress and
source.hasSelectedText() and
self.blankText.startswith(source.text())):
res = super(ClickDelegate, self).eventFilter(source, event)
source.clear()
return res
elif event.type() == QEvent.KeyPress and event.key() in (
Qt.Key_Escape, Qt.Key_Tab, Qt.Key_Backtab):
return False
return super(ClickDelegate, self).eventFilter(source, event)
def checkIndex(self, table, index):
if index in table.selectedIndexes() and index == table.currentIndex():
table.edit(index)
def editorEvent(self, event, model, option, index):
if (event.type() == QEvent.MouseButtonPress and
event.button() == Qt.LeftButton and
index in option.widget.selectedIndexes()):
table = option.widget
QTimer.singleShot(0, lambda: self.checkIndex(table, index))
return super(ClickDelegate, self).editorEvent(event, model, option, index)
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):
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,
Qt.Unchecked if int(index.data()) == 0 else 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() and Qt.ItemIsEditable) > 0:
return False
if event.type() == QEvent.MouseButtonRelease and event.button() == 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, Qt.EditRole)
class DoubleSpinBoxDelegate(QtWidgets.QStyledItemDelegate):
"""A delegate class displaying a double spin box."""
def __init__(self, parent=None, minimum=0.0, maximum=100.0, step=0.01):
QtWidgets.QStyledItemDelegate.__init__(self, parent)
self._min = minimum
self._max = maximum
self._step = step
def createEditor(self, parent, option, index):
editor = QtWidgets.QDoubleSpinBox(parent)
editor.setMinimum(self._min)
editor.setMaximum(self._max)
editor.setSingleStep(self._step)
editor.setAccelerated(True)
editor.installEventFilter(self)
return editor
def setEditorData(self, spinBox, index):
value = float(index.model().data(index, Qt.DisplayRole))
spinBox.setValue(value)
def setModelData(self, spinBox, model, index):
value = spinBox.value()
model.setData(index, value)
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
class ComboBoxDelegate(QItemDelegate):
def __init__(self, parent=None):
super(ComboBoxDelegate, self).__init__(parent)
self.items = []
def setItems(self, items):
self.items = items
def createEditor(self, parent, option, index):
combo = QComboBox(parent)
li = []
for item in self.items:
li.append(item)
combo.addItems(li)
combo.currentIndexChanged.connect(self.currentIndexChanged)
return combo
def setEditorData(self, editor, index):
editor.blockSignals(True)
text = index.model().data(index, Qt.DisplayRole)
try:
i = self.items.index(text)
except ValueError:
i = 0
editor.setCurrentIndex(i)
def setModelData(self, editor, model, index):
# model.setData(index, editor.currentIndex(), Qt.EditRole)
model.setData(index, editor.currentText())
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
#Slot()
def currentIndexChanged(self):
self.commitData.emit(self.sender())
class SchedulesViewer(QTableView):
# selectionChanged = Signal(QItemSelection)
# data_changed = Signal(QModelIndex, QModelIndex)
def __init__(self, parent=None):
QTableView.__init__(self, parent)
# self.setContextMenuPolicy(Qt.CustomContextMenu)
# self.customContextMenuRequested.connect(self.schedule_context_menu)
address = {'idx': '1',
'presets': 'presets',
'selected_source': 'get_source',
'selected_destinations': 'selected_destinations',
'interval': '0400',
'active': '1',
'priority': 'high',
'categories': 'programming',
'last_total': '222',
}
self.schedule_model = ScheduleModel(pd.DataFrame([address]))
self.proxyModel = QSortFilterProxyModel(self)
self.proxyModel.setSourceModel(self.schedule_model)
self.proxyModel.setDynamicSortFilter(True)
self.setModel(self.proxyModel)
**"""
HAVING LIMITS TO THE AMOUNT OF WIDGETS TABLE VIEW CAN HANDEL
"""
dialog_delegate = ClickDelegate(self)
self.setItemDelegateForColumn(2, dialog_delegate)
self.setItemDelegateForColumn(3, dialog_delegate)
# spin_delegate = DoubleSpinBoxDelegate()
# self.setItemDelegateForColumn(4, spin_delegate)
# CheckBox = CheckBoxDelegate(None)
# self.setItemDelegateForColumn(5, CheckBox)
data = ['programming', 'game_build', 'other']
combo_delegate = ComboBoxDelegate()
combo_delegate.setItems([str(row) for row in data])
self.setItemDelegateForColumn(6, combo_delegate)**
self.setSortingEnabled(True)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.horizontalHeader().setStretchLastSection(True)
self.verticalHeader().hide()
self.setSelectionMode(QAbstractItemView.SingleSelection)
self.proxyModel.sort(0, Qt.AscendingOrder)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setEditTriggers(QAbstractItemView.DoubleClicked)
self.setSelectionMode(QAbstractItemView.SingleSelection)
# self.selectionModel().selectionChanged.connect(self.selectionChanged)
self.show()
if __name__ == "__main__":
import sys
from PySide2.QtWidgets import QApplication
app = QApplication(sys.argv)
addressWidget = SchedulesViewer()
addressWidget.show()
sys.exit(app.exec_())
so please would someone help me understand what i'am i missing or not understanding, all that i want to achieve is to add the delegate's that have been hashed out and make it a editable table, but if i add either the spinbox or the checkbox delegate the app freezes and crashes so is there a limit as to how many delegate's the table view can handle or what i'am i doing wrong? Any help would be much appreciated please and thank you in advance..
Thanks to musicamante that pointed out so freindly my simple mistake of overlooking the obvious of the too self's missing to make all the delegates members of the instance and i have tested and it works so here is the code..
import pandas as pd
from PySide2 import QtWidgets
from PySide2.QtCore import (Qt, QAbstractTableModel, QModelIndex, QEvent,
QPersistentModelIndex,
QSortFilterProxyModel,
QTimer, Slot)
from PySide2.QtWidgets import QTableView, QAbstractItemView, QComboBox, QItemDelegate
class ScheduleModel(QAbstractTableModel):
def __init__(self, schedules_list=None, parent=None):
super(ScheduleModel, self).__init__(parent)
if schedules_list is None:
self.schedules_list = []
else:
self.schedules_list = schedules_list
def rowCount(self, index=QModelIndex()):
return self.schedules_list.shape[0]
def columnCount(self, index=QModelIndex()):
return self.schedules_list.shape[1]
def data(self, index, role=Qt.DisplayRole):
col = index.column()
if index.isValid():
if role == Qt.DisplayRole:
value = self.schedules_list.iloc[index.row(), index.column()]
return str(self.schedules_list.iloc[index.row(), index.column()])
return None
def headerData(self, section, orientation, role):
# section is the index of the column/row.
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.schedules_list.columns[section]
if orientation == Qt.Vertical:
return str(self.schedules_list.index[section])
def setData(self, index, value, role=Qt.EditRole):
if role != Qt.EditRole:
return False
if index.isValid() and 0 <= index.row() < len(self.schedules_list):
self.schedules_list.iloc[index.row(), index.column()] = value
if self.data(index, Qt.DisplayRole) == value:
self.dataChanged.emit(index, index, (Qt.EditRole,))
return True
return False
def flags(self, index):
if 1 <= index.column() <= 7:
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
if index.column() == 5:
return Qt.ItemIsEditable | Qt.ItemIsUserCheckable | Qt.ItemIsSelectable
elif index.column() == 1 and index.column() == 7:
return Qt.DecorationRole
else:
return Qt.ItemIsSelectable
class ClickDelegate(QtWidgets.QStyledItemDelegate):
blankText = '<Click here to add path>'
def openFileDialog(self, lineEdit):
if not self.blankText.startswith(lineEdit.text()):
currentPath = lineEdit.text()
else:
currentPath = ''
path, _ = QtWidgets.QFileDialog.getOpenFileName(lineEdit.window(),
'Select file', currentPath)
if path:
lineEdit.setText(path)
def createEditor(self, parent, option, index):
editor = QtWidgets.QWidget(parent)
layout = QtWidgets.QHBoxLayout(editor)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
editor.lineEdit = QtWidgets.QLineEdit(self.blankText)
layout.addWidget(editor.lineEdit)
editor.setFocusProxy(editor.lineEdit)
editor.lineEdit.installEventFilter(self)
button = QtWidgets.QToolButton(text='...')
layout.addWidget(button)
button.setFocusPolicy(Qt.NoFocus)
button.clicked.connect(lambda: self.openFileDialog(editor.lineEdit))
return editor
def setEditorData(self, editor, index):
if index.data():
editor.lineEdit.setText(str(index.data()))
editor.lineEdit.selectAll()
def setModelData(self, editor, model, index):
if not editor.lineEdit.text():
model.setData(index, None)
elif not self.blankText.startswith(editor.lineEdit.text()):
model.setData(index, editor.lineEdit.text())
def initStyleOption(self, option, index):
super(ClickDelegate, self).initStyleOption(option, index)
if not option.text:
option.text = self.blankText
def eventFilter(self, source, event):
if isinstance(source, QtWidgets.QLineEdit):
if (event.type() == QEvent.MouseButtonPress and
source.hasSelectedText() and
self.blankText.startswith(source.text())):
res = super(ClickDelegate, self).eventFilter(source, event)
source.clear()
return res
elif event.type() == QEvent.KeyPress and event.key() in (
Qt.Key_Escape, Qt.Key_Tab, Qt.Key_Backtab):
return False
return super(ClickDelegate, self).eventFilter(source, event)
def checkIndex(self, table, index):
if index in table.selectedIndexes() and index == table.currentIndex():
table.edit(index)
def editorEvent(self, event, model, option, index):
if (event.type() == QEvent.MouseButtonPress and
event.button() == Qt.LeftButton and
index in option.widget.selectedIndexes()):
table = option.widget
QTimer.singleShot(0, lambda: self.checkIndex(table, index))
return super(ClickDelegate, self).editorEvent(event, model, option, index)
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):
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,
Qt.Unchecked if int(index.data()) == 0 else 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() and Qt.ItemIsEditable) > 0:
return False
if event.type() == QEvent.MouseButtonRelease and event.button() ==
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, Qt.EditRole)
class DoubleSpinBoxDelegate(QtWidgets.QStyledItemDelegate):
"""A delegate class displaying a double spin box."""
def __init__(self, parent=None, minimum=0.0, maximum=100.0, step=0.01):
QtWidgets.QStyledItemDelegate.__init__(self, parent)
self._min = minimum
self._max = maximum
self._step = step
def createEditor(self, parent, option, index):
editor = QtWidgets.QDoubleSpinBox(parent)
editor.setMinimum(self._min)
editor.setMaximum(self._max)
editor.setSingleStep(self._step)
editor.setAccelerated(True)
editor.installEventFilter(self)
return editor
def setEditorData(self, spinBox, index):
value = float(index.model().data(index, Qt.DisplayRole))
spinBox.setValue(value)
def setModelData(self, spinBox, model, index):
value = spinBox.value()
model.setData(index, value)
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
class ComboBoxDelegate(QItemDelegate):
def __init__(self, parent=None):
super(ComboBoxDelegate, self).__init__(parent)
self.items = []
def setItems(self, items):
self.items = items
def createEditor(self, parent, option, index):
combo = QComboBox(parent)
li = []
for item in self.items:
li.append(item)
combo.addItems(li)
combo.currentIndexChanged.connect(self.currentIndexChanged)
return combo
def setEditorData(self, editor, index):
editor.blockSignals(True)
text = index.model().data(index, Qt.DisplayRole)
try:
i = self.items.index(text)
except ValueError:
i = 0
editor.setCurrentIndex(i)
def setModelData(self, editor, model, index):
# model.setData(index, editor.currentIndex(), Qt.EditRole)
model.setData(index, editor.currentText())
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
#Slot()
def currentIndexChanged(self):
self.commitData.emit(self.sender())
class SchedulesViewer(QTableView):
# selectionChanged = Signal(QItemSelection)
# data_changed = Signal(QModelIndex, QModelIndex)
def __init__(self, parent=None):
QTableView.__init__(self, parent)
# self.setContextMenuPolicy(Qt.CustomContextMenu)
# self.customContextMenuRequested.connect(self.schedule_context_menu)
address = {'idx': '1',
'presets': 'presets',
'selected_source': 'get_source',
'selected_destinations': 'selected_destinations',
'interval': '0400',
'active': '1',
'priority': 'high',
'categories': 'programming',
'last_total': '222',
}
self.schedule_model = ScheduleModel(pd.DataFrame([address]))
self.proxyModel = QSortFilterProxyModel(self)
self.proxyModel.setSourceModel(self.schedule_model)
self.proxyModel.setDynamicSortFilter(True)
self.setModel(self.proxyModel)
"""
HAVING LIMITS TO THE AMOUNT OF WIDGETS TABLE VIEW CAN HANDEL
"""
self.setItemDelegateForColumn(2, ClickDelegate(self))
self.setItemDelegateForColumn(3, ClickDelegate(self))
self.setItemDelegateForColumn(4, DoubleSpinBoxDelegate(self))
self.setItemDelegateForColumn(5, CheckBoxDelegate(self))
data = ['programming', 'game_build', 'other']
combo_delegate = ComboBoxDelegate(self)
combo_delegate.setItems([str(row) for row in data])
self.setItemDelegateForColumn(6, combo_delegate)
self.setSortingEnabled(True)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.horizontalHeader().setStretchLastSection(True)
self.verticalHeader().hide()
self.setSelectionMode(QAbstractItemView.SingleSelection)
self.proxyModel.sort(0, Qt.AscendingOrder)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setEditTriggers(QAbstractItemView.DoubleClicked)
self.setSelectionMode(QAbstractItemView.SingleSelection)
# self.selectionModel().selectionChanged.connect(self.selectionChanged)
self.show()
if __name__ == "__main__":
import sys
from PySide2.QtWidgets import QApplication
app = QApplication(sys.argv)
addressWidget = SchedulesViewer()
addressWidget.show()
sys.exit(app.exec_())
The code below creates a single QTableView. An instance of QAbstractTableModel is then created and assigned to it as its model. Lastly, the QItemDelegate is assigned to the QTableView.
The column 0 is being populated with QLineEdit. While column 1 is populated with QComboBox.
But even while QLineEdit is assigned a custom text value the QLineEdit remains to be blank. It doesn't happen with QComboBox which properly gets three items: 'Somewhere', 'Over', 'The Rainbow'.
What should be done to pre-populate column 0 QLineEdits with the text?
from PyQt4 import QtCore, QtGui
app = QtGui.QApplication([])
class Delegate(QtGui.QItemDelegate):
def __init__(self):
QtGui.QItemDelegate.__init__(self)
def createEditor(self, parent, option, index):
if index.column()==0:
lineedit=QtGui.QLineEdit(parent)
lineedit.setText('Somewhere over the rainbow')
return lineedit
elif index.column()==1:
combo=QtGui.QComboBox(parent)
combo.addItems(['Somewhere','Over','The Rainbow'])
combo.setCurrentIndex(index.row())
return combo
class Model(QtCore.QAbstractTableModel):
def __init__(self):
QtCore.QAbstractTableModel.__init__(self)
self.items = [[1, 'one', None], [2, 'two', None], [3, 'three', None]]
def rowCount(self, parent=QtCore.QModelIndex()):
return 3
def columnCount(self, parent=QtCore.QModelIndex()):
return 3
def data(self, index, role):
if not index.isValid(): return
row = index.row()
column = index.column()
if role == QtCore.Qt.DisplayRole:
return self.items[row][column]
tableModel=Model()
tableView=QtGui.QTableView()
tableView.setModel(tableModel)
tableView.setItemDelegate(Delegate())
for row in range(tableModel.rowCount()):
for column in range(tableModel.columnCount()):
index=tableModel.index(row, column)
tableView.openPersistentEditor(index)
tableView.show()
app.exec_()
EDITED LATER:
Big thanks Fabio for the trick.
Working code is posted below.
Instead of handling the value assignment inside of Delegate.createEditor() method we should implement Delegate.setEditorData(editor, index) instead.
Inside of setEditorData we can get both: index.column() and index.row() as well as to access self.items variable used by model using index argument value = index.model().items[row][column]
from PyQt4 import QtCore, QtGui
app = QtGui.QApplication([])
class Delegate(QtGui.QItemDelegate):
def __init__(self):
QtGui.QItemDelegate.__init__(self)
def createEditor(self, parent, option, index):
if index.column()==0:
lineedit=QtGui.QLineEdit(parent)
return lineedit
elif index.column()==1:
combo=QtGui.QComboBox(parent)
return combo
def setEditorData(self, editor, index):
row = index.row()
column = index.column()
value = index.model().items[row][column]
if isinstance(editor, QtGui.QComboBox):
editor.addItems(['Somewhere','Over','The Rainbow'])
editor.setCurrentIndex(index.row())
if isinstance(editor, QtGui.QLineEdit):
editor.setText('Somewhere over the rainbow')
class Model(QtCore.QAbstractTableModel):
def __init__(self):
QtCore.QAbstractTableModel.__init__(self)
self.items = [[1, 'one', 'ONE'], [2, 'two', 'TWO'], [3, 'three', 'THREE']]
def flags(self, index):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable
def rowCount(self, parent=QtCore.QModelIndex()):
return 3
def columnCount(self, parent=QtCore.QModelIndex()):
return 3
def data(self, index, role):
if not index.isValid(): return
row = index.row()
column = index.column()
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
return self.items[row][column]
tableModel=Model()
tableView=QtGui.QTableView()
tableView.setModel(tableModel)
tableView.setItemDelegate(Delegate())
for row in range(tableModel.rowCount()):
for column in range(tableModel.columnCount()):
index=tableModel.index(row, column)
tableView.openPersistentEditor(index)
tableView.show()
app.exec_()
The item delegate set the editor data by setEditorData method, that takes the model data and set it to the editor. In this case, the item delegate takes the model data (that maybe is empty) and set it to the QLineEdit, so it set an empty string.
If you want to initialize the data in the editor, you have to reimplement setEditorData, or initialize the data in your model.
For QComboBox, the items are displayed correcly because setEditorData doesn't change the items of the combo box. In general you have to reimplement setEditorData to set the current index of the combo box depending on the model data.
I suggest to read the documentation: Model/View Programming and Delegate Classes
The code below creates the single QTableView. There is a QItemDelegate is assigned as a PersistentEditor. There are two kind of editors created: QLineEdit is created for the column 0 and the 'QComboBox` is created for the column 1.
When the tableView is clicked I would like to get the instance of both editors: the instance of the LineEdit and the instance of comboBox. How to achieve this?
from PyQt4 import QtCore, QtGui
app = QtGui.QApplication([])
class Delegate(QtGui.QItemDelegate):
def __init__(self):
QtGui.QItemDelegate.__init__(self)
def createEditor(self, parent, option, index):
if index.column()==0:
lineedit=QtGui.QLineEdit(parent)
return lineedit
elif index.column()==1:
combo=QtGui.QComboBox(parent)
return combo
def setEditorData(self, editor, index):
row = index.row()
column = index.column()
value = index.model().items[row][column]
if isinstance(editor, QtGui.QComboBox):
editor.addItems(['Somewhere','Over','The Rainbow'])
editor.setCurrentIndex(index.row())
if isinstance(editor, QtGui.QLineEdit):
editor.setText('Somewhere over the rainbow')
class Model(QtCore.QAbstractTableModel):
def __init__(self):
QtCore.QAbstractTableModel.__init__(self)
self.items = [[1, 'one', 'ONE'], [2, 'two', 'TWO'], [3, 'three', 'THREE']]
def flags(self, index):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable
def rowCount(self, parent=QtCore.QModelIndex()):
return 3
def columnCount(self, parent=QtCore.QModelIndex()):
return 3
def data(self, index, role):
if not index.isValid(): return
row = index.row()
column = index.column()
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
return self.items[row][column]
def tableViewClicked(index):
print 'clicked indedx: %s'%index
tableModel=Model()
tableView=QtGui.QTableView()
tableView.setModel(tableModel)
tableView.setItemDelegate(Delegate())
tableView.clicked.connect(tableViewClicked)
for row in range(tableModel.rowCount()):
for column in range(tableModel.columnCount()):
index=tableModel.index(row, column)
tableView.openPersistentEditor(index)
tableView.show()
app.exec_()
I recently set up something similar like this. This might be what you were after.
Step 1, create the delegate to do whatever it is you need, and store the QLineEdit as a variable.
In the case below, I want a signal to emit every time the user leaves the QLineEdit.
class LineEditDelegate(QtWidgets.QStyledItemDelegate):
on_focus_out = QtCore.Signal(object)
def __init__(self, *args, **kwargs):
super(LineEditDelegate, self).__init__(*args, **kwargs)
self.line_edit = None
def createEditor(self, parent, option, index):
self.line_edit = QtWidgets.QLineEdit(parent=parent)
self.line_edit.destroyed.connect(lambda: self._line_edit_left(index=index))
return self.line_edit
def _line_edit_left(self, index):
"""
Triggers when line edit was closed / destroyed
:param index: QModelIndex, index of edited item
:return: None
"""
self.on_focus_out.emit(index)
return
Step 2, Create you QTableWidget and setup however you want, adding the delegate where you need it.
In this example, on all items in column 0
self.table = QtWidgets.QTableWidget(parent=self.base_widget)
self.table.setRowCount(2)
self.table.setColumnCount(2)
self.line_edit_delegate = LineEditDelegate(self.main_window)
self.table.setItemDelegateForColumn(0, self.line_edit_delegate)
I also connect my function to the signal, fulfilling the purpose of this delegate.
self.line_edit_delegate.on_focus_out.connect(self._check_text)
Step 3, Getting the delegate item, i.e. the QLineEdit.
First you'll need to get the item; you can do this in many ways such as:
item_0 = self.table.currentRow()
item_0 = self.table.currentItem()
or connecting through one of the QTableWidget's signals that pass through the QTableWidgetItem, row, or whatever:
self.table.itemClicked.connect(self.item_clicked)
self.table.itemDoubleClicked.connect(self.item_double_clicked)
Then with the QTableWidgetItem you can get the delegate, and then get the QLineEdit.
delegate = self.table.itemDelegate(self.table.indexFromItem(item_0))
delegate.line_edit
For my use, I wanted the user to add a new row and then have the QLineEdit get automatically selected for editing. This is what I used:
row_position = self.table.currentRow() + 1
item_0 = QtWidgets.QTableWidgetItem()
item_0.setFont(font)
self.table.setItem(row_position, 0, item_0)
self.table.openPersistentEditor(item_0)
delegate = self.table.itemDelegate(self.table.indexFromItem(item_0))
delegate.line_edit.setFocus()