Question

I am updating some code from using libglade to GtkBuilder, which is supposed to be the way of the future.

With gtk.glade, you could call glade_xml.signal_autoconnect(...) repeatedly to connect signals onto objects of different classes corresponding to different windows in the program. However Builder.connect_signals seems to work only once, and (therefore) to give warnings about any handlers that aren't defined in the first class that's passed in.

I realize I can connect them manually but this seems a bit laborious. (Or for that matter I could use some getattr hackery to let it connect them through a proxy to all the objects...)

Is it a bug there's no function to hook up handlers across multiple objects? Or am I missing something?

Someone else has a similar problem http://www.gtkforums.com/about1514.html which I assume means this can't be done.

Was it helpful?

Solution

Here's what I currently have. Feel free to use it, or to suggest something better:

class HandlerFinder(object):
    """Searches for handler implementations across multiple objects.
    """
    # See <http://stackoverflow.com/questions/4637792> for why this is
    # necessary.

    def __init__(self, backing_objects):
        self.backing_objects = backing_objects

    def __getattr__(self, name):
        for o in self.backing_objects:
            if hasattr(o, name):
                return getattr(o, name)
        else:
            raise AttributeError("%r not found on any of %r"
                % (name, self.backing_objects))

OTHER TIPS

I have been looking for a solution to this for some time and found that it can be done by passing a dict of all the handlers to connect_signals.

The inspect module can extract methods using inspect.getmembers(instance, predicate=inspect.ismethod These can then be concatenated into a dictionary using d.update(d3), watching out for duplicate functions such as on_delete.

Example code:

import inspect
...    
handlers = {}
for c in [win2, win3, win4, self]:  # self is the main window
    methods = inspect.getmembers(c, predicate=inspect.ismethod)
    handlers.update(methods)
builder.connect_signals(handlers)

This will not pick up alias method names declared using @alias. For an example of how to do that, see the code for Builder.py, at def dict_from_callback_obj.

I'm only a novice but this is what I do, maybe it can inspire;-)

I instantiate the major components from a 'control' and pass the builder object so that the instantiated object can make use of any of the builder objects (mainwindow in example) or add to the builder (aboutDialog example). I also pass a dictionary (dic) where each component adds "signals" to it.
Then the 'connect_signals(dic)' is executed.
Of course I need to do some manual signal connecting when I need to pass user arguments to the callback method, but those are few.

#modules.control.py
class Control:

    def __init__(self):

        # Load the builder obj
        guibuilder = gtk.Builder()
        guibuilder.add_from_file("gui/mainwindow.ui")
        # Create a dictionnary to store signal from loaded components
        dic = {}

        # Instanciate the components...
        aboutdialog = modules.aboutdialog.AboutDialog(guibuilder, dic)           
        mainwin = modules.mainwindow.MainWindow(guibuilder, dic, self)
        ...

        guibuilder.connect_signals(dic)
        del dic


#modules/aboutdialog.py
class AboutDialog:

    def __init__(self, builder, dic):
        dic["on_OpenAboutWindow_activate"] = self.on_OpenAboutWindow_activate
        self.builder = builder

    def on_OpenAboutWindow_activate(self, menu_item):
        self.builder.add_from_file("gui/aboutdialog.ui")
        self.aboutdialog = self.builder.get_object("aboutdialog")
        self.aboutdialog.run()

        self.aboutdialog.destroy()

#modules/mainwindow.py
class MainWindow:

    def __init__(self, builder, dic, controller):

        self.control = controller

        # get gui xml and/or signals
        dic["on_file_new_activate"] = self.control.newFile
        dic["on_file_open_activate"] = self.control.openFile
        dic["on_file_save_activate"] = self.control.saveFile
        dic["on_file_close_activate"] = self.control.closeFile
        ...

        # get needed gui objects
        self.mainWindow = builder.get_object("mainWindow")
        ...

Edit: alternative to auto attach signals to callbacks:
Untested code

def start_element(name, attrs):
    if name == "signal":
        if attrs["handler"]:
            handler = attrs["handler"]
            #Insert code to verify if handler is part of the collection
            #we want.
            self.handlerList.append(handler)

def extractSignals(uiFile)
    import xml.parsers.expat
    p = xml.parsers.expat.ParserCreate()
    p.StartElementHandler = self.start_element
    p.ParseFile(uiFile)

self.handlerList = []
extractSignals(uiFile)

for handler in handlerList:
    dic[handler] = eval(''. join(["self.", handler, "_cb"]))
builder.connect_signals
({ 
   "on_window_destroy" : gtk.main_quit, 
   "on_buttonQuit_clicked" : gtk.main_quit 
})
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top