由於語法渲染問題而影響閱讀體驗, 請移步博客閱讀~
本文GitPage地址

urwid

Reference:http://urwid.org/tutorial/index.html

Urwid Tutorial
Minimal Application
Global Input
Display Attributes
High Color Modes
Question and Answer
Signal Handlers
Multiple Questions
Simple Menu
Cascading Menu
Horizontal Menu
Adventure Game

Main tutorial: http://urwid.org/manual/index.html

Quick Start:

  1. import urwid
  2. txt = urwid.Text(u"Hello World")
  3. fill = urwid.Filler(txt, 'top') #'top', 'middle', 'bottom'
  4. loop = urwid.MainLoop(fill)
  5. loop.run()

urwid | python TUI 庫的介紹和簡單上手 - 图1

Global Input

  1. import urwid
  2. def show_or_exit(key):
  3. if key in ('q', 'Q'):
  4. raise urwid.ExitMainLoop()
  5. txt.set_text(repr(key))
  6. txt = urwid.Text(u"Hello World")
  7. fill = urwid.Filler(txt, 'top')
  8. loop = urwid.MainLoop(fill, unhandled_input=show_or_exit)
  9. loop.run()

urwid | python TUI 庫的介紹和簡單上手 - 图2

Display Attributes

  1. import urwid
  2. def exit_on_q(key):
  3. if key in ('q', 'Q'):
  4. raise urwid.ExitMainLoop()
  5. palette = [
  6. ('banner', 'black', 'light gray'),
  7. ('streak', 'black', 'dark red'),
  8. ('bg', 'black', 'dark blue'),]
  9. txt = urwid.Text(('banner', u" Hello World "), align='center')
  10. map1 = urwid.AttrMap(txt, 'streak')
  11. fill = urwid.Filler(map1)
  12. map2 = urwid.AttrMap(fill, 'bg')
  13. loop = urwid.MainLoop(map2, palette, unhandled_input=exit_on_q)
  14. loop.run()

urwid | python TUI 庫的介紹和簡單上手 - 图3
原图
urwid | python TUI 庫的介紹和簡單上手 - 图4
去掉‘bg’
urwid | python TUI 庫的介紹和簡單上手 - 图5
去掉‘streak’

解释:

txt, 最顶层,标签是‘banner’,获取palette中‘banner’的设置
map1, 和txt 一个道理。
txt 丢到map1, map1丢到 fill, fill丢到map2, 一层盖一层。

Question and Answer

  1. import urwid
  2. def exit_on_q(key):
  3. if key in ('q', 'Q'):
  4. raise urwid.ExitMainLoop()
  5. class QuestionBox(urwid.Filler):
  6. def keypress(self, size, key):
  7. if key != 'enter':
  8. return super(QuestionBox, self).keypress(size, key)
  9. self.original_widget = urwid.Text(
  10. u"Nice to meet you,\n%s.\n\nPress Q to exit." %
  11. edit.edit_text)
  12. edit = urwid.Edit(u"What is your name?\n")
  13. fill = QuestionBox(edit)
  14. loop = urwid.MainLoop(fill, unhandled_input=exit_on_q)
  15. loop.run()

urwid | python TUI 庫的介紹和簡單上手 - 图6

Signal Handlers

  1. import urwid
  2. palette = [('I say', 'default,bold', 'default', 'bold'),]
  3. ask = urwid.Edit(('I say', u"What is your name?\n"))
  4. reply = urwid.Text(u"")
  5. button = urwid.Button(u'Exit')
  6. div = urwid.Divider()
  7. pile = urwid.Pile([ask, div, reply, div, button])
  8. top = urwid.Filler(pile, valign='top')
  9. def on_ask_change(edit, new_edit_text):
  10. reply.set_text(('I say', u"Nice to meet you, %s" % new_edit_text))
  11. def on_exit_clicked(button):
  12. raise urwid.ExitMainLoop()
  13. urwid.connect_signal(ask, 'change', on_ask_change)
  14. urwid.connect_signal(button, 'click', on_exit_clicked)
  15. urwid.MainLoop(top, palette).run()

urwid | python TUI 庫的介紹和簡單上手 - 图7

Simple Menu

  1. import urwid
  2. choices = u'Chapman Cleese Gilliam Idle Jones Palin'.split()
  3. def menu(title, choices):
  4. body = [urwid.Text(title), urwid.Divider()]
  5. for c in choices:
  6. button = urwid.Button(c)
  7. urwid.connect_signal(button, 'click', item_chosen, c)
  8. body.append(urwid.AttrMap(button, None, focus_map='reversed'))
  9. return urwid.ListBox(urwid.SimpleFocusListWalker(body))
  10. def item_chosen(button, choice):
  11. response = urwid.Text([u'You chose ', choice, u'\n'])
  12. done = urwid.Button(u'Ok')
  13. urwid.connect_signal(done, 'click', exit_program)
  14. main.original_widget = urwid.Filler(urwid.Pile([response,
  15. urwid.AttrMap(done, None, focus_map='reversed')]))
  16. def exit_program(button):
  17. raise urwid.ExitMainLoop()
  18. main = urwid.Padding(menu(u'Pythons', choices), left=2, right=2)
  19. top = urwid.Overlay(main, urwid.SolidFill(u'\N{MEDIUM SHADE}'),
  20. align='center', width=('relative', 60),
  21. valign='middle', height=('relative', 60),
  22. min_width=20, min_height=9)
  23. urwid.MainLoop(top, palette=[('reversed', 'standout', '')]).run()

urwid | python TUI 庫的介紹和簡單上手 - 图8

Pop-up Menu

  1. import urwid
  2. def menu_button(caption, callback):
  3. button = urwid.Button(caption)
  4. urwid.connect_signal(button, 'click', callback)
  5. return urwid.AttrMap(button, None, focus_map='reversed')
  6. def sub_menu(caption, choices):
  7. contents = menu(caption, choices)
  8. def open_menu(button):
  9. return top.open_box(contents)
  10. return menu_button([caption, u'...'], open_menu)
  11. def menu(title, choices):
  12. body = [urwid.Text(title), urwid.Divider()]
  13. body.extend(choices)
  14. return urwid.ListBox(urwid.SimpleFocusListWalker(body))
  15. def item_chosen(button):
  16. response = urwid.Text([u'You chose ', button.label, u'\n'])
  17. done = menu_button(u'Ok', exit_program)
  18. top.open_box(urwid.Filler(urwid.Pile([response, done])))
  19. def exit_program(button):
  20. raise urwid.ExitMainLoop()
  21. menu_top = menu(u'Main Menu', [
  22. sub_menu(u'Applications', [
  23. sub_menu(u'Accessories', [
  24. menu_button(u'Text Editor', item_chosen),
  25. menu_button(u'Terminal', item_chosen),
  26. ]),
  27. ]),
  28. sub_menu(u'System', [
  29. sub_menu(u'Preferences', [
  30. menu_button(u'Appearance', item_chosen),
  31. ]),
  32. menu_button(u'Lock Screen', item_chosen),
  33. ]),
  34. ])
  35. class CascadingBoxes(urwid.WidgetPlaceholder):
  36. max_box_levels = 4
  37. #
  38. def __init__(self, box):
  39. super(CascadingBoxes, self).__init__(urwid.SolidFill(u'/'))
  40. self.box_level = 0
  41. self.open_box(box)
  42. #
  43. def open_box(self, box):
  44. self.original_widget = urwid.Overlay(urwid.LineBox(box),
  45. self.original_widget,
  46. align='center', width=('relative', 80),
  47. valign='middle', height=('relative', 80),
  48. min_width=24, min_height=8,
  49. left=self.box_level * 3,
  50. right=(self.max_box_levels - self.box_level - 1) * 3,
  51. top=self.box_level * 2,
  52. bottom=(self.max_box_levels - self.box_level - 1) * 2)
  53. self.box_level += 1
  54. #
  55. def keypress(self, size, key):
  56. if key == 'esc' and self.box_level > 1:
  57. self.original_widget = self.original_widget[0]
  58. self.box_level -= 1
  59. else:
  60. return super(CascadingBoxes, self).keypress(size, key)
  61. top = CascadingBoxes(menu_top)
  62. urwid.MainLoop(top, palette=[('reversed', 'standout', '')]).run()

urwid | python TUI 庫的介紹和簡單上手 - 图9

Horizontal Menu

  1. import urwid
  2. class MenuButton(urwid.Button):
  3. def __init__(self, caption, callback):
  4. super(MenuButton, self).__init__("")
  5. urwid.connect_signal(self, 'click', callback)
  6. self._w = urwid.AttrMap(urwid.SelectableIcon(
  7. [u' \N{BULLET} ', caption], 2), None, 'selected')
  8. class SubMenu(urwid.WidgetWrap):
  9. def __init__(self, caption, choices):
  10. super(SubMenu, self).__init__(MenuButton(
  11. [caption, u"\N{HORIZONTAL ELLIPSIS}"], self.open_menu))
  12. line = urwid.Divider(u'\N{LOWER ONE QUARTER BLOCK}')
  13. listbox = urwid.ListBox(urwid.SimpleFocusListWalker([
  14. urwid.AttrMap(urwid.Text([u"\n ", caption]), 'heading'),
  15. urwid.AttrMap(line, 'line'),
  16. urwid.Divider()] + choices + [urwid.Divider()]))
  17. self.menu = urwid.AttrMap(listbox, 'options')
  18. #
  19. def open_menu(self, button):
  20. top.open_box(self.menu)
  21. class Choice(urwid.WidgetWrap):
  22. def __init__(self, caption):
  23. super(Choice, self).__init__(
  24. MenuButton(caption, self.item_chosen))
  25. self.caption = caption
  26. #
  27. def item_chosen(self, button):
  28. response = urwid.Text([u' You chose ', self.caption, u'\n'])
  29. done = MenuButton(u'Ok', exit_program)
  30. response_box = urwid.Filler(urwid.Pile([response, done]))
  31. top.open_box(urwid.AttrMap(response_box, 'options'))
  32. def exit_program(key):
  33. raise urwid.ExitMainLoop()
  34. menu_top = SubMenu(u'Main Menu', [
  35. SubMenu(u'Applications', [
  36. SubMenu(u'Accessories', [
  37. Choice(u'Text Editor'),
  38. Choice(u'Terminal'),
  39. ]),
  40. ]),
  41. SubMenu(u'System', [
  42. SubMenu(u'Preferences', [
  43. Choice(u'Appearance'),
  44. ]),
  45. Choice(u'Lock Screen'),
  46. ]),
  47. ])
  48. palette = [
  49. (None, 'light gray', 'black'),
  50. ('heading', 'black', 'light gray'),
  51. ('line', 'black', 'light gray'),
  52. ('options', 'dark gray', 'black'),
  53. ('focus heading', 'white', 'dark red'),
  54. ('focus line', 'black', 'dark red'),
  55. ('focus options', 'black', 'light gray'),
  56. ('selected', 'white', 'dark blue')]
  57. focus_map = {
  58. 'heading': 'focus heading',
  59. 'options': 'focus options',
  60. 'line': 'focus line'}
  61. class HorizontalBoxes(urwid.Columns):
  62. def __init__(self):
  63. super(HorizontalBoxes, self).__init__([], dividechars=1)
  64. #
  65. def open_box(self, box):
  66. if self.contents:
  67. del self.contents[self.focus_position + 1:]
  68. self.contents.append((urwid.AttrMap(box, 'options', focus_map),
  69. self.options('given', 24)))
  70. self.focus_position = len(self.contents) - 1
  71. top = HorizontalBoxes()
  72. top.open_box(menu_top.menu)
  73. urwid.MainLoop(urwid.Filler(top, 'middle', 10), palette).run()

urwid | python TUI 庫的介紹和簡單上手 - 图10

Frames

urwid.Text

urwid | python TUI 庫的介紹和簡單上手 - 图11
不能:
{Column, AttrMap} —> Mainloop

可:
{Filler, Frame } -> Mainloop

Column -> Filler -> Mainloop
Text -> Filler -> Mainloop
Column -> Filler -> AttrMap -> Mainloop
Text -> Filler -> AttrMap -> Mainloop

Frame

Frame(header=hdr, body=map2)
Text -> AttrWrap > header
Columns -> Filler -> AttrMap -> body

ListBox

Filler -> ListBox
Padding -> SimpleListWalker -> ListBox
[Text, Pile, Columns] -> SimpleListWalker -> ListBox
[Text, Pile, Columns]-> SimpleListWalker -> ListBox -> AttrWrap -> Frame

Exampels

Source: https://github.com/urwid/urwid/tree/master/examples

urwid | python TUI 庫的介紹和簡單上手 - 图12
browse.py

urwid | python TUI 庫的介紹和簡單上手 - 图13
bigtext.py

urwid | python TUI 庫的介紹和簡單上手 - 图14
treesample.py

urwid | python TUI 庫的介紹和簡單上手 - 图15
calc.py

urwid | python TUI 庫的介紹和簡單上手 - 图16
tour.py

urwid | python TUI 庫的介紹和簡單上手 - 图17
edit.py

urwid | python TUI 庫的介紹和簡單上手 - 图18
fib.py

urwid | python TUI 庫的介紹和簡單上手 - 图19
graph.py

urwid | python TUI 庫的介紹和簡單上手 - 图20
terminal.py

urwid | python TUI 庫的介紹和簡單上手 - 图21
input_test.py

urwid | python TUI 庫的介紹和簡單上手 - 图22
pop_up.py

urwid | python TUI 庫的介紹和簡單上手 - 图23
palette_test.py

more examples: programcreek.com

pop_up.py

P-raw

  1. ##!/usr/bin/env python
  2. import urwid
  3. class PopUpDialog(urwid.WidgetWrap):
  4. """A dialog that appears with nothing but a close button """
  5. signals = ['close']
  6. def __init__(self):
  7. close_button = urwid.Button("that's pretty cool")
  8. urwid.connect_signal(close_button, 'click',
  9. lambda button:self._emit("close"))
  10. pile = urwid.Pile([urwid.Text(
  11. "^^ I'm attached to the widget that opened me. "
  12. "Try resizing the window!\n"), close_button])
  13. fill = urwid.Filler(pile)
  14. self.__super.__init__(urwid.AttrWrap(fill, 'popbg'))
  15. class ThingWithAPopUp(urwid.PopUpLauncher):
  16. def __init__(self):
  17. self.__super.__init__(urwid.Button("click-me"))
  18. urwid.connect_signal(self.original_widget, 'click',
  19. lambda button: self.open_pop_up())
  20. def create_pop_up(self):
  21. pop_up = PopUpDialog()
  22. urwid.connect_signal(pop_up, 'close',
  23. lambda button: self.close_pop_up())
  24. return pop_up
  25. def get_pop_up_parameters(self):
  26. return {'left':0, 'top':1, 'overlay_width':32, 'overlay_height':7}
  27. fill = urwid.Filler(urwid.Padding(ThingWithAPopUp(), 'center', 15))
  28. loop = urwid.MainLoop(
  29. fill,
  30. [('popbg', 'white', 'dark blue')],
  31. pop_ups=True)
  32. loop.run()

原来程序:
urwid | python TUI 庫的介紹和簡單上手 - 图24
在 pop up 的框里面加一个退出按钮
结构解析:

  1. close_button = urwid.Button("that's pretty cool") #定义一个button 名称
  2. urwid.connect_signal(close_button, 'click', # button 的作用
  3. lambda button:self._emit("close"))
  4. pile = urwid.Pile([urwid.Text( #一段文字,加上前面的button
  5. "^^ I'm attached to the widget that opened me. " #pile 在一起
  6. "Try resizing the window!\n"), close_button])
  7. fill = urwid.Filler(pile) #封装
  8. self.__super.__init__(urwid.AttrWrap(fill, 'popbg')) #完成

可以看出基本结构为, Button -> Pile + Button -> Filler
因此, 除了加入button title和 signal 外, 还需要在 pile处, 一起打包.
测试:

  1. esc_button = urwid.Button("Esc")
  2. urwid.connect_signal(esc_button, 'click',
  3. lambda button:self._emit("close"))
  4. pile = urwid.Pile([urwid.Text(
  5. "^^ I'm attached to the widget that opened me. "
  6. "Try resizing the window!\n"), close_button,esc_button])

直接加的话, 按键会依次排列:

urwid | python TUI 庫的介紹和簡單上手 - 图25

Button 位置等属性

用 urwid.Padding(close_button, ‘left’, 18) 参数 调整button 位置, 结果一个上左, 一个右下
注: 18 为字符数

urwid | python TUI 庫的介紹和簡單上手 - 图26

这里用到一个新函数 ,参考来源:stackoverflow

urwid.Columns

基本用法: urwid.Columns([button1, button2])

urwid | python TUI 庫的介紹和簡單上手 - 图27

  1. def on_exit_clicked(button):
  2. raise urwid.ExitMainLoop()
  3. ## button, 加在上文 p-raw 的 9~11行之间
  4. button = urwid.Button(u'Exit')
  5. urwid.connect_signal(button, 'click', on_exit_clicked)
  6. ## button的排序, 加载urwid.Pile() 里面
  7. urwid.Columns([
  8. urwid.Padding(close_button, 'left'),
  9. urwid.Padding(button, 'right', 8),
  10. ]),

完整代码:
pop-2

  1. ##!/usr/bin/env python
  2. import urwid
  3. def on_exit_clicked(button):
  4. raise urwid.ExitMainLoop()
  5. class PopUpDialog(urwid.WidgetWrap):
  6. """A dialog that appears with nothing but a close button """
  7. signals = ['close']
  8. def __init__(self):
  9. close_button = urwid.Button("that's NOT COOL")
  10. urwid.connect_signal(close_button, 'click',
  11. lambda button:self._emit("close"))
  12. button = urwid.Button(u'Exit')
  13. urwid.connect_signal(button, 'click', on_exit_clicked)
  14. pile = urwid.Pile([urwid.Text(
  15. "^^ I'm attached to the widget that opened me. "
  16. "Try resizing the window!\n"),
  17. urwid.Columns([
  18. urwid.Padding(close_button, 'left'),
  19. urwid.Padding(button, 'right', 8),
  20. ]),
  21. ])
  22. fill = urwid.Filler(pile)
  23. self.__super.__init__(urwid.AttrWrap(fill, 'popbg'))
  24. class ThingWithAPopUp(urwid.PopUpLauncher):
  25. def __init__(self):
  26. self.__super.__init__(urwid.Button("click-me"))
  27. urwid.connect_signal(self.original_widget, 'click',
  28. lambda button: self.open_pop_up())
  29. def create_pop_up(self):
  30. pop_up = PopUpDialog()
  31. urwid.connect_signal(pop_up, 'close',
  32. lambda button: self.close_pop_up())
  33. return pop_up
  34. def get_pop_up_parameters(self):
  35. return {'left':0, 'top':1, 'overlay_width':32, 'overlay_height':7}
  36. button = urwid.Button(u'Exit')
  37. urwid.connect_signal(button, 'click', on_exit_clicked)
  38. pile = urwid.Pile([urwid.Padding(ThingWithAPopUp(), 'center', 15),button])
  39. fill = urwid.Filler(pile)
  40. loop = urwid.MainLoop(
  41. fill,
  42. [('popbg', 'white', 'dark blue')],
  43. pop_ups=True)
  44. loop.run()

pop window size

修改这里就好了~

  1. def get_pop_up_parameters(self):
  2. return {'left':0, 'top':1, 'overlay_width':32, 'overlay_height':7}

urwid | python TUI 庫的介紹和簡單上手 - 图28

魔改

左右各一个list:
将pile 放入column中

urwid | python TUI 庫的介紹和簡單上手 - 图29

把pop-2 的 51,52行改成

  1. pile = urwid.Pile(body_A)
  2. col = urwid.Columns([pile,urwid.Text("│"),('fixed',14,pile)])
  3. fill = urwid.Filler(col)

bigtext.py

非常靓的一个
urwid | python TUI 庫的介紹和簡單上手 - 图30

排版区

  1. # Create chars_avail
  2. cah = urwid.Text("Characters Available:")
  3. self.chars_avail = urwid.Text("", wrap='any')
  4. ca = urwid.AttrWrap(self.chars_avail, 'chars')
  5. chosen_font_rb.set_state(True) # causes set_font_event call
  6. # Create Edit widget
  7. edit = self.create_edit("", "Urwid "+urwid.__version__,
  8. self.edit_change_event)
  9. # ListBox
  10. chars = urwid.Pile([cah, ca])
  11. fonts = urwid.Pile([urwid.Text("Fonts:")] + self.font_buttons,
  12. focus_item=1)
  13. col = urwid.Columns([fonts,('fixed',16,chars)], 3,
  14. focus_column=1)
  15. bt = urwid.Pile([bt, edit], focus_item=1)
  16. l = [bt, urwid.Divider(), col]
  17. w = urwid.ListBox(urwid.SimpleListWalker(l))
  18. # Frame
  19. w = urwid.AttrWrap(w, 'body')
  20. hdr = urwid.Text("Urwid BigText example program - F8 exits.")
  21. hdr = urwid.AttrWrap(hdr, 'header')
  22. w = urwid.Frame(header=hdr, body=w)

Frame 排版结构
urwid | python TUI 庫的介紹和簡單上手 - 图31
继续魔改. 首先发现一个bug, 虽然说按F8 退出, 但是按完以后, 只出来一个 pop window, 写着quit, 然后,,, 就没有然后了. 所以我们先来加一个Esc 按钮吧~ (以后再说)

Refresh

Eample

  1. import urwid
  2. import time
  3. import sys
  4. '''
  5. https://github.com/bavanduong/urwid-example/blob/master/clock.py
  6. '''
  7. class Clock:
  8. def keypress(self, key):
  9. if key in ('q', 'Q'):
  10. raise urwid.ExitMainLoop()
  11. def setup_view(self):
  12. self.clock_txt = urwid.BigText(
  13. time.strftime('%H:%M:%S'), urwid.font.HalfBlock5x4Font())
  14. self.view = urwid.Padding(self.clock_txt, 'center', width='clip')
  15. self.view = urwid.AttrMap(self.view, 'body')
  16. self.view = urwid.Filler(self.view, 'middle')
  17. def main(self):
  18. self.setup_view()
  19. loop = urwid.MainLoop(
  20. self.view, palette=[('body', 'dark cyan', '')],
  21. unhandled_input=self.keypress)
  22. loop.set_alarm_in(1, self.refresh)
  23. loop.run()
  24. def refresh(self, loop=None, data=None):
  25. self.setup_view()
  26. loop.widget = self.view
  27. loop.set_alarm_in(1, self.refresh)
  28. if __name__ == '__main__':
  29. clock = Clock()
  30. sys.exit(clock.main())

urwid | python TUI 庫的介紹和簡單上手 - 图32

實戰

Todolist

urwid | python TUI 庫的介紹和簡單上手 - 图33

Bilibili 信息板

urwid | python TUI 庫的介紹和簡單上手 - 图34


Enjoy~

本文由Python腳本GitHub/語雀自動更新

由於語法渲染問題而影響閱讀體驗, 請移步博客閱讀~
本文GitPage地址

GitHub: Karobben
Blog:Karobben
BiliBili:史上最不正經的生物狗