PyQt4: combine textChanged and editingFinished for QLineEdit
Asked Answered
S

2

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.

Smarm answered 29/8, 2012 at 16:8 Comment(0)
C
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)
Cudgel answered 29/8, 2012 at 16:42 Comment(10)
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.Kelso
@ekhumoro: You are right about those issues. Updated my answer.Cudgel
Not bad - but what if e.g. setText() or clear() is called between edits?Kelso
@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.Cudgel
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.Kelso
@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.Cudgel
@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?Cudgel
+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.Kelso
@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 :).Cudgel
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...Kelso
K
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)
Kelso answered 29/8, 2012 at 18:33 Comment(1)
Note that modified will stay toggled even if you revert it back to the original value before finishing the edit.Cyclothymia

© 2022 - 2024 — McMap. All rights reserved.