有没有办法向用户显示 urwid 列表框在显示部分上方/下方有其他项目?
我正在考虑类似滚动条的东西,它可以让您了解条目的数量。
或列表框顶部/底部的单独栏。
如果无法实现这种行为,有什么方法可以实现这种通知?
在我的研究中,我发现了这个问题,它试图最终达到同样的效果。给定的答案似乎检查所有元素是否可见。不幸的是,如果由于终端未调整大小而随时隐藏某些元素,这将失去其功能。
我想我已经找到了第二个可视化概念的实现(列表框顶部和底部的栏)。
#!/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:
我还编写了代码的变体,它基于原始规范:
#!/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
. (SelectableRow
取自用户 elias 的这个答案。)
这是相应的 TUI:
我已经实现了一个默认应用第二个可视化概念(顶部和底部的栏)的列表框。
它被调用additional_urwid_widgets.IndicativeListBox
并且可以通过pip安装。
有关说明小部件功能的独立示例,请参见此处。
有关更多(和更简单)的示例,请参见此处。
有关参数和选项的更详细说明,请参阅相应的 github wiki 条目。
#! /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()
#! /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()
在此示例中,必须另外按下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()