如何用PyGTK / Gtkbuilder重复显示一个Dialog?

时间:2011-01-11 11:56:54

标签: python dialog gtk pygtk gtkbuilder

我创建了一个PyGTK应用程序,当用户按下按钮时会显示一个Dialog。 该对话框在我的__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(),但对话框没有被销毁,但我觉得这对我来说不是正确的方法,因为我希望对话框能够表现模态并仅在用户关闭代码后才将控制权返回给代码。

是否有一种使用gtkbuilder使用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 个答案:

答案 0 :(得分:5)

实际上,请阅读Dialog.run()上的documentation。该对话框不会自动销毁。如果您hide()方法退出时run(),那么您应该可以run()多次{。}}。

或者,您可以在构建器文件中将对话框设置为模态,然后只需show()它。这将实现与run()类似但不完全相同的效果 - 因为run()创建了主GTK循环的第二个实例。

修改

如果未连接到delete-event信号,则出现分段错误的原因是您单击关闭按钮两次。以下是发生的事情:

  1. 单击“运行对话框”,这将调用对话框的run()方法。
  2. 出现模态对话框,并启动自己的主循环。
  3. 您单击关闭按钮。对话框的主循环退出,但由于run()会覆盖关闭按钮的正常行为,因此不会关闭对话框。它也没有被隐藏,所以它会挂起来。
  4. 您想知道为什么对话框仍然存在并再次单击关闭按钮。由于run()不再处于活动状态,因此会触发关闭按钮的正常行为:对话框将被销毁。
  5. 再次单击“运行对话框”,尝试调用已销毁对话框的run()方法。崩溃!
  6. 因此,如果您确保在步骤3之后hide()对话框,那么一切都应该有效。无需连接delete-event信号。

答案 1 :(得分: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 :(得分:1)

您的对话框只需要运行一次。假设菜单项触发对话框,代码应如下所示:

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. Dialog调用响应信号的处理程序
  4. Dialog调用Ok按钮的处理程序
  5. 对话框run()方法返回响应
  6. 为防止步骤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')