如何在 Python curses 中创建菜单和子菜单?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14200721/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to create a menu and submenus in Python curses?
提问by user1042840
AFAIK, there is no curses menu extension available in Python yet so you have to roll your own solution. I know about this patch http://bugs.python.org/issue1723038but I don't what's the current state of it. I found a nice class for Python that wraps what I want called 'cmenu' here http://www.promisc.org/blog/?p=33but I have a problem with that too. I want to make a menu where user can choose a highlighted element but instead of executing a particular action right away I want to display another menu, and then maybe another, ask for some input etc. My first thought was to remove the existing cmenu with screen.clear() or cleanup() but the old menu is not removed before the new one is drawn and the new menu looks like this:
AFAIK,Python 中还没有可用的 curses 菜单扩展,因此您必须推出自己的解决方案。我知道这个补丁http://bugs.python.org/issue1723038但我不知道它的当前状态。我找到了一个很好的 Python 类,它在http://www.promisc.org/blog/?p=33 处包装了我想要的“cmenu”,但我也有这个问题。我想制作一个菜单,用户可以在其中选择突出显示的元素,但不是立即执行特定操作,我想显示另一个菜单,然后可能是另一个,要求输入等。我的第一个想法是删除现有的 cmenu screen.clear() 或 cleanup() 但在绘制新菜单之前不会删除旧菜单,新菜单如下所示:
0. top
1. Exit
2. Another menu
-- end of the old menu that should go away --
3. first
4. second
5. third
There is no remove() method for removing an item in cmenu(). I guess the fact that the old menu is not cleared is caused by 'while True' loop in display() method but when I removed it some weird stuff was going on. I am using Python 2.7, this is my current code:
在 cmenu() 中没有删除项目的 remove() 方法。我想旧菜单没有被清除的事实是由 display() 方法中的“while True”循环引起的,但是当我删除它时,发生了一些奇怪的事情。我正在使用 Python 2.7,这是我当前的代码:
#!/usr/bin/python
#
# Adapted from:
# http://blog.skeltonnetworks.com/2010/03/python-curses-custom-menu/
#
# Goncalo Gomes
# http://promisc.org
#
import signal
signal.signal(signal.SIGINT, signal.SIG_IGN)
import os
import sys
import curses
import traceback
import atexit
import time
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
class cmenu(object):
datum = {}
ordered = []
pos = 0
def __init__(self, options, title="python curses menu"):
curses.initscr()
curses.start_color()
curses.init_pair(1, curses.COLOR_RED, curses.COLOR_WHITE)
curses.curs_set(0)
self.screen = curses.initscr()
self.screen.keypad(1)
self.h = curses.color_pair(1)
self.n = curses.A_NORMAL
for item in options:
k, v = item.items()[0]
self.datum[k] = v
self.ordered.append(k)
self.title = title
atexit.register(self.cleanup)
def cleanup(self):
curses.doupdate()
curses.endwin()
def upKey(self):
if self.pos == (len(self.ordered) - 1):
self.pos = 0
else:
self.pos += 1
def downKey(self):
if self.pos <= 0:
self.pos = len(self.ordered) - 1
else:
self.pos -= 1
def display(self):
screen = self.screen
while True:
screen.clear()
screen.addstr(2, 2, self.title, curses.A_STANDOUT|curses.A_BOLD)
screen.addstr(4, 2, "Please select an interface...", curses.A_BOLD)
ckey = None
func = None
while ckey != ord('\n'):
for n in range(0, len(self.ordered)):
optn = self.ordered[n]
if n != self.pos:
screen.addstr(5 + n, 4, "%d. %s" % (n, optn), self.n)
else:
screen.addstr(5 + n, 4, "%d. %s" % (n, optn), self.h)
screen.refresh()
ckey = screen.getch()
if ckey == 258:
self.upKey()
if ckey == 259:
self.downKey()
ckey = 0
self.cleanup()
if self.pos >= 0 and self.pos < len(self.ordered):
self.datum[self.ordered[self.pos]]()
self.pos = -1
else:
curses.flash()
def top():
os.system("top")
def exit():
sys.exit(1)
def submenu():
# c.screen.clear() # nope
# c.cleanup() # nope
submenu_list = [{"first": exit}, {"second": exit}, {"third": exit}]
submenu = cmenu(submenu_list)
submenu.display()
try:
list = [{ "top": top }, {"Exit": exit}, {"Another menu": submenu}]
c = cmenu(list)
c.display()
except SystemExit:
pass
else:
#log(traceback.format_exc())
c.cleanup()
采纳答案by kalhartt
I really recommend you look into using panels. Anytime you will have widgets that could possibly overlap, it makes life alot easier. This is a simple example that should get you started. (Neither curses.beep() or curses.flash() seem to work on my terminal, but that is beside the point)
我真的建议您考虑使用面板。任何时候你都会有可能重叠的小部件,它会让生活变得更轻松。这是一个可以帮助您入门的简单示例。(curses.beep() 或 curses.flash() 似乎都不适用于我的终端,但这无关紧要)
#!/usr/bin/env python
import curses
from curses import panel
class Menu(object):
def __init__(self, items, stdscreen):
self.window = stdscreen.subwin(0, 0)
self.window.keypad(1)
self.panel = panel.new_panel(self.window)
self.panel.hide()
panel.update_panels()
self.position = 0
self.items = items
self.items.append(("exit", "exit"))
def navigate(self, n):
self.position += n
if self.position < 0:
self.position = 0
elif self.position >= len(self.items):
self.position = len(self.items) - 1
def display(self):
self.panel.top()
self.panel.show()
self.window.clear()
while True:
self.window.refresh()
curses.doupdate()
for index, item in enumerate(self.items):
if index == self.position:
mode = curses.A_REVERSE
else:
mode = curses.A_NORMAL
msg = "%d. %s" % (index, item[0])
self.window.addstr(1 + index, 1, msg, mode)
key = self.window.getch()
if key in [curses.KEY_ENTER, ord("\n")]:
if self.position == len(self.items) - 1:
break
else:
self.items[self.position][1]()
elif key == curses.KEY_UP:
self.navigate(-1)
elif key == curses.KEY_DOWN:
self.navigate(1)
self.window.clear()
self.panel.hide()
panel.update_panels()
curses.doupdate()
class MyApp(object):
def __init__(self, stdscreen):
self.screen = stdscreen
curses.curs_set(0)
submenu_items = [("beep", curses.beep), ("flash", curses.flash)]
submenu = Menu(submenu_items, self.screen)
main_menu_items = [
("beep", curses.beep),
("flash", curses.flash),
("submenu", submenu.display),
]
main_menu = Menu(main_menu_items, self.screen)
main_menu.display()
if __name__ == "__main__":
curses.wrapper(MyApp)
Some things to note when looking over your code.
查看代码时需要注意的一些事项。
Using curses.wrapper(callable) to launch your application is cleaner than doing your own try/except with cleanup.
使用curses.wrapper(callable) 来启动你的应用程序比使用自己的try/except 进行清理更干净。
Your class calls initscr twice which will probably generate two screens (havent tested if it returns the same screen if its setup), and then when you have multiple menus there is no proper handling of (what should be) different windows/screens. I think its clearer and better bookkeeping to pass the menu the screen to use and let the menu make a subwindow to display in as in my example.
您的班级调用 initscr 两次,这可能会生成两个屏幕(尚未测试它是否在设置时返回相同的屏幕),然后当您有多个菜单时,没有正确处理(应该是什么)不同的窗口/屏幕。我认为将菜单传递给屏幕使用并让菜单制作一个子窗口以显示在我的示例中更清晰,更好的簿记。
Naming a list 'list'isn't a great idea, because it shadows the list()function.
命名一个列表'list'并不是一个好主意,因为它掩盖了list()函数。
If you want to launch another terminal app like 'top', it is probably better to let python exit curses cleanly first then launch in order to prevent any futzing with terminal settings.
如果你想启动另一个像“top”这样的终端应用程序,最好先让 python 干净地退出curses,然后再启动,以防止任何终端设置的混乱。

