5

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

是一个显示问题的视频。

右侧窗口是运行neovim的终端模拟器,左侧窗口是与其连接的Tkinter UI程序。这个想法是 tkinter UI 应该反映 neovim 终端屏幕,包括尺寸。在此视频中,从不将焦点从终端窗口移开,因此 Tk 必须处理的唯一事件来自与 neovim 的连接(描述屏幕更新的虚拟“nvim”事件)

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

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

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

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

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

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

4

2 回答 2

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的方法来做到这一点。方法可能是一个不错的选择:.after_idle

注册一个在系统空闲时调用的回调。回调将被调用,主循环中不再有要处理的事件。每次调用 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)

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

于 2014-06-18T19:23:53.620 回答
0

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

于 2017-05-30T23:00:21.570 回答