Python: urwid: trying to handle different views

1k views Asked by At

I am trying to write a program with different views.

I tried to make a class which handles different views with urwid, also to separate the view code from the rest. But after a lot of different tries, i don't know where to start anymore.

Which urwid objects do I need for a clean erasing and redraw of the screen? And how do they need to be encapsulated so i can switch views after user input?

1

There are 1 answers

1
foundling On

From the Urwid documentation:

The topmost widget displayed by MainLoop must be passed as the first parameter to the constructor. If you want to change the topmost widget while running, you can assign a new widget to the MainLoop object’s MainLoop.widget attribute. This is useful for applications that have a number of different modes or views.

Now for some code:

import urwid

# This function handles input not handled by widgets.
# It's passed into the MainLoop constructor at the bottom.
def unhandled_input(key):
  if key in ('q','Q'):
    raise urwid.ExitMainLoop()
  if key == 'enter':
    try:

      ## This is the part you're probably asking about

      loop.widget = next(views).build()   
    except StopIteration:
      raise urwid.ExitMainLoop()

# A class that is used to create new views, which are 
# two text widgets, piled, and made into a box widget with
# urwid filler
class MainView(object):
  def __init__(self,title_text,body_text):
    self.title_text = title_text
    self.body_text = body_text

  def build(self):
    title = urwid.Text(self.title_text)
    body = urwid.Text(self.body_text)
    body = urwid.Pile([title,body])
    fill = urwid.Filler(body)
    return fill

# An iterator consisting of 3 instantiated MainView objects.
# When a user presses Enter, since that particular key sequence
# isn't handled by a widget, it gets passed into unhandled_input.
views = iter([ MainView(title_text='Page One',body_text='Lorem ipsum dolor sit amet...'),
          MainView(title_text='Page Two',body_text='consectetur adipiscing elit.'),
          MainView(title_text='Page Three',body_text='Etiam id hendrerit neque.')
        ])

initial_view = next(views).build()
loop = urwid.MainLoop(initial_view,unhandled_input=unhandled_input)
loop.run()

In short, I've used a global key handling function to listen for a certain sequence pressed by the user and on receiving that sequence, my key handling function builds a new view object with the MainView class and replaces loop.widget with that object. Of course, in an actual application, you're going to want to create a signal handler on a particular widget in your view class rather than use the global unhandled_input function for all user input. You can read about the connect_signal function here.

Note the part about garbage collection in Signal Functions documentation: if you're intending to write something with many views, they will remain in memory even after you've replaced them due to the fact that the signal_handler is a closure, which retains a reference to that widget implicitly, so you need to pass the weak_args named argument into the urwid.connect_signal function to tell Urwid to let it go once its not actively being used in the event loop.