简体   繁体   English

如何在 Python curses 中创建菜单和子菜单?

[英]How to create a menu and submenus in Python curses?

AFAIK, there is no curses menu extension available in Python yet so you have to roll your own solution. AFAIK,Python 中还没有可用的 curses 菜单扩展,因此您必须推出自己的解决方案。 I know about this patch http://bugs.python.org/issue1723038 but I don't what's the current state of it.我知道这个补丁http://bugs.python.org/issue1723038但我不知道它的当前状态。 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.我找到了一个很好的 Python 类,它在http://www.promisc.org/blog/?p=33 处包装了我想要的“cmenu”,但我也有这个问题。 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:我想制作一个菜单,用户可以在其中选择突出显示的元素,但不是立即执行特定操作,我想显示另一个菜单,然后可能是另一个菜单,要求输入等。我的第一个想法是删除现有的 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().在 cmenu() 中没有删除项目的 remove() 方法。 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.我想旧菜单没有被清除的事实是由 display() 方法中的“while True”循环引起的,但是当我删除它时,发生了一些奇怪的事情。 I am using Python 2.7, this is my current code:我正在使用 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()

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.您的班级两次调用 initscr 可能会生成两个屏幕(尚未测试它是否在设置时返回相同的屏幕),然后当您有多个菜单时,没有正确处理(应该是什么)不同的窗口/屏幕。 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.我认为将菜单传递给屏幕使用并让菜单制作一个子窗口以显示在我的示例中更清晰,更好的簿记。

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,然后再启动,以防止任何终端设置的混乱。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM