Pysyde/QTableView: openPersistentEditor works only once?
-
Hi,
Did you check the size of the model when
initComboBoxes
is called ?
If it's valid, can you try not replacing the delegate ? -
@dammilo
I use PyQt, but pretty similar to PySide. This probably has nothing to do with your problem, but...self.parent = parent
QItemDelegate
inherits all the way fromQObject.parent()
method. Is it a good idea to create an instance attribute with the same name as an inherited method?In your code, you could get rid of that anyway and simply use the existing
parent()
method instead. You won't need your ownparent
attribute in any of your classes deriving fromQObject
. -
thank you guys for your inputs
@SGaist
hi, i checked and the model looks always valid to me.
My impression is that on a model level everything works - in fact, the numbers that i see on the combobox column are coming straight out of it.
Also, I just noticed that if i double click the cell and go into edit mode, I still get my combobox delegate and I can correctly edit the values; the combobox disappears as soon as i'm done with the edit.I tried moving the setItemDelegateForColumn method around - only in init, after the openPersistentEditor, before it, duplicated...but no luck
@JonB
good call, didn't know that, I fixed it. But sadly, as you said, that didn't solve the issue -
modelReset
should be emit after endResetMode.What version of PySide are you using ?
-
I was facing exactly the same problem with PyQt5.
I was creating a QTableView and assigned a QStyledItemDelegate to one of the columns and opened the persistent editor for the column items.
Whenever I was programmatically adding a new row and reopening the persistent editor for all table rows, the tableview data did not change upon selecting a new item in the QComboBox of the item delegate.
It took me a while to figure out that persistent editor was actually behaving as expected for the very last row of the table.
I'm pretty sure that when opening the peristent editor after having added a row to the table, a second QComboBox is drawn ontop of the already existing ones in rows 0:(numberOfRowsOfOriginalTable).I've been able to solve the problem by effectively closing the existing persistent editors and reopening them again for all rows of the updated table.
Nevertheless, I would expect PyQt to ignore the openPersistentEditor() command on rows where the persistent editor is already open or to give out an error message.
-
@SGaist sorry, it took me a while to create the mwe. Here you are:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys from PyQt5.QtCore import (Qt, QAbstractTableModel, pyqtSlot) from PyQt5.QtWidgets import (QLabel, QTextEdit, QApplication, QStyledItemDelegate, QComboBox, QMainWindow, QTableView, QFormLayout, QPushButton, QWidget, QHBoxLayout, QCheckBox) import copy class ComboDelegate(QStyledItemDelegate): def __init__(self, parent, items): self.items = items QStyledItemDelegate.__init__(self, parent) def createEditor(self, parent, option, index): self.editor = QComboBox(parent) font = self.editor.font() font.setPointSize(8) self.editor.setFont(font) self.editor.setStyleSheet("background-color: white;\n" "border: 1px solid gray;\n" "padding: 1px 3px 1px 3px;") self.editor.addItems(self.items) self.editor.currentIndexChanged.connect(self.currentIndexChanged) return self.editor def setEditorData(self, editor, index): editor.blockSignals(True) # block signals that are not caused by the user value = index.data(Qt.DisplayRole) if value == "": editor.setStyleSheet("background-color: red") else: editor.setStyleSheet("background-color: white") num = self.items.index(value) editor.setCurrentIndex(num) if index.column() == 1: editor.showPopup() editor.blockSignals(False) def setModelData(self, editor, model, index): model.setData(index, editor.currentText()) def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect) @pyqtSlot() def currentIndexChanged(self): self.commitData.emit(self.sender()) class tableModel(QAbstractTableModel): def __init__(self, table_data = [['', '', '', '', '']]): QAbstractTableModel.__init__(self) self.table_data = table_data self.no_columns = 5 def addRow(self, rowNbr, rowData): if rowNbr == -1: self.table_data.append(rowData) else: self.table_data.insert(rowNbr, rowData) self.layoutChanged.emit() def deleteRow(self, rowNbr): self.table_data.pop(rowNbr) def data(self, index, role=Qt.DisplayRole): if not index.isValid(): return None value = self.table_data[index.row()][index.column()] if role == Qt.EditRole: return value if role == Qt.DisplayRole: if isinstance(value, float): # Render float to 2 dp return "%.2f" % value else: return value if role == Qt.TextAlignmentRole: if index.column() in [0, 1, 2]: return Qt.AlignVCenter + Qt.AlignLeft else: return Qt.AlignVCenter + Qt.AlignRight def rowCount(self, index): return len(self.table_data) def columnCount(self, index): return self.no_columns def headerData(self, section, orientation, role): if role != Qt.DisplayRole: return None if orientation == Qt.Horizontal: return ("Type", "ID", "Location", "Offset T [\xB0C]", "Offset RH [%]")[section] else: return "{}".format(section) def setData(self, index, value, role=Qt.EditRole): if index.isValid(): self.table_data[index.row()][index.column()] = value self.dataChanged.emit(index, index, [Qt.DisplayRole]) return True else: return False def flags(self, index): return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable class Main(QMainWindow): def __init__(self): super().__init__() self.central_widget = QWidget() self.setCentralWidget(self.central_widget) fbox = QFormLayout() hbox = QHBoxLayout() self.table = QTableView() self.label = QLabel('Current table data as python list') self.tedit = QTextEdit() self.applyFixCheckBox = QCheckBox('Apply fix (close and reopen ' + 'persistent editor. To be checked ' + 'right after program start in ' + 'this example)') fbox.addRow(self.table) self.addRowBtn = QPushButton('Add row') self.deleteRowBtn = QPushButton("Delete row") self.addRowBtn.clicked.connect(self.add_row2table) self.deleteRowBtn.clicked.connect(self.delete_row_from_table) hbox.addWidget(self.addRowBtn) hbox.addWidget(self.deleteRowBtn) fbox.addRow(hbox) fbox.addRow(self.label) fbox.addRow(self.tedit) fbox.addRow(self.applyFixCheckBox) self.centralWidget().setLayout(fbox) data = [['Option2', 'Teststring', 'Testlocation', 1, 2], ['Option1', 'Teststring', 'Testlocation', 0, 0]] self.model = tableModel(data) self.table.setModel(self.model) self.model.dataChanged.connect(self.tableChanged) self.resize(600,400) self.lastTableData = [] self.tableChanged() self.setComboBoxes() def tableChanged(self): if self.model.table_data != self.lastTableData: self.tedit.setText(str(self.model.table_data)) self.table.model().layoutChanged.emit() self.lastTableData = copy.deepcopy(self.model.table_data) def setComboBoxes(self): self.table.setItemDelegateForColumn(0, ComboDelegate(self, ["", "Option1", "Option2", "Option3"])) for row in range(0, self.model.rowCount(0)): self.table.openPersistentEditor(self.model.index(row, 0)) def add_row2table(self): if self.applyFixCheckBox.isChecked(): # have to actively close the persistent editor, otherwise when reopening # an editor it is drawn ontop of the old one for row in range(0, self.model.rowCount(0)): self.table.closePersistentEditor(self.model.index(row, 0)) index = sorted(self.table.selectionModel().selectedRows()) if index != []: self.model.addRow(index[0].row(), ['Option1', '', '', '', '']) else: # add row at the end self.model.addRow(-1, ['Option1', '', '', '', '']) self.tableChanged() self.setComboBoxes() def delete_row_from_table(self): indexes = sorted(self.table.selectionModel().selectedRows()) if indexes != []: if self.applyFixCheckBox.isChecked(): # have to actively close the persistent editor, otherwise when # reopening an editor it is drawn ontop of the old one for row in range(0, self.model.rowCount(0)): self.table.closePersistentEditor( self.model.index(row, 0)) ind = [elem.row() for elem in indexes] ind.reverse() for i in ind: self.model.deleteRow(i) self.tableChanged() self.setComboBoxes() def closeEvent(self,event): QApplication.quit() if __name__ == '__main__': app = QApplication.instance() # prevent kernel crash at every second run of the program due to bug # in iPython if app is None: app = QApplication(sys.argv) main = Main() main.show() main.raise_() app.exec_()
-
There's no need to close them first.
The issue here comes rather from the replacement of the delegate. There's no need for that. If you just set it in the constructor and trigger the persistent editors when you add a line, you should be fine.
One thing that might be worth checking is why replacing the delegate does not close de persistent editor. I do not know whether it's on purpose as you asked for a persistent editor after all.