pygtk gtk.Builder.connect_signals 连接多个对象?

3 投票
4 回答
2840 浏览
提问于 2025-04-16 09:39

我正在把一些代码从使用libglade更新到GtkBuilder,听说这是未来的趋势。

在使用gtk.glade的时候,你可以反复调用 glade_xml.signal_autoconnect(...) 来把信号连接到不同窗口的不同类的对象上。但是 Builder.connect_signals 似乎只能用一次,因此如果在第一个传入的类中没有定义某些处理程序,它就会发出警告。

我知道我可以手动连接这些信号,但这感觉有点麻烦。(或者我可以用一些getattr的小技巧,通过一个代理来连接所有对象……)

难道没有一个函数可以在多个对象之间连接处理程序,这算是个bug吗?还是我漏掉了什么?

还有其他人也遇到类似的问题,http://www.gtkforums.com/about1514.html,我想这意味着这件事是做不到的。

4 个回答

2

我只是个新手,但这是我做的事情,也许能给你一些启发;-)

我从一个“控制器”中创建主要的组件,并把构建器对象传递给它,这样创建出来的对象就可以使用任何构建器对象(比如示例中的主窗口),或者可以往构建器里添加东西(比如关于对话框的例子)。我还传递了一个字典(dic),每个组件会往里面添加“信号”。
然后我会执行 'connect_signals(dic)'。
当然,当我需要把用户的参数传递给回调方法时,我需要手动连接一些信号,但这种情况不多。

#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")
        ...

补充:自动将信号连接到回调的替代方法:
这段代码还没测试过

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"]))
3

我找这个问题的解决办法已经有一段时间了,发现可以通过把所有处理函数放到一个字典里,然后传给 connect_signals 来实现。

可以用 inspect 模块来提取方法,方法是使用 inspect.getmembers(instance, predicate=inspect.ismethod)。然后可以用 d.update(d3) 把这些方法合并到一个字典里,不过要注意避免重复的函数,比如 on_delete

示例代码:

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)

不过,这种方法不会抓取用 @alias 声明的别名方法。如果想知道怎么做,可以看看 Builder.py 文件里的 def dict_from_callback_obj 这部分代码。

4

这是我现在的代码。你可以随意使用它,或者给我提一些更好的建议:

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))

撰写回答