Python / Kivy:在kivy的树视图上的`up`和`down`键盘界面

时间:2018-04-11 06:32:58

标签: python-2.7 kivy kivy-language

我想在up上使用downTree View键盘界面。当我点击name时,Tree View弹出显示并updown关键工作正常。但是在使用TextInputrow键选择行之后,我在up中为过滤器down键入了一些内容,然后它给出了错误{{ 1}}。如何AttributeError: 'NoneType' object has no attribute 'parent_node'使用rowup键进行选择?

test.py

down

test.kv

from kivy.uix.screenmanager import Screen
from kivy.app import App
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.popup import Popup
from kivy.uix.treeview import TreeView, TreeViewLabel, TreeViewNode
from kivy.uix.label import Label
from kivy.properties import ObjectProperty, ListProperty, StringProperty
Window.size = (500, 400)


def populate_tree_view(tree_view, parent, node):
    if parent is None:
        tree_node = tree_view.add_node(TreeViewLabel(text=node['node_id'],
                                                     is_open=True))
    else:
        tree_node = tree_view.add_node(TreeViewLabel(text=node['node_id'],
                                                     is_open=True), parent)

    for child_node in node['children']:
        populate_tree_view(tree_view, tree_node, child_node)


class TreeViewLabel(Label, TreeViewNode):
    pass


class TreeViewGroup(Popup):
    tree_view = ObjectProperty(None)
    tv = ObjectProperty(None)
    filter_text = StringProperty('')
    tree = ListProperty([])

    def __init__(self, **kwargs):
        super(TreeViewGroup, self).__init__(**kwargs)
        self.create_tree_view_root()
        rows = ['test{}'.format(i) for i in range(1, 20)]
        self.tree = [{'node_id': r, 'children': []} for r in rows]
        self.tv.bind(minimum_height=self.tree_view.setter('height'))
        self.create_tree_view_branch(self.tree)

    def create_tree_view_root(self):
        self.tv = TreeView(root_options=dict(text=""),
                           hide_root=False,
                           indent_level=4)

    def create_tree_view_branch(self, obj):
        for branch in obj:
            populate_tree_view(self.tv, None, branch)
        self.tree_view.add_widget(self.tv)

    def on_open(self, *args):
        self.filter_text = App.get_running_app().root.name.text
        self._request_keyboard()
        self.ti.focus = True

    def dismiss_callback(self):
        if self._keyboard is not None:
            self._keyboard.release()
        self.tree_view.clear_widgets()
        self.dismiss()
        App.get_running_app().root.name.focus = True

    def _request_keyboard(self):
        self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
        self._keyboard.bind(on_key_down=self._on_keyboard_down)
        if self.tv.selected_node is None:
            self.tv.select_node(self.tv.root.nodes[0])

    def _keyboard_closed(self):
        self._keyboard.unbind(on_key_down=self._on_keyboard_down)
        self._keyboard.release()
        self._keyboard = None

    def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
        node = self.tv.selected_node
        _, key = keycode
        if key in ('down', 'up'):
            parent = node.parent_node
            ix = parent.nodes.index(node)
            nx = ix+1 if key == 'down' else ix-1
            next_node = parent.nodes[nx % len(parent.nodes)]
            self.tv.select_node(next_node)
            self.scroll.scroll_to(next_node)
        elif key in ('enter', 'numpadenter'):
            App.get_running_app().root.name.text = node.text
            print(node.text)
            self.dismiss_callback()

        # Keycode is composed of an integer + a string
        # If we hit escape, release the keyboard
        if keycode[1] == 'escape':
            keyboard.release()

        # Return True to accept the key. Otherwise, it will be used by
        # the system.
        return True

    def filter(self, value):
        self.tree_view.clear_widgets()
        self.create_tree_view_root()
        filtered_tree = []
        for node in self.tree:
            if value.lower() in node['node_id'].lower():
                filtered_tree.append(node)
        self.create_tree_view_branch(filtered_tree)
        self._request_keyboard()
        self.ti.focus = True


class GroupScreen(Screen):
    name = ObjectProperty(None)
    popup = ObjectProperty(None)

    def display_groups(self, instance):
        if len(instance.text) > 0:
            if self.popup is None:
                self.popup = TreeViewGroup()
            self.popup.open()


class Group(App):
    def build(self):
        self.root = Builder.load_file('test.kv')
        return self.root


if __name__ == '__main__':
    Group().run()

2 个答案:

答案 0 :(得分:1)

问题是您的keyboard正在关闭,因此当您点击TextInput时,系统会忽略键盘事件。当您点击keyboard Enter Select时,可以安排再次请求TextInput,方法是将其限制为单行并添加on_text_validate:项,在你的kv文件中这样:

    TextInput:
        id : ti
        size_hint_y: .13
        multiline: False
        on_text_validate: root.validate()
        on_text: root.filter(self.text)

在您的TreeViewGroup课程中,将验证方法添加为:

def validate(self):
    self.on_open()

这将调用您的on_open方法并再次设置键盘事件。

答案 1 :(得分:1)

选择一行 - 按ENTER或鼠标单击

使用向上或向下箭头滚动,然后按ENTER选择特定行。 注意:替代方案,鼠标单击以选择特定行。

将输入字符串从Name传递给Filter

通过整个单词,例如 test ,将 on_focus 替换为 on_text_validate

干 - 不要重复自己

有重复的代码,我已将它们转换为模块以便于维护。

def create_treeview_root(self):
...
def create_treeview_branch(self, obj):
...
def dismiss_callback(self):

片段

GroupScreen:
    ...
        SingleLineTextInput:
            id: name
            focus: True
            multiline: False
            on_text_validate: root.display_groups(self)

弹出事件on_open

删除 __ init __ 中的绑定,因为 on_open 会自动绑定,并在弹出Popup时触发。 Popup Events » API

class TreeviewGroup(Popup):
    ...

    def __init__(self,obj, **kwargs):
        super(TreeviewGroup, self).__init__(**kwargs)
        ...
        self.bind(on_open=self.on_open)

实施例

main.py

from kivy.uix.screenmanager import Screen
from kivy.app import App
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.popup import Popup
from kivy.uix.treeview import TreeView, TreeViewLabel, TreeViewNode
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.properties import ObjectProperty, ListProperty, StringProperty, NumericProperty
Window.size = (500, 400)


def populate_tree_view(tree_view, parent, node):
    if parent is None:
        tree_node = tree_view.add_node(TreeViewLabel(text=node['node_id'],
                                                     is_open=True))
    else:
        tree_node = tree_view.add_node(TreeViewLabel(text=node['node_id'],
                                                     is_open=True), parent)

    for child_node in node['children']:
        populate_tree_view(tree_view, tree_node, child_node)


class CustomTextInput(TextInput):

    def do_cursor_movement(self, action, control=False, alt=False):
        if not self._lines:
            return
        if action in ('cursor_up', 'cursor_down'):
            App.get_running_app().root.popup._request_keyboard()
        return super(CustomTextInput, self).do_cursor_movement(action, control=control, alt=alt)


class TreeViewLabel(Label, TreeViewNode):
    pass


class TreeViewGroup(Popup):
    tree_view = ObjectProperty(None)
    filter_text_input = ObjectProperty(None)
    tv = ObjectProperty(None)
    filter_text = StringProperty('')
    tree = ListProperty([])
    obj = ObjectProperty(None)

    keycodes = {
        # specials keys
        'backspace': 8, 'tab': 9, 'enter': 13, 'rshift': 303, 'shift': 304,
        'alt': 308, 'rctrl': 306, 'lctrl': 305,
        'super': 309, 'alt-gr': 307, 'compose': 311, 'pipe': 310,
        'capslock': 301, 'escape': 27, 'spacebar': 32, 'pageup': 280,
        'pagedown': 281, 'end': 279, 'home': 278, 'left': 276, 'up':
        273, 'right': 275, 'down': 274, 'insert': 277, 'delete': 127,
        'numlock': 300, 'print': 144, 'screenlock': 145, 'pause': 19,

        # F1-15
        'f1': 282, 'f2': 283, 'f3': 284, 'f4': 285, 'f5': 286, 'f6': 287,
        'f7': 288, 'f8': 289, 'f9': 290, 'f10': 291, 'f11': 292, 'f12': 293,
        'f13': 294, 'f14': 295, 'f15': 296,
    }

    def __init__(self, **kwargs):
        super(TreeViewGroup, self).__init__(**kwargs)
        self._keyboard = None

        self.create_tree_view_root()
        rows = ['test{}'.format(i) for i in range(1, 20)]
        self.tree = [{'node_id': r, 'children': []} for r in rows]
        self.tv.bind(minimum_height=self.tree_view.setter('height'))
        self.create_tree_view_branch(self.tree)

    def create_tree_view_root(self):
        self.tv = TreeView(root_options=dict(text=""),
                           hide_root=False,
                           indent_level=4)

    def create_tree_view_branch(self, obj):
        for branch in obj:
            populate_tree_view(self.tv, None, branch)
        self.tree_view.add_widget(self.tv)

    def on_open(self, *args):
        self.obj = self.filter_text_input
        self.filter_text_input.focus = True
        self.filter_text = App.get_running_app().root.name.text

    def dismiss_callback(self):
        if self._keyboard is not None:
            self._keyboard.release()
        self.tree_view.clear_widgets()
        self.dismiss()
        App.get_running_app().root.name.focus = True

    def _request_keyboard(self):
        self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
        self._keyboard.bind(on_key_down=self._on_keyboard_down)
        if (self.tv.selected_node is None) \
                and (len(self.tv.root.nodes) > 0):
            self.tv.select_node(self.tv.root.nodes[0])
        else:
            self.filter_text_input.focus = True

    def _keyboard_closed(self):
        self._keyboard.unbind(on_key_down=self._on_keyboard_down)
        self._keyboard.release()
        self._keyboard = None

    def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
        node = self.tv.selected_node
        _, key = keycode
        if key in ('down', 'up'):
            parent = node.parent_node
            ix = parent.nodes.index(node)
            nx = ix+1 if key == 'down' else ix-1
            next_node = parent.nodes[nx % len(parent.nodes)]
            self.tv.select_node(next_node)
            self.scroll.scroll_to(next_node)
        elif key in ('enter', 'numpadenter'):
            App.get_running_app().root.name.text = node.text
            print(node.text)
            self.dismiss_callback()

        # Keycode is composed of an integer + a string
        # If we hit escape, release the keyboard
        if keycode[1] == 'escape':
            keyboard.release()

        if self.string_to_keycode(key) == -1:
            self.filter_text += key
            self.obj.focus = True

        # Return True to accept the key. Otherwise, it will be used by
        # the system.
        return True

    def string_to_keycode(self, value):
        '''Convert a string to a keycode number according to the
        :attr:`TreeViewGroup.keycodes`. If the value is not found in the
        keycodes, it will return -1.
        '''
        return TreeViewGroup.keycodes.get(value, -1)

    def filter(self, value):
        self.tree_view.clear_widgets()
        self.create_tree_view_root()
        filtered_tree = []
        for node in self.tree:
            if value.lower() in node['node_id'].lower():
                filtered_tree.append(node)
        self.create_tree_view_branch(filtered_tree)


class GroupScreen(Screen):
    name = ObjectProperty(None)
    popup = ObjectProperty(None)

    def display_groups(self, instance):
        if len(instance.text) > 0:
            if self.popup is None:
                self.popup = TreeViewGroup()
            self.popup.open()


class Group(App):
    def build(self):
        self.root = Builder.load_file('test.kv')
        return self.root


if __name__ == '__main__':
    Group().run()

test.kv

#:kivy 1.10.0

<CustomTextInput>:
    size_hint_y: .13
    multiline: False

<TreeViewLabel>:
    color_selected: [1, 0, 0, 1]  if self.is_selected else [.1, .1, .1, 1]  # red
    on_touch_down:
        app.root.name.text = self.text
        app.root.popup.dismiss_callback()

<TreeviewGroup>:
    tree_view: tree_view
    filter_text_input: filter_text_input
    title: "Select"
    title_size: 17
    size: 800, 800
    auto_dismiss: False
    scroll: scroll

    BoxLayout
        orientation: "vertical"

        CustomTextInput:
            id: filter_text_input
            text: root.filter_text
            on_text:
                root.filter_text = self.text
                root.filter(self.text)

        ScrollView:
            id: scroll
            size_hint: 1, .9

            BoxLayout:
                size_hint_y: None
                id: tree_view

        GridLayout:
            cols: 2
            row_default_height: '20dp'
            size_hint: .5, 0.1
            pos_hint: {'x': .25, 'y': 1}
            Button:
                text: 'Ok'
                on_release:
                    root.dismiss_callback()

            Button:
                text: 'Cancel'
                on_release:
                    root.dismiss_callback()

<CustomLabel@Label>:
    text_size: self.size
    valign: "middle"
    padding_x: 5

<SingleLineTextInput@TextInput>:
    multiline: False

<GreenButton@Button>:
    background_color: 1, 1, 1, 1
    size_hint_y: None
    height: self.parent.height * 0.150

GroupScreen:
    name: name

    GridLayout:
        cols: 2
        padding : 30,30
        spacing: 10, 10
        row_default_height: '40dp'

        CustomLabel:
            text: ' '

        CustomLabel:
            text: ' '

        CustomLabel:
            text: 'Name'

        SingleLineTextInput:
            id: name
            focus: True
            multiline: False
            on_text: root.display_groups(self)

        GreenButton:
            text: 'Ok'

        GreenButton:
            text: 'Cancel'
            on_press: app.stop()

输出

Img01 - App Startup Img02 - Filter Popup Img03 - Scroll to bottom Img04 - Enter pressed Img05 - filter text Img06 - clicked OK button