Python Tkinter: 通过回调事件更改窗口标题时应用程序卡顿

3 投票
3 回答
904 浏览
提问于 2025-04-17 00:10

我正在用Tkinter创建一个简单的Python界面,想用self.title来改变窗口的标题,当某个事件发生时。

如果我把这个事件绑定到一个按钮上,或者直接在Tk线程中调用事件处理函数,窗口标题就会按预期改变。但是,我想让这个事件由一个单独的线程来触发,结果发现,在事件处理函数中使用title会导致应用程序卡住。

我在事件处理函数中做的其他事情(比如更新标签)都没问题,所以我猜这个事件是正常触发的。我试过用wm_title代替title,但没有看到什么不同。我查了一下title的用法,发现没有什么奇怪的地方,只要用一个字符串来设置标题就可以了。

这里有一个简化的示例,能重现这个问题(我在WinXP上运行v2.7.1,供参考);这个应用程序运行正常10秒(可以移动窗口、调整大小等),然后Timer生成事件后,应用程序就会冻结。

import Tkinter
import threading

class Gui(Tkinter.Tk):

    def __init__(self, parent=None):
        Tkinter.Tk.__init__(self, parent)
        self.title('Original Title')
        self.label = Tkinter.Label(self, text='Just a Label.',
            width=30, anchor='center')
        self.label.grid()

        self.bind('<<change_title>>', self.change_title)

        timer = threading.Timer(10, self.event_generate, ['<<change_title>>'])
        timer.start()

    def change_title(self, event=None):
        self.title('New Title')

G = Gui(None)
G.mainloop()

3 个回答

0

我也试过在2.6.2版本(Windows系统)上,但标题没有变化。不过没有出现运行错误。

0

嗯,我觉得你的代码运行得很好。

不过,如果在十秒之前中断它,就会出现“RuntimeError: main thread is not in main loop”的错误提示。

我是在ubuntu 10.10上使用python 2.6.6。

希望这些信息对你有帮助。

2

我遇到了同样的问题,就是当从主线程以外的线程调用self.title()时,界面会卡住。Tkinter要求所有的界面操作都必须在同一个线程中进行,也就是主线程。

我的解决办法是让那个单独的线程把函数放到一个队列里。主线程会定期处理这个队列,利用Tkinter提供的after(ms)函数。下面是一个用你代码的例子:

import Tkinter
import threading
from Queue import Queue, Empty

class Gui(Tkinter.Tk):

    def __init__(self, parent=None):
        Tkinter.Tk.__init__(self, parent)
        self.ui_queue = Queue()
        self._handle_ui_request()
        self.title('Original Title')
        self.label = Tkinter.Label(self, text='Just a Label.',
            width=30, anchor='center')
        self.label.grid()

        self.bind('<<change_title>>', self.change_title)

        timer = threading.Timer(1, self.event_generate, ['<<change_title>>'])
        timer.start()

    def change_title(self, event=None):
        # Separate the function name, it's args and keyword args, 
        # and put it in the queue as a tuple.
        ui_function = (self.title, ('New Title',), {})
        self.ui_queue.put(ui_function)

    def _handle_ui_request(self):
        '''
        Periodically services the UI queue to handles UI requests in the main thread.
        '''
        try:
            while True:
                f, a, k = self.ui_queue.get_nowait()
                f(*a, **k)
        except Empty:
            pass

        self.after(200, self._handle_ui_request)


G = Gui(None)
G.mainloop()

撰写回答