如何在 Python curses 中创建菜单和子菜单?
AFAIK,Python 中还没有可用的 curses 菜单扩展,因此您必须推出自己的解决方案.我知道这个补丁 http://bugs.python.org/issue1723038 但我不知道它的当前状态.我找到了一个很好的 Python 类,它在 http://www.promisc.org/blog/?p=33 但我也有这个问题.我想制作一个菜单,用户可以在其中选择突出显示的元素,但不是立即执行特定操作,我想显示另一个菜单,然后可能是另一个,要求输入等.我的第一个想法是删除现有的 cmenuscreen.clear() 或 cleanup() 但在绘制新菜单之前不会删除旧菜单,新菜单如下所示:
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/issue1723038 but 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=33 but 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:
0. top
1. Exit
2. Another menu
-- end of the old menu that should go away --
3. first
4. second
5. third
在 cmenu() 中没有删除项目的 remove() 方法.我想旧菜单没有被清除的事实是由 display() 方法中的while True"循环引起的,但是当我删除它时,发生了一些奇怪的事情.我使用的是 Python 2.7,这是我当前的代码:
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:
#!/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()
我真的建议你考虑使用 面板.任何时候你都会有可能重叠的小部件,它会让生活变得更轻松.这是一个可以帮助您入门的简单示例.(curses.beep() 或 curses.flash() 似乎都不适用于我的终端,但这无关紧要)
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)
#!/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.
使用curses.wrapper(callable) 来启动你的应用程序比使用自己的try/except 进行清理要干净.
Using curses.wrapper(callable) to launch your application is cleaner than doing your own try/except with cleanup.
您的班级两次调用 initscr 可能会生成两个屏幕(尚未测试是否在设置时返回相同的屏幕),然后当您有多个菜单时,没有正确处理(应该是什么)不同的窗口/屏幕.我认为将菜单传递给屏幕使用并让菜单制作一个子窗口以在我的示例中显示它更清晰和更好的簿记.
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.
命名列表 'list'
不是一个好主意,因为它隐藏了 list()
函数.
Naming a list 'list'
isn't a great idea, because it shadows the list()
function.
如果您想启动另一个终端应用程序,例如top",最好先让 python 干净地退出curses,然后再启动,以防止终端设置出现任何问题.
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.