Urwid:使光标不可见

7

我正在使用urwid,这是一个Python“框架”,用于在ncurses中设计终端用户界面。唯一的问题是,在urwid中有一件事我无法像在curses中那样轻松地做到 - 隐藏光标。目前,当选择按钮时,光标是可见的,看起来很丑陋。是否有方法可以禁用它?


urwid.SelectableIcon 的行为类似于按钮,但没有光标;这可能值得一看。 - anon01
4个回答

6
我同意在 urwid.Button 上闪烁的光标看起来有点简陋,所以我想出了一个方案来隐藏它。在 urwid 中,Button 类仅是包含一个 SelectableIcon 和两个 Text widgets(封装 "<" 和 ">" 的)的 WidgetWrap 子类。默认情况下,是 SelectableIcon 类将光标位置设置为标签的第一个字符。通过子类化 SelectableIcon,修改光标位置,然后将其封装到 urwid.WidgetWrap 子类中,您可以创建自己的自定义按钮,可以执行所有内置的 Button 或者更多功能。
这是在我的项目中的样子。 enter image description here
import urwid

class ButtonLabel(urwid.SelectableIcon):
    def __init__(self, text):
        """
        Here's the trick: 
        we move the cursor out to the right of the label/text, so it doesn't show
        """
        curs_pos = len(text) + 1 
        urwid.SelectableIcon.__init__(self, text, cursor_position=curs_pos)

接下来,您可以将ButtonLabel对象与其他对象一起包装在一个WidgetWrap子类中,这将成为您的自定义按钮类。
class FixedButton(urwid.WidgetWrap):
    _selectable = True
    signals = ["click"]
    def __init__(self, label):
        self.label = ButtonLabel(label)
        # you could combine the ButtonLabel object with other widgets here
        display_widget = self.label 
        urwid.WidgetWrap.__init__(self, urwid.AttrMap(display_widget, None, focus_map="button_reversed"))

    def keypress(self, size, key):
        """
        catch all the keys you want to handle here
        and emit the click signal along with any data 
        """
        pass

    def set_label(self, new_label):
        # we can set the label at run time, if necessary
        self.label.set_text(str(new_label))

    def mouse_event(self, size, event, button, col, row, focus):
        """
        handle any mouse events here
        and emit the click signal along with any data 
        """
        pass

在这段代码中,FixedButton WidgetWrap子类中并没有太多的小部件组合,但你可以在按钮的边缘添加"["和"]",将其包装成LineBox等。如果这些都是多余的,你可以将事件处理函数移动到ButtonLabel类中,并在点击时发出信号。
为了使用户在移动到按钮上时反转按钮,将其包装到AttrMap中,并将focus_map设置为某个调色板条目(在我的例子中是"button_reversed")。

嗨@imrek,我也需要一个解决方案。你的方法很好。我发现两个问题:1)你不需要真正的子类化SelectableIcon——你可以通过FixedButton内的self.label实例完成所有操作——检查长度并设置光标位置。2)如果“label”是标记而不是纯文本,则您的代码将无法工作。解决方案是使用urwid util函数decompose_tagmarkup()。FixedButton.__init()__中的完整代码如下: text, attrib = decompose_tagmarkup(label) cursor_pos = len(text) + 1 self._label = urwid.SelectableIcon("", cursor_pos) - scottmac991

3
基于Drunken Master的答案,我尽可能地清理了解决方案。 urwid.SelectableIcon 基本上是带有丑陋闪烁光标的 urwid.Text 字段。 因此,我们不必覆盖urwid.SelectableIcon 并将其打包到 urwid.WidgetWrap 中,而是直接使用 urwid.Text ,使其可以被选择并对按钮/鼠标激活做出反应(受启发于 Urwid的简单菜单教程):
import urwid

choices = u'Chapman Cleese Gilliam Idle Jones Palin'.split()

class ListEntry(urwid.Text):
    _selectable = True

    signals = ["click"]

    def keypress(self, size, key):
        """
        Send 'click' signal on 'activate' command.
        """
        if self._command_map[key] != urwid.ACTIVATE:
            return key

        self._emit('click')

    def mouse_event(self, size, event, button, x, y, focus):
        """
        Send 'click' signal on button 1 press.
        """
        if button != 1 or not urwid.util.is_mouse_press(event):
            return False

        self._emit('click')
        return True

def menu(title, choices):
    body = [urwid.Text(title), urwid.Divider()]
    for c in choices:
        button = ListEntry(c)
        urwid.connect_signal(button, 'click', item_chosen, c)
        body.append(urwid.AttrMap(button, None, focus_map='reversed'))
    return urwid.ListBox(urwid.SimpleFocusListWalker(body))

def item_chosen(button, choice):
    response = urwid.Text([u'You chose ', choice, u'\n'])
    done = ListEntry(u'Ok')
    urwid.connect_signal(done, 'click', exit_program)
    main.original_widget = urwid.Filler(urwid.Pile([response,
                                                    urwid.AttrMap(done, None, focus_map='reversed')]))

def exit_program(button):
    raise urwid.ExitMainLoop()

main = urwid.Padding(menu(u'Pythons', choices), left=2, right=2)
top = urwid.Overlay(main, urwid.SolidFill(u'\N{MEDIUM SHADE}'),
                    align='center', width=('relative', 60),
                    valign='middle', height=('relative', 60),
                    min_width=20, min_height=9)
urwid.MainLoop(top, palette=[('reversed', 'standout', '')]).run()

非常好用:

enter image description here


你有一个完整的工作示例吗?我不确定如何直接使用你的代码。如果你知道更好的解决方案,那就更好了。这个问题是隐藏光标(并显示焦点的漂亮反色)的顶级结果,因此需要一个完整的示例来帮助理解。 - Etzeitet
1
嗨@Etzeitet,看看我编辑过的答案吧。你可以简单地使用ListEntry类来替换urwid.Button类。 - orzechow
太棒了!我可以确认这个例子可以工作。我正在慢慢掌握urwid,并且这确实有所帮助。感谢更新! - Etzeitet

2
urwid使用curs_set函数,但没有将其作为类方法公开。有人可以修改urwid以允许使用此方法;否则,就没有可靠的方法来实现这一点。
你可以将其报告为问题

1
我明白了。我在那里发布了一个问题,希望urwid开发人员能够考虑这个问题。 - makos
仅暴露功能是不够的,因为urwid将会重新打开光标。makos已经向上游报告了这个问题:https://github.com/urwid/urwid/issues/170 - Mike Frysinger

1
沿着醉拳大师的回答路线,但是使用“微创手术”:
class ButtonLabel(urwid.SelectableIcon):
    '''
    use Drunken Master's trick to move the cursor out of view
    '''
    def set_text(self, label):
        '''
        set_text is invoked by Button.set_label
        '''
        self.__super.set_text(label)
        self._cursor_position = len(label) + 1


class MyButton(urwid.Button):
    '''
    - override __init__ to use our ButtonLabel instead of urwid.SelectableIcon

    - make button_left and button_right plain strings and variable width -
      any string, including an empty string, can be set and displayed

    - otherwise, we leave Button behaviour unchanged
    '''
    button_left = "["
    button_right = "]"

    def __init__(self, label, on_press=None, user_data=None):
        self._label = ButtonLabel("")
        cols = urwid.Columns([
            ('fixed', len(self.button_left), urwid.Text(self.button_left)),
            self._label,
            ('fixed', len(self.button_right), urwid.Text(self.button_right))],
            dividechars=1)
        super(urwid.Button, self).__init__(cols)

        if on_press:
            urwid.connect_signal(self, 'click', on_press, user_data)

        self.set_label(label)

在这里,我们只修改按钮的外观,但保持其行为不变。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接