How can I intercept when a widget loses its focus

Question:

I have a QPlainTextEdit and want to process the content when it loses focus. I’ve seen that I can either do this with the focusChanged event or with the focusOutEvent virtual function.

I don’t know how to pass parameters with the new syntax (i.e. my_app.focusChanged.connect(my_handler) where my_handler is a locally defined function). So I tried working with the virtual function.

Since the interface is created with QT Designer inheriting QPlainTextEdit would be an overkill, so I tried to override the virtual function by simply using my_text_edit.focusOutEvent = my_handler. This did intercept the message as I wanted, but it apparently overwrote some built in functionality in QPlainTextEdit and I get artefacts – namely the cursor from the text edit does not disappear when it looses focus. I figured that I should somehow call the original event and what worked for me was the following:

In my __init__ method I have:

    self.original_handler = self.my_text_edit.focusOutEvent
    self.my_text_edit.focusOutEvent = self.my_handler

The definition of my_handler is:

    def my_handler(self, event):
        self.original_handler(event)
        # my own handling follows...

I am basically replicating what I expect the library to do for me. I find this too clumsy and I can see how it can backfire in many ways during maintenance. Can anyone suggest a neater way to do it? Thanks!

Asked By: mapto

||

Answers:

Personally, I never use the monkey-patching style of overriding virtual methods, but the correct way to retain the original behaviour would be to call the base-class method directly, like this:

def my_handler(self, event):
    QtGui.QPlainTextEdit.focusOutEvent(self.my_text_edit, event)
    # my own handling follows...

I don’t understand why you can’t use the focusChanged signal, though. The handler for it would simply be:

def my_handler(self, old, new):
    if old is self.my_text_edit:
        print('focus out')
    elif new is self.my_text_edit:
        print('focus in')

However, my own preference would be to use an event filter:

class Window(QtGui.QMainWindow)
    def __init__(self):
        ...
        self.my_text_edit.installEventFilter(self)

    def eventFilter(self, source, event):
        if (event.type() == QtCore.QEvent.FocusOut and
            source is self.my_text_edit):
            print('eventFilter: focus out')
            # return true here to bypass default behaviour
        return super(Window, self).eventFilter(source, event)

This is a much more flexible solution, which provides a generic way to handle any event type for any widget that has been imported via Qt Designer (or, in fact, any widget that you just don’t want to subclass).

There is also the possibility of widget promotion, which can be used to directly replace the widgets in generated ui modules with your own subclasses. This would then allow you to override any virtual methods via inheritance, rather than monkey-patching individual instances. If you are importing several widgets of the same type from Qt Designer which share a lot of custom functionality, this can provide a very clean solution.

A simple explanation of how to do widget promotion in PyQt can be found in my answer to: Replace QWidget objects at runtime.

Answered By: ekhumoro
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.