PyQt Binding singals dynamically

So I got around to solving this thing when I wanted to create a QFrame that docks itself to the top right, ignoring all layout (so it can actually be on top of things). Useful to me as a sort of icon bar on top of a tab widget, given that the tab widget would never have so many tabs that the tabs go behind the icons.

To dock something it would need to know it’s parent widget and connect to the parent resize event to update it’s own geometry. There is no resize signal however, so the resizeEvent needs to be overridden; but this isn’t possible because the resizeEvent handles all kinds of stuff that we need.

So we can choose the cheap way out and inherit QWidget, override the resizeEvent and create a QFrame that is outside the layout and always forced in the top right, but let’s disregard that for a moment as this gets more interesting.

We can’t create signals on runtime, so we need a custom signal class that works exactly like pyqtBoundSignal in usage except it doesn’t crash Qt on creation.

Note: The pyqtBoundSignal class can’t be created manally, the pyqtSignal class is just a placeholder and can’t be used as it contains no actual signal functionality.

We also can’t extend functions in a decent way in Python, but this hack proved quite useful.

'''
Created on Feb 15, 2013
@author: Trevor van Hoof
@package Qtutils
'''


class UnboundSignal():
    def __init__(self):
        self._functions = []
    
    def emit(self):
        for function in self._functions:
            function()
    
    def connect(self, inBoundFunction):
        self._functions.append( inBoundFunction )
    
    def disconnect(self, inBoundFunction):
        try:
            self._functions.remove( inBoundFunction )
        except:
            print('Warning: function %s not removed from signal %s'%(inBoundFunction,self))

So here’s the UnboundSignal class I use, it just implements all the signal functionality I use (new style) and then I can instantiate it, it is only not aware of what parent it has or the self class, but as a bonus it could be driven by multiple classes or instances at the same time.

Example: when you wish to have one object fill the gap between two others you need the middle object to link to the resizeEvent of both or you just give the other objects a shared signal.

Then for our test class we need to initialize it with a parent, always.

from PyQt4 import QtGui
from Qtutils.LaunchAsStandalone import QtStandalone
from Qtutils.unboundsignal import UnboundSignal

class Tst(QtGui.QFrame):
    def __init__(self, inParent):
        QtGui.QFrame.__init__(self, inParent)

Then as the parent is known we can give that parent a resized property, set it to a new signal and connec to that signal.

        self.parent().resized = UnboundSignal()
        self.parent().resized.connect( self.doPrint )

Lastly we need to override the resizeEvent and show the widget:

        self.parent().resizeEvent = self.extendResizeEvent( self.parent().resizeEvent )
        
        self.show()

Now for that extend method:

    '''
    Awesome method extension from
    http://stackoverflow.com/a/2789542
    '''
    def extendResizeEvent(self, fn):
        def extendedResizeEvent(*args, **kwargs):
            fn(*args, **kwargs)
            fn.__self__.resized.emit()
            #we could do this instead of using the signal:
            #self.updatePosition()
            #but the signal could be created out of
            #this class and be globally accessible
        return extendedResizeEvent

It could even stack infinitely and as long as all the extensions do not depend on new arguments it is reasonably maintainable code. Then last let’s launch the app:

def main():
    w = QtGui.QWidget()
    Tst(w)
    w.show()
    return w
    
QtStandalone(main)

The QtStandalone class can be found in this post.

To finish this example we could implement no parent initializing and override the setParent command to disconnect from the current signal and create another signal on another parent again; or always have this class be owner of the signal instead of the parent that emits it (also reverting the function); but that may lead to more trouble when doing this with multiple objects to the same parent. Also we should check whether the parent already has a resized signal in which case the initialization is not necessary.

Leave a Reply

Your email address will not be published. Required fields are marked *