如何在PyGTK/Gtkbuilder中重复显示对话框?
我创建了一个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 个回答
我花了一些时间搞明白这一点。从一个构建器中重新获取同一个对象并不会创建一个新的对象实例,而只是返回一个指向旧的(已经被销毁的)对象的引用。不过,如果你创建一个新的构建器实例,并把文件加载到这个新的构建器中,它就会创建一个新的对象实例。
所以我的对话框创建函数大概是这样的:
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,但这应该是等效的。
你的对话框只需要运行一次。假设是通过菜单项来触发这个对话框,代码大概是这样的:
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()
隐藏对话框,就会遇到一个问题:如何与这个对话框进行沟通,以便它能与应用程序的其他部分保持同步,即使它是隐藏的。
有些人希望“重复运行对话框”的原因之一是用户输入了无效的信息,你想给用户一个机会来纠正它。这需要在对话框的响应信号处理器中处理。对话框中的事件顺序是:
- 用户实际按下“确定”按钮
- 对话框发送响应
gtk.RESPONSE_OK
(-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')
其实,你可以看看关于 Dialog.run()
的 文档。对话框并不会自动被销毁。如果你在 run()
方法结束时调用 hide()
,那么你就可以随意多次调用 run()
。
另外,你也可以在构建文件中把对话框设置为模态,然后只需调用 show()
。这样做的效果和 run()
有点像,但不完全一样,因为 run()
会创建一个新的主 GTK 循环。
编辑
如果你没有连接到 delete-event
信号而出现段错误,是因为你点击了关闭按钮两次。事情是这样的:
- 你点击“运行对话框”,这会调用对话框的
run()
方法。 - 模态对话框出现,并开始自己的主循环。
- 你点击关闭按钮。对话框的主循环退出,但由于
run()
改变了关闭按钮的正常行为,对话框并没有关闭。它也没有被隐藏,所以还在那儿。 - 你会想,为什么对话框还在,然后又点击关闭按钮。此时
run()
已经不活跃了,关闭按钮的正常行为被触发:对话框被销毁。 - 你再次点击“运行对话框”,这时试图调用已经被销毁的对话框的
run()
方法。崩溃了!
所以,如果你确保在第 3 步后调用 hide()
,那么一切就应该正常。其实不需要连接到 delete-event
信号。