如何在PyGTK/Gtkbuilder中重复显示对话框?

11 投票
3 回答
7688 浏览
提问于 2025-04-16 09:45

我创建了一个PyGTK应用程序,当用户按下一个按钮时,会显示一个对话框。这个对话框是在我的 __init__ 方法中加载的:

builder = gtk.Builder()
builder.add_from_file("filename")
builder.connect_signals(self) 
self.myDialog = builder.get_object("dialog_name")

在事件处理函数中,我用命令 self.myDialog.run() 来显示对话框,但这只有效一次,因为在 run() 执行后,对话框会自动被销毁。如果我第二次点击按钮,应用程序就会崩溃。

我听说可以用 show() 来代替 run(),这样对话框就不会被销毁,但我觉得这样不太合适,因为我希望对话框是模态的,也就是说,只有在用户关闭它后,程序才可以继续执行。

有没有简单的方法可以使用 run() 方法重复显示对话框,并且使用gtkbuilder?我尝试过重新加载整个对话框,但这似乎不太有效,对话框缺少了所有的子元素(而且我更希望在程序开始时只使用一次构建器)。


[解决方案](编辑过)
正如下面的回答所指出的,使用 hide() 就可以解决这个问题。我起初以为还需要捕捉“删除事件”,但实际上并不需要。一个简单的有效示例是:


import pygtk
import gtk

class DialogTest:

    def rundialog(self, widget, data=None):
        self.dia.show_all()
        result = self.dia.run() 
        self.dia.hide()


    def destroy(self, widget, data=None):
        gtk.main_quit()

    def __init__(self):
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.connect("destroy", self.destroy)

        self.dia = gtk.Dialog('TEST DIALOG', self.window, 
           gtk.DIALOG_MODAL  | gtk.DIALOG_DESTROY_WITH_PARENT)
        self.dia.vbox.pack_start(gtk.Label('This is just a Test'))


        self.button = gtk.Button("Run Dialog")    
        self.button.connect("clicked", self.rundialog, None)
        self.window.add(self.button)
        self.button.show()
        self.window.show()



if __name__ == "__main__":
    testApp = DialogTest()
    gtk.main()

3 个回答

2

我花了一些时间搞明白这一点。从一个构建器中重新获取同一个对象并不会创建一个新的对象实例,而只是返回一个指向旧的(已经被销毁的)对象的引用。不过,如果你创建一个新的构建器实例,并把文件加载到这个新的构建器中,它就会创建一个新的对象实例。

所以我的对话框创建函数大概是这样的:

def create():
    builder = gtk.Builder()
    builder.add_from_file('gui/main.ui')

    dlg = builder.get_object('new_dialog')

    def response_function(dialog, response_id):
        ... do stuff ...
        dialog.destroy()

    dlg.connect('response', response_function)
    dlg.show_all()

需要注意的是,在这种情况下,我没有使用run()来等待响应,因为我在使用twisted,但这应该是等效的。

2

你的对话框只需要运行一次。假设是通过菜单项来触发这个对话框,代码大概是这样的:

def on_menu_item_clicked(self, widget, data=None):
    dialog = FunkyDialog()
    response = dialog.run()

    if response = gtk.RESPONSE_OK:
        // do something with the dialog data

    dialog.destroy()

dialog.run() 是一个阻塞的主循环,只有当对话框发送了响应后才会返回。通常这个响应是通过“确定”和“取消”按钮来完成的。当这个过程结束时,对话框就完成了,需要被销毁。

如果想要多次显示这个对话框,用户需要按照相同的流程操作(在上面的例子中,就是点击菜单项)。对话框在 __init__ 方法中负责自己初始化。如果你选择 hide() 隐藏对话框,就会遇到一个问题:如何与这个对话框进行沟通,以便它能与应用程序的其他部分保持同步,即使它是隐藏的

有些人希望“重复运行对话框”的原因之一是用户输入了无效的信息,你想给用户一个机会来纠正它。这需要在对话框的响应信号处理器中处理。对话框中的事件顺序是:

  1. 用户实际按下“确定”按钮
  2. 对话框发送响应 gtk.RESPONSE_OK (-5)
  3. 对话框调用响应信号的处理器
  4. 对话框调用“确定”按钮的处理器
  5. 对话框的 run() 方法返回响应

为了防止第4步和第5步发生,响应处理器必须抑制响应信号。可以通过以下方式实现:

def on_dialog_response(self, dialog, response, data=None:
    if response == gtk.RESPONSE_OK:
        if data_is_not_valid:
            # Display an error message to the user

            # Suppress the response
            dialog.emit_stop_by_name('response')
7

其实,你可以看看关于 Dialog.run()文档。对话框并不会自动被销毁。如果你在 run() 方法结束时调用 hide(),那么你就可以随意多次调用 run()

另外,你也可以在构建文件中把对话框设置为模态,然后只需调用 show()。这样做的效果和 run() 有点像,但不完全一样,因为 run() 会创建一个新的主 GTK 循环。

编辑

如果你没有连接到 delete-event 信号而出现段错误,是因为你点击了关闭按钮两次。事情是这样的:

  1. 你点击“运行对话框”,这会调用对话框的 run() 方法。
  2. 模态对话框出现,并开始自己的主循环。
  3. 你点击关闭按钮。对话框的主循环退出,但由于 run() 改变了关闭按钮的正常行为,对话框并没有关闭。它也没有被隐藏,所以还在那儿。
  4. 你会想,为什么对话框还在,然后又点击关闭按钮。此时 run() 已经不活跃了,关闭按钮的正常行为被触发:对话框被销毁。
  5. 你再次点击“运行对话框”,这时试图调用已经被销毁的对话框的 run() 方法。崩溃了!

所以,如果你确保在第 3 步后调用 hide(),那么一切就应该正常。其实不需要连接到 delete-event 信号。

撰写回答