您如何结合多个TUI表单来编写更复杂的应用程序? [英] How do you combine multiple TUI forms to write more complex applications?

查看:145
本文介绍了您如何结合多个TUI表单来编写更复杂的应用程序?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想用基于 T ext的 U ser I 界面(




  • 第一种形式包含一个列表。每个列表元素代表一个按钮。

  • 如果按下相应的按钮,则会出现另一种形式,可以在其中输入列表项的数据。

  • 然后,第一个形式是再次显示(具有更新的列表条目)。



这是我的尝试,它使用库 npyscreen ,但不会返回第一种形式。该代码也不包含更改列表项的逻辑。

 #! / usr / bin / env python3 
#编码:utf8

import npyscreen

#content
标头= [第1列,第2列,第3栏,第4栏]
条目= [[ a1, a2, a3, a4],
[ b1, b2, b3, b4],
[ c1, c2, c3, c4],
[ d1, d2, d3, d4 ],
[ e1, e2, e3, e4]]


#返回一个字符串,其中各段用空格填充。
def format_entry(entry):
返回 {:10} | {:10} | {:10} | {:10}。format(entry [0],entry [1],entry [2],条目[3])


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

#添加第二种形式的小部件
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(无)

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

#添加主要形式的小部件
def create(self):
self.add(npyscreen.FixedText,value = format_entry(headers),editable = False,name = header)

for i,枚举项(条目):
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 =编辑行)

def onCleanExit(self):
npyscreen.notify_wait(再见!)

def change_form(自身,名称):
self.switchForm(名称)


如果__name__ == __main__:
tui = TestTUI()
tui.run()


解决方案

所以下面是我对这个问题的理解,可以描述为 master-detail的实现控制台



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



有许多可以改进的地方,包括使其看起来更漂亮。 :)



这里是代码:

 #!/ usr / bin / env python 
#-*-编码:utf-8-*-

该示例程序演示如何为主详细信息UI
实现小部件使用urwid库的记录列表(http://urwid.org)


来自__future__导入print_function,absolute_import,除法
来自functools导入部分
import urwid


PALETTE = [
('bold','bold',''),
('reveal focus','black', 'dark cyan','standout'),
]


def show_or_exit(key):
如果键入('q','Q',' esc'):
提高urwid.ExitMainLoop()


HEADERS = [字段1,字段2,字段3,字段4]
条目= [
[ a1, a2, a3, a4],
[ b1, b2, b3, b4],
[ c1, c2, c3, c4],
[ d1, d2, d3, d4],
[ e1, e2, e3, e4],
[ e1, e2, e3, e4],
[ f1, f2 , f3, f4],
[ g1, g2, g3, g4],
[ h1, h2, h3, h4],
]


类SelectableRow(urwid.WidgetWrap):
def __init __(self,contents,on_select = None):
self.on_select = on_select
self.contents =内容
self._columns = urwid.Columns([内容中c的urwid.Text(c)])
self._focusable_columns = urwid.AttrMap(self._columns,``,'reveal focus')
super(SelectableRow,self).__ init __(self._focusable_columns)

def selectable(self):
返回True

def update_contents(self,contents) :
#就地更新列表记录...
self.contents [:] =内容

#...并将显示的项目
更新为t, (w,_)in zip(contents,self._columns.contents):
w.set_text(t)

def keypress(self,size,key):
如果self.on_select并键入('enter',):
self.on_select(self)
返回键

def __repr __(self):
return'%s(contents =%r)'%(self .__ class __.__ name__,self.contents)


类CancelableEdit(urwid.Edit):
def __init __(self,* args,** kwargs):
self.on_cancel = kwargs.pop('on_cancel',无)
super(CancelableEdit,self).__ init __(* args,** kwargs)

def keypress(self,size,key):
if key =='esc ':
self.on_cancel(self)
其他:
return super(CancelableEdit,self).keypress(size,key)


def build_dialog (标题,内容,背景,on_save = None,on_cancel = None):
个按钮= urwid.Columns([
urwid.Button('Save',on_press = on_save),
urwid。 Button('Cancel',on_press = on_cancel),
])
堆= urwid.Pile(
[urwid.Text(title),urwid.Divider('-')]
+内容
+ [urwid.Divider(''),按钮]

返回urw id.Overlay(
urwid.Filler(urwid.LineBox(pile)),
urwid.Filler(background),
'center',
('relative',80) ,
'居中',
('相对',80),



类App(object):
def __init__ (自我,条目):
self.entries =条目
self.header = urwid.Text('欢迎来到主要细节Urwid示例!')
self.footer = urwid.Text( '状态:就绪')

的内容= [
SelectableRow(row,on_select = self.show_detail_view)
用于条目
中的行]
listbox = urwid.ListBox(urwid.SimpleFocusListWalker(contents))

#TODO:屏幕大小上限
size = len(entries)

self.master_pile = urwid。桩([
self.header,
urwid.Divider(u'─'),
urwid.BoxAdapter(listbox,size),
urwid.Divider(u'─' ),
se lf.footer,
])
self.widget = urwid.Filler(self.master_pile,'top')
self.loop = urwid.MainLoop(self.widget,PALETTE,unhandled_input = show_or_exit)

def show_detail_view(self,row):
self._edits = [
CancelableEdit('%s:'%key,value,on_cancel = self.close_dialog)
作为键,zip中的值(标题,row.contents)
]
self.loop.widget = build_dialog(
title ='Editing',
contents = self ._edits,
background = self.master_pile,
on_save = partial(self.save_and_close_dialog,row),
on_cancel = self.close_dialog,

self.show_status ('Detail:%r'%row)

def save_and_close_dialog(self,row,btn):
new_content = [e.self中的e.edit_text。_edits]

row.update_contents(new_content)

self.show_status('Updated')
self。 loop.widget = self.widget

def close_dialog(self,btn):
self.loop.widget = self.widget

def show_status(self,mesg ):
self.footer.set_text(str(mesg))

def start(self):
self.loop.run()


如果__name__ =='__main__':
app = App(ENTRIES)
app.start()

App 类保存应用程序的状态,跟踪主窗口小部件,并包含根据用户操作(如点击)调用的方法



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



CancelableEdit 小部件的存在只是为了能够对<对话框窗口中的kbd> esc 。



请随时提出任何进一步的澄清性问题,我尝试使用体面的名称并保持de或多或少具有可读性,但是我知道这里还有很多事情,我不确定需要详细解释什么。



这是一个有趣的运动,感谢您给我这样做的借口! =)


I would like to write a program with a Text-based User Interface (TUI) that consists of several forms.

  • The first form contains a "list". Each list element represents a button.
  • If the respective button is pressed, another form should appear in which one can enter the data for the list entry.
  • Then the first form is displayed again (with updated list entries).

Here is my attempt, which uses the library npyscreen but does not return to the first form. The code does also not contain the logic to change the list item.

#! /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()

解决方案

So what follows is my take to this problem, which can be described as an implementation of a master-detail user interface for the console.

This uses the urwid library, building some custom widgets to achieve the described UI, which has two modes: master view (where the main widget is a pile of records) and the detail view (an overlayed dialog, with the master view behind).

There are many things that can be improved, including making it look prettier. :)

Here is the code:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Sample program demonstrating how to implement widgets for a master-detail UI
for a list of records using the urwid library (http://urwid.org)
"""

from __future__ import print_function, absolute_import, division
from functools import partial
import urwid


PALETTE = [
    ('bold', 'bold', ''),
    ('reveal focus', 'black', 'dark cyan', 'standout'),
]


def show_or_exit(key):
    if key in ('q', 'Q', 'esc'):
        raise urwid.ExitMainLoop()


HEADERS = ["Field 1", "Field 2", "Field 3", "Field 4"]
ENTRIES = [
    ["a1", "a2", "a3", "a4"],
    ["b1", "b2", "b3", "b4"],
    ["c1", "c2", "c3", "c4"],
    ["d1", "d2", "d3", "d4"],
    ["e1", "e2", "e3", "e4"],
    ["e1", "e2", "e3", "e4"],
    ["f1", "f2", "f3", "f4"],
    ["g1", "g2", "g3", "g4"],
    ["h1", "h2", "h3", "h4"],
]


class SelectableRow(urwid.WidgetWrap):
    def __init__(self, contents, on_select=None):
        self.on_select = on_select
        self.contents = contents
        self._columns = urwid.Columns([urwid.Text(c) for c in contents])
        self._focusable_columns = urwid.AttrMap(self._columns, '', 'reveal focus')
        super(SelectableRow, self).__init__(self._focusable_columns)

    def selectable(self):
        return True

    def update_contents(self, contents):
        # update the list record inplace...
        self.contents[:] = contents

        # ... and update the displayed items
        for t, (w, _) in zip(contents, self._columns.contents):
            w.set_text(t)

    def keypress(self, size, key):
        if self.on_select and key in ('enter',):
            self.on_select(self)
        return key

    def __repr__(self):
        return '%s(contents=%r)' % (self.__class__.__name__, self.contents)


class CancelableEdit(urwid.Edit):
    def __init__(self, *args, **kwargs):
        self.on_cancel = kwargs.pop('on_cancel', None)
        super(CancelableEdit, self).__init__(*args, **kwargs)

    def keypress(self, size, key):
        if key == 'esc':
            self.on_cancel(self)
        else:
            return super(CancelableEdit, self).keypress(size, key)


def build_dialog(title, contents, background, on_save=None, on_cancel=None):
    buttons = urwid.Columns([
        urwid.Button('Save', on_press=on_save),
        urwid.Button('Cancel', on_press=on_cancel),
    ])
    pile = urwid.Pile(
        [urwid.Text(title), urwid.Divider('-')]
        + contents
        + [urwid.Divider(' '), buttons]
    )
    return urwid.Overlay(
        urwid.Filler(urwid.LineBox(pile)),
        urwid.Filler(background),
        'center',
        ('relative', 80),
        'middle',
        ('relative', 80),
    )


class App(object):
    def __init__(self, entries):
        self.entries = entries
        self.header = urwid.Text('Welcome to the Master Detail Urwid Sample!')
        self.footer = urwid.Text('Status: ready')

        contents = [
            SelectableRow(row, on_select=self.show_detail_view)
            for row in entries
        ]
        listbox = urwid.ListBox(urwid.SimpleFocusListWalker(contents))

        # TODO: cap to screen size
        size = len(entries)

        self.master_pile = urwid.Pile([
            self.header,
            urwid.Divider(u'─'),
            urwid.BoxAdapter(listbox, size),
            urwid.Divider(u'─'),
            self.footer,
        ])
        self.widget = urwid.Filler(self.master_pile, 'top')
        self.loop = urwid.MainLoop(self.widget, PALETTE, unhandled_input=show_or_exit)

    def show_detail_view(self, row):
        self._edits = [
            CancelableEdit('%s: ' % key, value, on_cancel=self.close_dialog)
            for key, value in zip(HEADERS, row.contents)
        ]
        self.loop.widget = build_dialog(
            title='Editing',
            contents=self._edits,
            background=self.master_pile,
            on_save=partial(self.save_and_close_dialog, row),
            on_cancel=self.close_dialog,
        )
        self.show_status('Detail: %r' % row)

    def save_and_close_dialog(self, row, btn):
        new_content = [e.edit_text for e in self._edits]

        row.update_contents(new_content)

        self.show_status('Updated')
        self.loop.widget = self.widget

    def close_dialog(self, btn):
        self.loop.widget = self.widget

    def show_status(self, mesg):
        self.footer.set_text(str(mesg))

    def start(self):
        self.loop.run()


if __name__ == '__main__':
    app = App(ENTRIES)
    app.start()

The App class holds the state of the app, keeping track of the main widgets and contains methods that are called upon user actions like hitting the buttons save/cancel.

The records are updated inplace, in the method update_contents of the SelectableRow widget, which represents a record being displayed in the master list.

The CancelableEdit widget exists just to be able to react to esc from the dialog window.

Feel free to ask any further clarifying question, I tried to use decent names and keep the code more or less readable, but I know that there is also a lot going on here and I'm not sure what needs to be explained in detail.

This was a fun exercise, thanks for giving me the excuse to do it! =)

这篇关于您如何结合多个TUI表单来编写更复杂的应用程序?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆