如何在Python curses模块中删除子窗口

5 投票
2 回答
4101 浏览
提问于 2025-04-17 13:59

我有一个使用了子窗口的 curses 应用程序,但我似乎无法删除这些子窗口。

比如,这段代码就不管用:

import curses
def fill(window, ch):
    y, x = window.getmaxyx()
    s = ch * (x - 1)
    for line in range(y):
        window.addstr(line, 0, s)

def main(stdscr):
    fill(stdscr, 'M')
    stdscr.refresh()
    stdscr.getch()

    subwin = stdscr.subwin(1, 28, 20, 13)
    fill(subwin, 'J')
    subwin.refresh()
    subwin.getch()

    del subwin
    stdscr.touchwin()
    stdscr.refresh()
    stdscr.getch()

curses.wrapper(main)

当你运行这段代码时,屏幕上会填满'M',然后当你按下一个键时,会创建一个子窗口,并填充'J'。最后,当你再按一个键时,代码会删除这个子窗口,并完全重新绘制屏幕。然而,那些'J'还是留在那儿。

经过一些实验,我发现调用 stdscr 的 clear() 方法可以让子窗口消失,但我想恢复背景到原来的样子,而不是把它清空再重写。有没有人知道怎么做到这一点?

2 个回答

1

这段代码有两个问题。

首先,正如之前的帖子提到的,子窗口和父窗口共享一个缓冲区,所以如果你想要一个完全独立的窗口,应该使用 curses.newwin()

其次,使用 del 来删除一个窗口是有问题的,因为它依赖于引用计数和垃圾回收来正常工作。(换句话说,你必须删除对这个窗口的所有引用,才能让它正常工作。)我建议使用 curses.panel 模块来明确地显示或隐藏窗口。

11

你为什么要使用子窗口?如果你创建一个新的顶层窗口,代码就能正常工作了——只需把 stdscr.subwin 改成 curses.newwin,就能按预期运行。

我不是 curses 的专家,但我觉得子窗口和它的父窗口共享字符缓冲区,也就是说对其中一个的修改会影响到另一个。所以,如果你想把一个窗口分成几个逻辑区域(比如菜单栏、主区域和状态栏),那么子窗口是很有用的。不过,如果你想要的是像对话框或弹出菜单那样的东西,那就需要一个全新的窗口(有自己独立的缓冲区)。

我找不到任何明确的 ncurses 参考资料来支持或反对我的观点,但 AIX 的手册页似乎支持这个观点:

请记住,子窗口共享其父窗口的缓冲区。通过父窗口或任何子窗口对共享窗口缓冲区的修改,会影响所有共享该缓冲区的窗口。

当然,这并不是对 ncurses 的最终结论,但我找不到相反的资料,而且这确实能解释观察到的行为。我还做了一个简单的实验,在你示例中的 subwin.getch() 行后,我加了这一行:

raise Exception(stdscr.instr(20, 15, 3))

在你的示例中,我得到的主窗口内容是 JJJ。如果我把代码改成使用 curses.newwin() 来创建窗口,而不是 stdscr.subwin(),我就得到了预期的 MMM

我不知道有多少特定的 Python curses 资源,但大多数关于 ncurses 的标准教程和文档对这种情况都很有帮助。以前我在使用它时,这篇文档对我很有帮助。如果你往下滚动到“示例”部分,你会看到菜单弹出窗口并不是子窗口——他用以下稍微模糊的解释提到了这一点:

我们不希望这个新窗口覆盖背景上之前写的字符。菜单关闭后,这些字符应该保留在那。这就是为什么菜单窗口不能作为 stdscr 的子窗口创建。

另外,我记得同时使用 stdscr 和你自己的窗口可能会导致问题——“官方”的 ncurses 介绍中有关于这方面的一些警告。它还建议尽量避免重叠窗口,因为这似乎容易出错,但我记得在短期的临时对话框中使用它们时没有遇到任何问题(这也是我唯一的使用场景)。当然,仅仅因为我的简单用例没有暴露出任何问题,并不意味着就没有问题。在像 ncurses 这样复杂的东西中,我认为保持简单是明智的。

希望这些对你有帮助。正如我所说,我绝对不是 curses 的专家,但希望这能让你更进一步。

撰写回答