【问题标题】:Why does Tkinter text widget "lags" updates for increased window heights?为什么 Tkinter 文本小部件“滞后”更新以增加窗口高度?
【发布时间】:2014-08-09 04:16:55
【问题描述】:

我目前正在为neovim 实现一个示例 UI, 并决定使用 Tkinter/python 由于流行/简单 平台。我遇到的问题是 tkinter 似乎“堆叠” 当窗口高度超过某个阈值时,UI 会更新。

Here 是一个显示问题的视频。

右边的窗口是运行neovim的终端模拟器,左边的 window 是连接到它的 Tkinter UI 程序。这个想法是 tkinter UI 应该镜像 neovim 终端屏幕,包括 方面。在这个视频中,永远不要把注意力从 终端窗口,因此 Tk 必须处理的唯一事件来自 连接到 neovim(描述屏幕的虚拟“nvim”事件 更新)

视频的第一部分显示,当 窗口高度很小,但是当我增加时开始滞后更新 高度。

Here 是 Tkinter 程序的代码。虽然 neovim API 非常新并且仍在大量开发中(代码可能对某些读者没有意义),但我认为我要解决的问题接近实现终端 模拟器(使用 Tk 文本小部件):它必须有效地处理大量格式化文本更新。

我在 GUI 编程方面非常缺乏经验。 Tkinter 是一个明智的选择吗? 这个任务?如果是,那么有人可以提示我做错了什么吗?

解释一下发生了什么:Neovim API 是线程安全的,vim.next_event() 方法会阻塞(不忙等待,它使用下面的 libuv 事件循环)直到收到事件。

vim.next_event() 调用返回时,它会使用generate_event 通知Tkinter 线程,该线程将执行实际的事件处理(它还缓冲redraw:startredraw:stop 之间的事件以优化屏幕更新)。

所以实际上有两个事件循环并行运行,后台事件循环以线程安全的方式提供 Tkinter 事件循环(generate_event 方法是少数可以从其他线程调用的方法之一)

【问题讨论】:

  • 您知道,Tk 仅在事件循环空闲时更新绘图? (例如,当after idle 触发时)。您可能只是简单地用许多事件淹没事件循环,因此重绘仅在突发过去后发生。除此之外,您人为地将 Queue 对象的大小限制为 1,因此使用 python 糟糕的线程,您需要在各处支付大量线程更改成本。

标签: python user-interface tkinter tk neovim


【解决方案1】:

我会仔细检查一下,事实上,Tkinter 才是问题所在。我这样做的方法是在收到事件时简单地写到终端。

但现在我仔细看看这可能是你的问题:

    t = Thread(target=get_nvim_events, args=(self.nvim_events,
                                             self.vim,
                                             self.root,))

线程不能很好地处理事件循环——Tkinter 已经有一个。我不确定 neovim api 是否设置为使用回调,但这通常是您希望传播更改的方式。

既然您说您不熟悉 GUI 编程,我将假设您不熟悉事件循环的概念。基本上,假设你有一些看起来像这样的代码:

while True:
    if something_to_do:
        do_it_now()

显然这是一个繁忙的循环,会烧毁你的 CPU,所以通常你的事件循环会阻止或设置操作系统的回调,这允许它放弃 CPU,当有趣的事情发生时,操作系统会说,“有人点击了这里”或“有人按下了一个键”或“嘿,你让我现在叫醒你!”

因此,作为 GUI 开发人员,您的工作就是进入该事件循环。你并不真正关心什么时候发生了什么事——你只是想回应它。使用 Tkinter,您可以使用 .after 方法 see "non-event callbacks" 做到这一点。 .after_idle method:

可能是一个不错的选择

注册一个在系统空闲时调用的回调。回调将被调用,主循环中不再有要处理的事件。每次调用 after_idle 时只会调用一次回调。

这意味着你不会阻止按键或鼠标点击,它只会在 Tkinter 完成处理其他内容(例如绘图、调用回调等)后运行

我预计可能发生的情况是您的线程和主循环出现问题(可能要感谢 GIL)。我环顾四周,但没有立即看到任何明显的东西,但您想要做的事情是:

def do_something(arg):
    # do something with `arg` here


def event_happened(event_args): #whatever args the event generates
    root.after_idle(lambda: do_something(event_args))

vim.bind("did_something", event_happened)

当然,你也可以完全绕过事件循环,让事件做你想做的事。

【讨论】:

  • 我已更新问题以更好地解释发生了什么。
【解决方案2】:

我还观察到 Tkinter 小部件的类似问题,并发现这似乎是由于 Tkinter 降低其性能的问题。如果没有和大修 Tkinter,这个问题似乎无法解决。

【讨论】:

  • 对我来说似乎是一个答案:有时“你不能”就是答案。如果它谈论 tk 的哪些方面产生了这种性能,这可能是一个更好的答案,但我认为它确实回答了这个问题。
猜你喜欢
  • 1970-01-01
  • 2012-06-02
  • 2014-12-28
  • 1970-01-01
  • 1970-01-01
  • 2019-12-05
  • 1970-01-01
  • 2012-09-05
  • 1970-01-01
相关资源
最近更新 更多