如何组合多个TUI表单来编写更复杂的应用程序?

AFo*_*eee 5 python tui console-application urwid npyscreen

我想编写一个带有基于T ext的U ser I接口(TUI)的程序,它由几种形式组成.

介绍几种形式.

  • 第一个表单包含"列表".每个列表元素代表一个按钮.
  • 如果按下相应的按钮,则应出现另一个表单,其中可以输入列表条目的数据.
  • 然后再次显示第一个表单(使用更新的列表条目).

这是我的尝试,它使用库npyscreen但不返回第一个表单.代码也不包含更改列表项的逻辑.

#! /usr/bin/env python3
# coding:utf8

import npyscreen

# content
headers = ["column 1", "column 2", "column 3", "column 4"]
entries = [["a1", "a2", "a3", "a4"],
           ["b1", "b2", "b3", "b4"],
           ["c1", "c2", "c3", "c4"],
           ["d1", "d2", "d3", "d4"], 
           ["e1", "e2", "e3", "e4"]]


# returns a string in which the segments are padded with spaces.
def format_entry(entry):
    return "{:10} | {:10} | {:10} | {:10}".format(entry[0], entry[1] , entry[2], entry[3])


class SecondForm(npyscreen.Form):
    def on_ok(self):
        self.parentApp.switchFormPrevious()

    # add the widgets of the second form
    def create(self):
        self.col1 = self.add(npyscreen.TitleText, name="column 1:")
        self.col2 = self.add(npyscreen.TitleText, name="column 2:")
        self.col3 = self.add(npyscreen.TitleText, name="column 3:")
        self.col4 = self.add(npyscreen.TitleText, name="column 4:")


class MainForm(npyscreen.Form):    
    def on_ok(self):
        self.parentApp.switchForm(None)

    def changeToSecondForm(self):
        self.parentApp.change_form("SECOND")

    # add the widgets of the main form
    def create(self):
        self.add(npyscreen.FixedText, value=format_entry(headers), editable=False, name="header")

        for i, entry in enumerate(entries):
            self.add(npyscreen.ButtonPress, when_pressed_function=self.changeToSecondForm, name=format_entry(entry))


class TestTUI(npyscreen.NPSAppManaged):
    def onStart(self):
        self.addForm("MAIN", MainForm)
        self.addForm("SECOND", SecondForm, name="Edit row")

    def onCleanExit(self):
        npyscreen.notify_wait("Goodbye!")

    def change_form(self, name):
        self.switchForm(name)


if __name__ == "__main__":
    tui = TestTUI()
    tui.run()
Run Code Online (Sandbox Code Playgroud)

Eli*_*les 2

接下来是我对这个问题的看法,它可以被描述为控制台主从用户界面的实现。

\n\n

这使用urwid 库,构建一些自定义小部件来实现所描述的 UI,它有两种模式:主视图(其中主小部件是一堆记录)和详细视图(一个覆盖的对话框,主视图在后面)。

\n\n

有很多事情可以改进,包括让它看起来更漂亮。:)

\n\n

这是代码:

\n\n
#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n"""\nSample program demonstrating how to implement widgets for a master-detail UI\nfor a list of records using the urwid library (http://urwid.org)\n"""\n\nfrom __future__ import print_function, absolute_import, division\nfrom functools import partial\nimport urwid\n\n\nPALETTE = [\n    (\'bold\', \'bold\', \'\'),\n    (\'reveal focus\', \'black\', \'dark cyan\', \'standout\'),\n]\n\n\ndef show_or_exit(key):\n    if key in (\'q\', \'Q\', \'esc\'):\n        raise urwid.ExitMainLoop()\n\n\nHEADERS = ["Field 1", "Field 2", "Field 3", "Field 4"]\nENTRIES = [\n    ["a1", "a2", "a3", "a4"],\n    ["b1", "b2", "b3", "b4"],\n    ["c1", "c2", "c3", "c4"],\n    ["d1", "d2", "d3", "d4"],\n    ["e1", "e2", "e3", "e4"],\n    ["e1", "e2", "e3", "e4"],\n    ["f1", "f2", "f3", "f4"],\n    ["g1", "g2", "g3", "g4"],\n    ["h1", "h2", "h3", "h4"],\n]\n\n\nclass SelectableRow(urwid.WidgetWrap):\n    def __init__(self, contents, on_select=None):\n        self.on_select = on_select\n        self.contents = contents\n        self._columns = urwid.Columns([urwid.Text(c) for c in contents])\n        self._focusable_columns = urwid.AttrMap(self._columns, \'\', \'reveal focus\')\n        super(SelectableRow, self).__init__(self._focusable_columns)\n\n    def selectable(self):\n        return True\n\n    def update_contents(self, contents):\n        # update the list record inplace...\n        self.contents[:] = contents\n\n        # ... and update the displayed items\n        for t, (w, _) in zip(contents, self._columns.contents):\n            w.set_text(t)\n\n    def keypress(self, size, key):\n        if self.on_select and key in (\'enter\',):\n            self.on_select(self)\n        return key\n\n    def __repr__(self):\n        return \'%s(contents=%r)\' % (self.__class__.__name__, self.contents)\n\n\nclass CancelableEdit(urwid.Edit):\n    def __init__(self, *args, **kwargs):\n        self.on_cancel = kwargs.pop(\'on_cancel\', None)\n        super(CancelableEdit, self).__init__(*args, **kwargs)\n\n    def keypress(self, size, key):\n        if key == \'esc\':\n            self.on_cancel(self)\n        else:\n            return super(CancelableEdit, self).keypress(size, key)\n\n\ndef build_dialog(title, contents, background, on_save=None, on_cancel=None):\n    buttons = urwid.Columns([\n        urwid.Button(\'Save\', on_press=on_save),\n        urwid.Button(\'Cancel\', on_press=on_cancel),\n    ])\n    pile = urwid.Pile(\n        [urwid.Text(title), urwid.Divider(\'-\')]\n        + contents\n        + [urwid.Divider(\' \'), buttons]\n    )\n    return urwid.Overlay(\n        urwid.Filler(urwid.LineBox(pile)),\n        urwid.Filler(background),\n        \'center\',\n        (\'relative\', 80),\n        \'middle\',\n        (\'relative\', 80),\n    )\n\n\nclass App(object):\n    def __init__(self, entries):\n        self.entries = entries\n        self.header = urwid.Text(\'Welcome to the Master Detail Urwid Sample!\')\n        self.footer = urwid.Text(\'Status: ready\')\n\n        contents = [\n            SelectableRow(row, on_select=self.show_detail_view)\n            for row in entries\n        ]\n        listbox = urwid.ListBox(urwid.SimpleFocusListWalker(contents))\n\n        # TODO: cap to screen size\n        size = len(entries)\n\n        self.master_pile = urwid.Pile([\n            self.header,\n            urwid.Divider(u\'\xe2\x94\x80\'),\n            urwid.BoxAdapter(listbox, size),\n            urwid.Divider(u\'\xe2\x94\x80\'),\n            self.footer,\n        ])\n        self.widget = urwid.Filler(self.master_pile, \'top\')\n        self.loop = urwid.MainLoop(self.widget, PALETTE, unhandled_input=show_or_exit)\n\n    def show_detail_view(self, row):\n        self._edits = [\n            CancelableEdit(\'%s: \' % key, value, on_cancel=self.close_dialog)\n            for key, value in zip(HEADERS, row.contents)\n        ]\n        self.loop.widget = build_dialog(\n            title=\'Editing\',\n            contents=self._edits,\n            background=self.master_pile,\n            on_save=partial(self.save_and_close_dialog, row),\n            on_cancel=self.close_dialog,\n        )\n        self.show_status(\'Detail: %r\' % row)\n\n    def save_and_close_dialog(self, row, btn):\n        new_content = [e.edit_text for e in self._edits]\n\n        row.update_contents(new_content)\n\n        self.show_status(\'Updated\')\n        self.loop.widget = self.widget\n\n    def close_dialog(self, btn):\n        self.loop.widget = self.widget\n\n    def show_status(self, mesg):\n        self.footer.set_text(str(mesg))\n\n    def start(self):\n        self.loop.run()\n\n\nif __name__ == \'__main__\':\n    app = App(ENTRIES)\n    app.start()\n
Run Code Online (Sandbox Code Playgroud)\n\n

该类App保存应用程序的状态,跟踪主要小部件,并包含根据用户操作(例如单击保存/取消按钮)调用的方法。

\n\n

记录在 SelectableRow 小部件的方法中就地更新update_contents,这表示主列表中显示的记录。

\n\n

CancelableEdit小部件的存在只是为了能够对esc对话框窗口做出反应。

\n\n

请随意提出任何进一步澄清的问题,我尝试使用合适的名称并保持代码或多或少的可读性,但我知道这里还发生了很多事情,我不确定需要详细解释什么。

\n\n

这是一个有趣的练习,谢谢你给我这个借口!=)

\n