拖动主窗口时打开对话框导致GTK程序冻结问题求助
拖动主窗口时弹出对话框导致程序冻结的问题
我遇到了一个问题:当拖动主窗口时,弹出的消息对话框会导致程序冻结。我尝试使用idle_add来显示对话框,但没能解决这个问题。
以下是Gtk 2.0的测试代码:
from time import sleep import gtk import pygtk pygtk.require("2.0") from threading import Thread import gobject class PyApp(gtk.Window): def __init__(self): super(PyApp, self).__init__() self.connect("destroy", gtk.main_quit) self.set_size_request(250, 100) self.set_position(gtk.WIN_POS_CENTER) self.set_title("Test") btn = gtk.Button("Click Here") btn.connect("clicked", self.on_click) self.add(btn) self.show_all() def decorator_threaded(func): def wrapper(*args, **kwargs): gtk.gdk.threads_enter() thread = Thread(target=func, args=args, kwargs=kwargs) thread.start() return thread return wrapper @decorator_threaded def running_on_another_thread(self): sleep(2) # 模拟耗时任务 gobject.idle_add(self.error_message) def on_click(self, widget): self.running_on_another_thread() def error_message(self): md = gtk.MessageDialog(self, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, "Error") md.run() md.destroy() PyApp() gtk.gdk.threads_init() gtk.main()
我也尝试了Gtk 3.0,同样出现冻结问题:
import threading import time import gi gi.require_version("Gtk", "3.0") from gi.repository import GLib, Gtk def app_main(): win = Gtk.Window(default_height=100, default_width=250) win.connect("destroy", Gtk.main_quit) win.set_position(Gtk.WindowPosition.CENTER) def error_message(): md = Gtk.MessageDialog( transient_for=win, flags=0, message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.CLOSE, text="Error", ) md.run() md.destroy() def example_target(): time.sleep(2) # 模拟耗时任务 GLib.idle_add(error_message) win.show_all() thread = threading.Thread(target=example_target) thread.daemon = True thread.start() if __name__ == "__main__": app_main() Gtk.main()
解决方案
问题出在调用md.run()时,这个方法会启动一个嵌套的主循环,而当主窗口正在被拖动时,GTK的主循环处于**事件抓取(grab)**状态,嵌套循环无法正常获取事件,最终导致程序冻结。
解决方法是避免使用run()阻塞主循环,改用非模态方式显示对话框,并通过响应信号来处理关闭逻辑:
Gtk 3.0 修改后的代码
import threading import time import gi gi.require_version("Gtk", "3.0") from gi.repository import GLib, Gtk def app_main(): win = Gtk.Window(default_height=100, default_width=250) win.connect("destroy", Gtk.main_quit) win.set_position(Gtk.WindowPosition.CENTER) def error_message(): md = Gtk.MessageDialog( transient_for=win, flags=0, message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.CLOSE, text="Error", ) # 连接响应信号,点击按钮后销毁对话框 md.connect("response", lambda dialog, response_id: dialog.destroy()) # 非模态显示对话框 md.present() def example_target(): time.sleep(2) # 模拟耗时任务 GLib.idle_add(error_message) win.show_all() thread = threading.Thread(target=example_target) thread.daemon = True thread.start() if __name__ == "__main__": app_main() Gtk.main()
Gtk 2.0 修改后的代码
from time import sleep import gtk import pygtk pygtk.require("2.0") from threading import Thread import gobject class PyApp(gtk.Window): def __init__(self): super(PyApp, self).__init__() self.connect("destroy", gtk.main_quit) self.set_size_request(250, 100) self.set_position(gtk.WIN_POS_CENTER) self.set_title("Test") btn = gtk.Button("Click Here") btn.connect("clicked", self.on_click) self.add(btn) self.show_all() def decorator_threaded(func): def wrapper(*args, **kwargs): thread = Thread(target=func, args=args, kwargs=kwargs) thread.start() return thread return wrapper @decorator_threaded def running_on_another_thread(self): sleep(2) # 模拟耗时任务 gobject.idle_add(self.error_message) def on_click(self, widget): self.running_on_another_thread() def error_message(self): md = gtk.MessageDialog(self, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, "Error") # 连接响应信号处理关闭 md.connect("response", lambda dialog, response_id: dialog.destroy()) # 非模态显示 md.show() PyApp() gtk.gdk.threads_init() gtk.main()
另外,Gtk 2.0原代码中的线程装饰器里的gtk.gdk.threads_enter()是多余的,甚至可能引发问题,因为装饰器是在主线程调用的,启动子线程时不需要这个操作,所以已经在修改后的代码中去掉了。
这样修改后,拖动主窗口时弹出对话框就不会再冻结了。
内容的提问来源于stack exchange,提问作者Gilson Urbano
相关产品推荐
相关产品推荐

