¿Cómo combina varios formularios TUI para escribir aplicaciones más complejas?

Me gustaría escribir un progtwig con una interfaz de usuario ( TUI ) basada en ext. Que consta de varias formas.

Presentación de las diversas formas.

  • El primer formulario contiene una “lista”. Cada elemento de la lista representa un botón.
  • Si se presiona el botón correspondiente, debe aparecer otro formulario en el que se pueden ingresar los datos para la entrada de la lista.
  • Luego, se vuelve a mostrar el primer formulario (con entradas de lista actualizadas).

Aquí está mi bash, que usa la biblioteca npyscreen pero no vuelve al primer formulario. El código tampoco contiene la lógica para cambiar el elemento de la lista.

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

    Entonces, lo que sigue es mi análisis de este problema, que se puede describir como una implementación de una interfaz de usuario con detalles maestros para la consola.

    Esto utiliza la biblioteca urwid , creando algunos widgets personalizados para lograr la IU descrita, que tiene dos modos: vista maestra (donde el widget principal es un montón de registros) y la vista detallada (un cuadro de diálogo superpuesto, con la vista maestra detrás).

    Hay muchas cosas que se pueden mejorar, incluso hacer que se vea más bonita. 🙂

    Aquí está el código:

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

    La clase de la App mantiene el estado de la aplicación, realiza un seguimiento de los widgets principales y contiene métodos que se utilizan en las acciones del usuario, como pulsar los botones guardar / cancelar.

    Los registros se actualizan in situ, en el método update_contents del widget update_contents , que representa un registro que se muestra en la lista maestra.

    El widget CancelableEdit existe solo para poder reactjsr a esc desde la ventana de diálogo.

    Siéntase libre de hacer cualquier otra pregunta aclaratoria, intenté usar nombres decentes y mantener el código más o menos legible, pero sé que también hay muchas cosas en juego aquí y no estoy seguro de lo que se necesita explicar en detalle.

    Este fue un ejercicio divertido, ¡gracias por darme la excusa para hacerlo! =)

    Me encontré usando Npyscreen y encontré tu pregunta. Si aún está trabajando en esta aplicación, aquí está su código inicial, pero esta vez volviendo al formulario principal:

     #! /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:") def afterEditing(self): self.parentApp.setNextForm("MAIN") 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()