You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

拖动主窗口时打开对话框导致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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 17:05:22