16

Is there a way to combine textChanged and editingFinished for QLineEdit? The problem is that editingFinished is emitted even if I only move the cursor away from QLineEdit without any changes. Whereas I want to emit a signal only when any changes were performed after I finished editing.

I can imagine only to store somewhere the current text, compare the entered text with it and do something only if it differs. But I wonder if there is any solution purely based on signals handling.

EDIT: At the end I had to store the current text and compare with the new text and not follow the proposed solution. I realized that in my application "1.2345" and "1.23" would be the same text but nevertheless I have to update some other values in this case and so on. I really appreciate detailed answer and comments by @Avaris and @ekhumoro, and will accept it since it seems to solve originally posted problem.

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
Ekaterina Mishina
  • 1,633
  • 5
  • 20
  • 23

2 Answers2

16

Edit

For capturing manual edits:

class MyLineEdit(QtGui.QLineEdit):
    textModified = QtCore.pyqtSignal(str, str) # (before, after)

    def __init__(self, contents='', parent=None):
        super(MyLineEdit, self).__init__(contents, parent)
        self.returnPressed.connect(self.checkText)
        self._before = contents

    def focusInEvent(self, event):
        if event.reason() != QtCore.Qt.PopupFocusReason:
            self._before = self.text()
        super(MyLineEdit, self).focusInEvent(event)

    def focusOutEvent(self, event):
        if event.reason() != QtCore.Qt.PopupFocusReason:
            self.checkText()
        super(MyLineEdit, self).focusOutEvent(event)

    def checkText(self):
        if self._before != self.text():
            self._before = self.text()
            self.textModified.emit(self._before, self.text())

Edit 2

For capturing all edits (programmatic and manual):

class MyLineEdit(QtGui.QLineEdit):
    textModified = QtCore.pyqtSignal(str, str) # (before, after)

    def __init__(self, contents='', parent=None):
        super(MyLineEdit, self).__init__(contents, parent)
        self.editingFinished.connect(self.checkText)
        self.textChanged.connect(lambda: self.checkText())
        self.returnPressed.connect(lambda: self.checkText(True))
        self._before = contents

    def checkText(self, _return=False):
        if (not self.hasFocus() or _return) and self._before != self.text():
            self._before = self.text()
            self.textModified.emit(self._before, self.text())

Edit 3

For capturing only text changes by the user:

class MyLineEdit(QtGui.QLineEdit):
    textModified = QtCore.pyqtSignal(str, str) # (before, after)

    def __init__(self, contents='', parent=None):
        super(MyLineEdit, self).__init__(contents, parent)
        self.editingFinished.connect(self.__handleEditingFinished)
        self.textChanged.connect(self.__handleTextChanged)
        self._before = contents

    def __handleTextChanged(self, text):
        if not self.hasFocus():
            self._before = text

    def __handleEditingFinished(self):
        before, after = self._before, self.text()
        if before != after:
            self._before = after
            self.textModified.emit(before, after)
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
Avaris
  • 35,883
  • 7
  • 81
  • 72
  • Your solution seems incomplete. For example, the signal will fire if the line-edit's context menu is opened (so maybe you need to check the `event.reason()`). Also, the signal *won't* fire if return/enter is pressed - so some keyboard handling is needed. – ekhumoro Aug 29 '12 at 18:48
  • 1
    @ekhumoro: You are right about those issues. Updated my answer. – Avaris Aug 29 '12 at 19:15
  • Not bad - but what if e.g. `setText()` or `clear()` is called between edits? – ekhumoro Aug 29 '12 at 19:45
  • @ekhumoro: Argh, this is going to turn into a new `QLineEdit` implementation :). Then comes `del`, `backspace`, `insert`, etc... Hmm I think I have a solution for all. – Avaris Aug 29 '12 at 20:01
  • I think the `_before` attribute should be set before any signals are emitted, as the text could be modified by a handler. But even so: is the OP's spec matched? The signal should be emitted *"only when any changes were performed after I finished editing"*. Whereas your current solution may also emit `textModified` after programmatic text changes. – ekhumoro Aug 29 '12 at 23:58
  • @ekhumoro: Oh, I took your comment about `setText` as 'it won't fire with `setText`'. So you meant just updating `_before`. OK, that depends on what 'editing' means. I thought any means of editing (programmatic/manual) is 'editing', and changed the subclass accordingly. Otherwise `focus*Event` method works there. I'll put that back up. – Avaris Aug 30 '12 at 00:52
  • @ekhumoro: Also good point about setting `_before` before the emit. Only problem with the `focus*Event` solution I see is if someone alters the text while editing, `_before` would be off (e.g. connecting `returnPressed` to `clear`). For that, I have no solution besides re-writing many methods that alter the text programmatically. Do you have any suggestions? – Avaris Aug 30 '12 at 01:03
  • +1 For your efforts :) As these comments are getting rather long, I took the liberty of adding an alternative example to your answer to show exactly what I had in mind. – ekhumoro Aug 30 '12 at 01:40
  • @ekhumoro: :) That's good too. That still behaves weirdly with `returnPressed` connected to `clear`, but to be fair, I'm not sure how it's supposed to behave for that :). – Avaris Aug 30 '12 at 01:56
  • Perhaps this all goes to show why Qt doesn't try to implement this behaviour. Having looked again at my alternative, I'm not sure I really like it now! Maybe there just isn't a good, general solution to this problem... – ekhumoro Aug 30 '12 at 02:06
10

If you just want to detect whether any changes have been made (as opposed to whether the text is different from how it started), you can use the modified property of the QLineEdit with the editingFinished signal:

    self.edit = QtGui.QLineEdit(self)
    self.edit.editingFinished.connect(self.handleEditingFinished)
    ...

def handleEditingFinished(self):
    if self.edit.isModified():
        # do interesting stuff ...
        print 'Editing Finished'
    self.edit.setModified(False)
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • 1
    Note that `modified` will stay toggled even if you revert it back to the original value before finishing the edit. – syockit May 13 '19 at 03:09