如何在Python curses模块中删除子窗口
我有一个使用了子窗口的 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 个回答
这段代码有两个问题。
首先,正如之前的帖子提到的,子窗口和父窗口共享一个缓冲区,所以如果你想要一个完全独立的窗口,应该使用 curses.newwin()
。
其次,使用 del
来删除一个窗口是有问题的,因为它依赖于引用计数和垃圾回收来正常工作。(换句话说,你必须删除对这个窗口的所有引用,才能让它正常工作。)我建议使用 curses.panel
模块来明确地显示或隐藏窗口。
你为什么要使用子窗口?如果你创建一个新的顶层窗口,代码就能正常工作了——只需把 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 的专家,但希望这能让你更进一步。