pythonpyqt6qlineedit

Prevent editingFinished Signal from QLineEdit After Programmatic Text Update


I am using a QLineEdit in PyQt6. I need to programmatically set the text without triggering the editingFinished signal. As expected, editingFinished is not triggered directly by setText. However, it puts the QLineEdit in a state where editingFinished is triggered when the widget later gains and loses focus, even if there are no additional text changes in the meantime. If the QLineEdit gains and loses focus a second time (without text changes) editingFinished is not triggered. I am looking for a way to put the QLineEdit into the state it is in after the first focus loss, but without triggering editingFinished. Using blockSignals(True) while setting the text programmatically does not work, as the signal is still emitted later when the widget loses focus. Trying to reset the internal state using setModified(False) does not seem to be the right option either. Subclassing QLineEdit and using flags to handle programmatic and user changes differently could work, but does seem unnecessary and complex compared to just restoring a state whenre QLineEdit just forgets about the textChange if it was done programmatically.

How can I reliably suppress the editingFinished signal after a programmatic text update while maintaining normal behavior for user edits?

Below is an example: To reproduce the undesired behavior, click the setTextButton, then click into the QLineEdit, then into the QSpinBox to make QLineEdit lose focus.

import sys
from PyQt6.QtWidgets import QApplication,QWidget,QVBoxLayout,QPushButton,QLineEdit,QSpinBox

class Foo(QWidget):

    def __init__(self):
        super().__init__()
        lay = QVBoxLayout(self)  
        
        self.s=QSpinBox()
        self.s.setValue(0)
        lay.addWidget(self.s)
        self.le=QLineEdit('text')
        self.le.editingFinished.connect(self.editingFinished)
        lay.addWidget(self.le)
        b=QPushButton('setText')
        b.clicked.connect(self.clicked)
        lay.addWidget(b)
        
    def editingFinished(self):
        # increment spinBox to show that editingFinished has been called
        self.s.setValue(self.s.value()+1)
        
    def clicked(self):
        # set text programmatically
        # triggers textChanged but not editingFinished
        self.le.setText(self.le.text()+'+') # editingFinished will called later if the control gains and looses focus, even without any additional text changes!
        # self.le.setModified(False)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainWindow = Foo()
    mainWindow.show()
    sys.exit(app.exec())

Solution

  • You can use QLineEdit.textEdited signal to handle text changes originated from user, but it is emited on every keystroke, not on lost focus, to hanle user changes on lost focus you can store edited state and use it later in editingFinished handler.

    import sys
    from PyQt6.QtWidgets import QApplication,QWidget,QVBoxLayout,QPushButton,QLineEdit,QSpinBox
    from PyQt6.QtCore import pyqtSignal
    
    class LineEdit(QLineEdit):
        userEditingFinished = pyqtSignal(str)
    
        def __init__(self, parent = None):
            super().__init__(parent)
            self._edited = False
            self.editingFinished.connect(self.onEditingFinished)
            self.textEdited.connect(self.onTextEdited)
    
        def onTextEdited(self):
            self._edited = True
    
        def onEditingFinished(self):
            if self._edited:
                self.userEditingFinished.emit(self.text())
            self._edited = False
    
    class Foo(QWidget):
    
        def __init__(self):
            super().__init__()
            layout = QVBoxLayout(self)  
            
            self.spinBox = QSpinBox()
            self.spinBox.setValue(0)
            layout.addWidget(self.spinBox)
            self.lineEdit = LineEdit('text')
            self.lineEdit.userEditingFinished.connect(self.onUserEditingFinished)
    
            layout.addWidget(self.lineEdit)
            button = QPushButton('setText')
            button.clicked.connect(self.onButtonClicked)
            layout.addWidget(button)
            
        def onUserEditingFinished(self, text):
            self.spinBox.setValue(self.spinBox.value()+1)
            
        def onButtonClicked(self):
            self.lineEdit.setText(self.lineEdit.text()+'+')
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        mainWindow = Foo()
        mainWindow.show()
        sys.exit(app.exec())