代码之家  ›  专栏  ›  技术社区  ›  AFoeee

如何指示urwid列表框中的项目多于当前显示的项目?

  •  1
  • AFoeee  · 技术社区  · 6 年前

    有没有办法向用户显示urwid列表框在dispalyd部分的上方/下方有其他项?

    我在想一个类似滚动条的东西,它给出了条目数的概念。

    listbox with vertical scrollbar.

    或列表框顶部/底部的单独栏。

    transformation of listbox while scrolling down.

    如果无法实现此行为,有哪些方法可以实现此通知?

    this question ,最终尝试实现同样的目标。 给出的答案似乎检查了所有元素是否可见。不幸的是,如果某些元素因为终端没有调整大小而在任何时候被隐藏,那么它就失去了它的功能。

    2 回复  |  直到 6 年前
        1
  •  1
  •   AFoeee    6 年前

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    import urwid
    
    ENTRIES = [letter for letter in "abcdefghijklmnopqrstuvwxyz"]
    
    PALETTE = [
        ("notifier_active",   "dark cyan",  "light gray"),
        ("notifier_inactive", "black", "dark gray"),
        ("reveal_focus",      "black",      "dark cyan", "standout")
    ]
    
    
    class MyListBox(urwid.ListBox):
        def __init__(self, body, on_focus_change=None):
            super().__init__(body)
    
            self.on_focus_change = on_focus_change
    
        # Overriden
        def change_focus(self, size, position, offset_inset=0, coming_from=None, cursor_coords=None, snap_rows=None):
            super().change_focus(size,
                                 position,
                                 offset_inset,
                                 coming_from,
                                 cursor_coords,
                                 snap_rows)
    
            # Implement a hook to be able to deposit additional logic
            if self.on_focus_change != None:
                self.on_focus_change(size,
                                     position,
                                     offset_inset,
                                     coming_from,
                                     cursor_coords,
                                     snap_rows)
    
    
    class App(object):
        def __init__(self, entries):
            # Get terminal dimensions
            terminal_cols, terminal_rows = urwid.raw_display.Screen().get_cols_rows()
            list_rows = (terminal_rows - 2) if (terminal_rows > 7) else 5       
            # (available_rows - notifier_rows) OR my preferred minimum size
    
            # At the beginning, "top" is always visible
            self.notifier_top = urwid.AttrMap(urwid.Text('^', align="center"),
                                              "notifier_inactive")
    
            # Determine presentation depending on size and number of elements
            self.notifier_bottom = urwid.AttrMap(urwid.Text('v', align="center"),
                                                 "notifier_inactive" if (len(entries) <= list_rows) else "notifier_active")
    
            contents = [urwid.AttrMap(urwid.Button(entry), "", "reveal_focus")
                        for entry in entries]
    
            self.listbox = MyListBox(urwid.SimpleFocusListWalker(contents),
                                     self.update_notifiers)                   # Pass the hook
    
            master_pile = urwid.Pile([
                self.notifier_top,
                urwid.BoxAdapter(self.listbox, list_rows),
                self.notifier_bottom,
            ])
    
            widget = urwid.Filler(master_pile,
                                  'top')
    
            self.loop = urwid.MainLoop(widget,
                                       PALETTE,
                                       unhandled_input=self.handle_input)
    
        # Implementation for hook
        def update_notifiers(self, size, position, offset_inset, coming_from, cursor_coords, snap_rows):
            # which ends are visible? returns "top", "bottom", both or neither.
            result = self.listbox.ends_visible(size)
    
            if ("top" in result) and ("bottom" in result):
                self.notifier_top.set_attr_map({None:"notifier_inactive"})
                self.notifier_bottom.set_attr_map({None:"notifier_inactive"})
            elif "top" in result:
                self.notifier_top.set_attr_map({None:"notifier_inactive"})
                self.notifier_bottom.set_attr_map({None:"notifier_active"})
            elif "bottom" in result:
                self.notifier_top.set_attr_map({None:"notifier_active"})
                self.notifier_bottom.set_attr_map({None:"notifier_inactive"})
            else:
                self.notifier_top.set_attr_map({None:"notifier_active"})
                self.notifier_bottom.set_attr_map({None:"notifier_active"})
    
        def handle_input(self, key):
            if key in ('q', 'Q', 'esc'):
                self.exit()
    
        def start(self):
            self.loop.run()
    
        def exit(self):
            raise urwid.ExitMainLoop()
    
    
    if __name__ == '__main__':
        app = App(ENTRIES)
        app.start()
    

    基本上,我创建了 urwid.Listbox change_focus() 方法添加挂钩。显然,当焦点改变时,会在内部调用此方法。

    ends_visible() 方法,该方法返回列表框的当前可见端(顶部、底部、两者或两者都不)。基于此,我修改了两个 urwid.Text 元素。

    代码生成以下TUI:

    listbox while scrolling down.



    我还编写了一个基于原始规范的代码变体:

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    import urwid
    
    HEADERS = ["column 1",
               "column 2",
               "column 3",
               "column 4"]
    
    ENTRIES = [["{}1".format(letter),
                "{}2".format(letter),
                "{}3".format(letter),
                "{}4".format(letter)] for letter in "abcdefghijklmnopqrstuvwxyz"]
    
    PALETTE = [
        ("column_headers", "white, bold", ""),
        ("notifier_active",   "dark cyan",  "light gray"),
        ("notifier_inactive", "black", "dark gray"),
        ("reveal_focus",      "black",      "dark cyan", "standout")
    ]
    
    
    class SelectableRow(urwid.WidgetWrap):
        def __init__(self, contents, on_select=None):
            self.contents = contents
            self.on_select = on_select
    
            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 MyListBox(urwid.ListBox):
        def __init__(self, body, on_focus_change=None):
            super().__init__(body)
    
            self.on_focus_change = on_focus_change
    
        # Overriden
        def change_focus(self, size, position, offset_inset=0, coming_from=None, cursor_coords=None, snap_rows=None):
            super().change_focus(size,
                                 position,
                                 offset_inset,
                                 coming_from,
                                 cursor_coords,
                                 snap_rows)
    
            # Implement a hook to be able to deposit additional logic
            if self.on_focus_change != None:
                self.on_focus_change(size,
                                     position,
                                     offset_inset,
                                     coming_from,
                                     cursor_coords,
                                     snap_rows)
    
    
    class App(object):
        def __init__(self, entries):
            # Get terminal dimensions
            terminal_cols, terminal_rows = urwid.raw_display.Screen().get_cols_rows()
            list_rows = (terminal_rows - 6) if (terminal_rows > 11) else 5       
            # (available_rows - divider_rows - column_headers_row - notifier_rows) OR my preferred minimum size
    
            column_headers = urwid.AttrMap(urwid.Columns([urwid.Text(c) for c in HEADERS]),
                                           "column_headers")
    
            # At the beginning, "top" is always visible
            self.notifier_top = urwid.AttrMap(urwid.Text('^', align="center"),
                                              "notifier_inactive")
    
            # Determine presentation depending on size and number of elements
            self.notifier_bottom = urwid.AttrMap(urwid.Text('v', align="center"),
                                                 "notifier_inactive" if (len(entries) <= list_rows) else "notifier_active")
    
            contents = [SelectableRow(entry) for entry in entries]
    
            self.listbox = MyListBox(urwid.SimpleFocusListWalker(contents),
                                     self.update_notifiers)                    # Pass the hook
    
            master_pile = urwid.Pile([
                urwid.Divider(u'─'),
                column_headers,
                urwid.Divider(u'─'),
                self.notifier_top,
                urwid.BoxAdapter(self.listbox, list_rows),
                self.notifier_bottom,
                urwid.Divider(u'─'),
            ])
    
            widget = urwid.Filler(master_pile,
                                  'top')
    
            self.loop = urwid.MainLoop(widget,
                                       PALETTE,
                                       unhandled_input=self.handle_input)
    
        # Implementation for hook
        def update_notifiers(self, size, position, offset_inset, coming_from, cursor_coords, snap_rows):
            # which ends are visible? returns "top", "bottom", both or neither.
            result = self.listbox.ends_visible(size)
    
            if ("top" in result) and ("bottom" in result):
                self.notifier_top.set_attr_map({None:"notifier_inactive"})
                self.notifier_bottom.set_attr_map({None:"notifier_inactive"})
            elif "top" in result:
                self.notifier_top.set_attr_map({None:"notifier_inactive"})
                self.notifier_bottom.set_attr_map({None:"notifier_active"})
            elif "bottom" in result:
                self.notifier_top.set_attr_map({None:"notifier_active"})
                self.notifier_bottom.set_attr_map({None:"notifier_inactive"})
            else:
                self.notifier_top.set_attr_map({None:"notifier_active"})
                self.notifier_bottom.set_attr_map({None:"notifier_active"})
    
        def handle_input(self, key):
            if key in ('q', 'Q', 'esc'):
                self.exit()
    
        def start(self):
            self.loop.run()
    
        def exit(self):
            raise urwid.ExitMainLoop()
    
    
    if __name__ == '__main__':
        app = App(ENTRIES)
        app.start()
    

    唯一真正的区别是,我使用 SelectableRow urwid.Button 可选择行 this answer of user elias .)

    以下是相应的TUI:

    list box while scrolling down (more complex list content)

        2
  •  1
  •   AFoeee    6 年前

    它被称为 additional_urwid_widgets.IndicativeListBox 可通过安装 pip .

    Demonstration of list box


    为了一个 ,它演示了小部件的功能,请参见 here .

    示例 ,请参见 here .

    详细说明 有关参数和选项的详细信息,请参见 the corresponding github wiki entry .



    一些例子

    最小

    #! /usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    from additional_urwid_widgets import IndicativeListBox    # installed via pip
    import urwid                                              # installed via pip
    
    # Color schemes that specify the appearance off focus and on focus.
    PALETTE = [("reveal_focus", "black", "light cyan", "standout")]
    
    # The list box is filled with buttons.
    body = [urwid.Button(letter) for letter in "abcdefghijklmnopqrstuvwxyz"]
    
    # Wrap the list items into an 'urwid.AttrMap', so that they have an other appearance when focused.
    # Instead of an simple list-like object you can/should create a 'urwid.ListWalker'.
    attr_body = [urwid.AttrMap(entry, None, "reveal_focus") for entry in body]
    
    ilb = IndicativeListBox(attr_body)
    
    loop = urwid.MainLoop(ilb,
                          PALETTE)
    loop.run()
    

    visual output of example 'Minimal'


    显示上/下项目

    #! /usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    from additional_urwid_widgets import IndicativeListBox    # installed via pip
    import urwid                                              # installed via pip
    
    # Color schemes that specify the appearance off focus and on focus.
    PALETTE = [("reveal_focus", "black", "light cyan", "standout")]
    
    # The list box is filled with buttons.
    body = [urwid.Button(letter) for letter in "abcdefghijklmnopqrstuvwxyz"]
    
    # Wrap the list items into an 'urwid.AttrMap', so that they have an other appearance when focused.
    # Instead of an simple list-like object you can/should create a 'urwid.ListWalker'.
    attr_body = [urwid.AttrMap(entry, None, "reveal_focus") for entry in body]
    
    ilb = IndicativeListBox(attr_body,
                            topBar_endCovered_prop=("{} above ...", None, None),
                            bottomBar_endCovered_prop=("{} below ...", None, None))
    
    loop = urwid.MainLoop(ilb,
                          PALETTE)
    loop.run()
    

    Visual output of example 'Display items above/below'


    与其他窗口小部件(也有样式)的上下文

    在这个例子中, ctrl键 必须另外按下,以便列表框响应输入。
    urwid.Pile ).

    #! /usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    from additional_urwid_widgets import IndicativeListBox, MODIFIER_KEY    # installed via pip
    import urwid                                                            # installed via pip
    
    # Color schemes that specify the appearance off focus and on focus.
    PALETTE = [("reveal_focus",              "black",            "light cyan",   "standout"),
               ("ilb_barActive_focus",       "dark cyan",        "light gray"),
               ("ilb_barActive_offFocus",    "light gray",       "dark gray"),
               ("ilb_barInactive_focus",     "light cyan",       "dark gray"),
               ("ilb_barInactive_offFocus",  "black",            "dark gray"),
               ("ilb_highlight_offFocus",    "black",            "dark cyan")]
    
    # The list box is filled with buttons.
    body = [urwid.Button(letter) for letter in "abcdefghijklmnopqrstuvwxyz"]
    
    # Wrap the list items into an 'urwid.AttrMap', so that they have an other appearance when focused.
    # Instead of an simple list-like object you can/should create a 'urwid.ListWalker'.
    attr_body = [urwid.AttrMap(entry, None, "reveal_focus") for entry in body]
    
    ilb = ilb = IndicativeListBox(attr_body,
                                  modifier_key=MODIFIER_KEY.CTRL,
                                  return_unused_navigation_input=False,
                                  topBar_endCovered_prop=("ᐃ", "ilb_barActive_focus", "ilb_barActive_offFocus"),
                                  topBar_endExposed_prop=("───", "ilb_barInactive_focus", "ilb_barInactive_offFocus"), 
                                  bottomBar_endCovered_prop=("ᐁ", "ilb_barActive_focus", "ilb_barActive_offFocus"), 
                                  bottomBar_endExposed_prop=("───", "ilb_barInactive_focus", "ilb_barInactive_offFocus"),
                                  highlight_offFocus="ilb_highlight_offFocus")
    
    pile = urwid.Pile([urwid.Text("The listbox responds only if 'ctrl' is pressed."),
                       urwid.Divider(" "),
                       urwid.Button("a button"),
                       urwid.BoxAdapter(ilb, 6),         # Wrap flow widget in box adapter
                       urwid.Button("another button")])
    
    
    loop = urwid.MainLoop(urwid.Filler(pile, "top"),
                          PALETTE)
    loop.run()
    

    Visual output of example 'In contex with other widgets (also styled)'