how to save data being edited when QtableView cell loses focus on close of form

Viewed 912

In the default behavior of editing a cell in a QtableView, when the user clicks away either to another widget or closes the form, the edits are lost. After a lot of googling, I have found a way to save the edits if the user selects another widget in the form, but if the form is closed, the edits are still lost. The blog post is here.

I attempted to call the closeEditor method from the forms closeEvent, but it requires two parameters: the editor and hint. I can provide QAbstractItemDelegate.NoHint but the editor is expecting the QlineEdit object where the editing is taking place. I am lost on how to provide this for the cell currently being edited.

Here is a gif of the current behaviour: enter image description here

My question is how do I provide the QlineEdit of the cell being edited?

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtSql import *
from PyQt5.QtWidgets import *
from phones import *

class Main(QMainWindow):

    def __init__(self, parent=None):

        QMainWindow.__init__(self, parent)
        
        self.resize(490, 998)
        self.layoutWidget = QWidget(self)
        self.layoutWidget.setObjectName("layoutWidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget)
        self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
        self.new_phone = QtWidgets.QPushButton(self.layoutWidget)
        self.new_phone.setObjectName("new_phone")
        self.new_phone.setText("New Phone")
        self.horizontalLayout_7.addWidget(self.new_phone)
        self.delete_phone = QtWidgets.QPushButton(self.layoutWidget)
        self.delete_phone.setObjectName("delete_phone")
        self.delete_phone.setText("Delete phone")
        self.horizontalLayout_7.addWidget(self.delete_phone)
        self.verticalLayout.addLayout(self.horizontalLayout_7)
        
        self.phone_view = Syn_tableview()
        
        self.verticalLayout.addWidget(self.phone_view)
        self.cont_id = '9'
        self.setCentralWidget(self.layoutWidget)
        self.new_phone.clicked.connect(self.add_phone)
        self.populate_phones()

    def populate_phones(self):
        self.phone_model = QSqlTableModel(self)
        self.phone_model.setTable("contact_phones")
        self.phone_model.setFilter("contact_id='{0}'".format(self.cont_id))
        self.phone_model.select()

        self.phone_view.setModel(self.phone_model)  
        self.phone_view.resizeColumnsToContents()
         
    def add_phone(self):
        self.phone_model.submitAll()
        self.phone_model.setEditStrategy(QSqlTableModel.OnManualSubmit)
        row = self.phone_model.rowCount()
        record = self.phone_model.record()
        record.setGenerated('id', False)            #primary key
        record.setValue('contact_id', self.cont_id) #foreign key
        self.phone_model.insertRecord(row, record)
        phone_index_edit = self.phone_model.index(row, self.phone_model.fieldIndex('phone_number'))
        self.phone_view.edit(phone_index_edit)

    def closeEvent(self, event):
        submit = self.phone_model.submitAll()
        
        #This is the problem 
        self.phone_view.closeEditor("QLineEdit", QAbstractItemDelegate.NoHint)
        
    
class Syn_tableview(QTableView):
    def __init__(self, *args, **kwargs):
        QTableView.__init__(self, *args, **kwargs)    
        
    def closeEditor(self, editor, hint):
        if hint == QAbstractItemDelegate.NoHint:
            QTableView.closeEditor(self, editor,
                QAbstractItemDelegate.SubmitModelCache)


if __name__=="__main__":
    app=QApplication(sys.argv)
    db = QSqlDatabase.addDatabase("QPSQL");
    db.setHostName(server)
    db.setDatabaseName(database)
    db.setUserName(user)
    db.setPassword(pword)
    myapp = Main()
    myapp.show()
    sys.exit(app.exec_())
1 Answers

The delegate editors are children of the QTableView so you can use findChildren to get them, to make sure they are not other children you can set an objectName that allows you to filter them:

import sys
from PyQt5 import QtCore, QtSql, QtWidgets


def create_connection():
    db = QtSql.QSqlDatabase.addDatabase("QPSQL")
    # FIXME
    db.setHostName("server")
    db.setDatabaseName("database")
    db.setUserName("user")
    db.setPassword("pword")
    if not db.open():
        print(db.lastError().text())
        return False
    return True


class Syn_Delegate(QtWidgets.QStyledItemDelegate):
    def createEditor(self, parent, option, index):
        editor = super(Syn_Delegate, self).createEditor(parent, option, index)
        if isinstance(editor, QtWidgets.QWidget):
            editor.setObjectName("syn_editor")
        return editor


class Syn_Tableview(QtWidgets.QTableView):
    def closeEditor(self, editor, hint):
        if hint == QtWidgets.QAbstractItemDelegate.NoHint:
            hint = QtWidgets.QAbstractItemDelegate.SubmitModelCache
        super(Syn_Tableview, self).closeEditor(editor, hint)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        self.new_phone = QtWidgets.QPushButton(self.tr("New Phone"))
        self.delete_phone = QtWidgets.QPushButton(self.tr("Delete phone"))
        self.phone_view = Syn_Tableview()
        self.phone_model = QtSql.QSqlTableModel()
        self.phone_model.setEditStrategy(QtSql.QSqlTableModel.OnManualSubmit)
        self.phone_view.setModel(self.phone_model)
        self.phone_view.resizeColumnsToContents()
        delegate = Syn_Delegate(self)
        self.phone_view.setItemDelegate(delegate)

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)

        lay = QtWidgets.QGridLayout(central_widget)
        lay.addWidget(self.new_phone, 0, 0)
        lay.addWidget(self.delete_phone, 0, 1)
        lay.addWidget(self.phone_view, 1, 0, 1, 2)

        self._contact_id = "9"

        self.populate_phones()

        self.new_phone.clicked.connect(self.add_phone)

    @property
    def contact_id(self):
        return self._contact_id

    def populate_phones(self):
        self.phone_model.setTable("contact_phones")
        self.phone_model.setFilter("contact_id='{0}'".format(self.contact_id))
        self.phone_model.select()

    @QtCore.pyqtSlot()
    def add_phone(self):
        self.phone_model.submitAll()

        row = self.phone_model.rowCount()
        record = self.phone_model.record()
        record.setGenerated("id", False)  # primary key
        record.setValue("contact_id", self.contact_id)  # foreign key
        self.phone_model.insertRecord(row, record)

        phone_index_edit = self.phone_model.index(
            row, self.phone_model.fieldIndex("phone_number")
        )
        if phone_index_edit.isValid():
            self.phone_view.edit(phone_index_edit)

    def closeEvent(self, event):
        for editor in self.phone_view.findChildren(QtWidgets.QWidget, "syn_editor"):
            self.phone_view.commitData(editor)
        submit = self.phone_model.submitAll()
        super().closeEvent(event)


def main():
    app = QtWidgets.QApplication(sys.argv)

    if not create_connection():
        sys.exit(-1)

    w = MainWindow()
    w.show()
    w.resize(640, 480)

    ret = sys.exit(app.exec_())

    sys.exit(ret)


if __name__ == "__main__":
    main()
Related