Search code examples
python-2.7kivykivy-language

kivy :How to work _on_keyboard_down event with scroll


I am using _on_keyboard_down event in Tree View.label to be selected with the up and down keys.I added scroll in Tree View List.Can someone tell me how to scroll with up and down when needed scroll?

.py

class TreeviewGroup(Popup):
    treeview = ObjectProperty(None)
    tv = ObjectProperty(None)
    h = NumericProperty(0)
    popup = ObjectProperty()

    def __init__(self, **kwargs):
        super(TreeviewGroup, self).__init__(**kwargs)
        self.tv = TreeView(root_options=dict(text=""),
                       hide_root=False,
                       indent_level=4)

        rows = [('test1'),('test2'),('test3'),('test4'),('test5'),('test6')]

        tree = []

        for r in rows:
            tree.append({'node_id': r, 'children': []})

        for branch in tree:
            populate_tree_view(self.tv, None, branch)
        #self.remove_widgets()
        self.treeview.add_widget(self.tv)
        Clock.schedule_once(self.update, 1)

        self.bind(on_open=self.on_open)

    def on_open(self, *args):
        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 = 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 'down' else ix-1
            next_node = parent.nodes[nx % len(parent.nodes)]
            self.tv.select_node(next_node)
            return True
        elif key == 'enter':
            App.get_running_app().root.name.text = node.text
            keyboard.release()
            self.dismiss()

    def remove_widgets(self):
        for child in [child for child in self.treeview.children]:
            self.treeview.remove_widget(child)

    def update(self, *args):
        self.h = len([child for child in self.tv.children]) * 24

.kv

<TreeviewGroup>:
    treeview: treeview
    title: "Select"
    title_size: 17
    title_font: "Verdana"
    size: 800, 800
    auto_dismiss: False

    BoxLayout
        orientation: "vertical"
        ScrollView:
            size_hint: 1, .9
            BoxLayout:
                size_hint_y: None
                id: treeview
                height: root.h

        GridLayout:
            cols : 2
            row_default_height: '20dp'
            size_hint: .5, 0.2
            pos_hint: {'x': .25, 'y': 1}

            Button:
                text: 'Ok'
                on_release: root.dismiss()

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

<TreeViewLabel>:
    text_size: self.size
    height: 30

Solution

  • You have to use the scroll_to() method and pass the node, you must also set the height of the BoxLayout(self.treeview) to the size of the content, in this case to the TreeView(self.tv).

    So you must remove the height: root.h in the .kv, and as you create the TreeView in the .py we will make the connection there, in addition to removing that update method that is not necessary and that interrupts the operation of the ScrollView. I do not understand why it is the value 24 in:

    self.h = len([child for child in self.tv.children]) * 24
    

    It would not be more appropriate 30 which is the height of the TreeViewLabel, in addition to using Clock for this type of tasks is not appropriate, the use of bind():

    self.tv.bind(minimum_height=self.treeview.setter('height'))
    

    is the solution.

    You must also change:

    nx = ix+1 if 'down' else ix-1
    

    to

    nx = ix+1 if key == 'down' else ix-1
    

    otherwise you can never climb.

    Making all those changes the solution is:

    *.py

    class TreeviewGroup(Popup):
        treeview = ObjectProperty(None)
        tv = ObjectProperty(None)
        h = NumericProperty(0)
        popup = ObjectProperty()
    
        def __init__(self, **kwargs):
            super(TreeviewGroup, self).__init__(**kwargs)
            self.tv = TreeView(root_options=dict(text=""),
                           hide_root=False,
                           indent_level=4)
    
            rows = [('test{}').format(i) for i in range(1, 20)]#[('test1'),('test2'),('test3'),('test4'),('test5'),('test6')]
    
            tree = [{'node_id': r, 'children': []} for r in rows]
    
            self.tv.bind(minimum_height=self.treeview.setter('height'))
            for branch in tree:
                populate_tree_view(self.tv, None, branch)
            #self.remove_widgets()
            self.treeview.add_widget(self.tv)
    
            self.bind(on_open=self.on_open)
    
        def on_open(self, *args):
            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 = 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)
                return True
            elif key == 'enter':
                App.get_running_app().root.name.text = node.text
                keyboard.release()
                self.dismiss()
    
        def remove_widgets(self):
            for child in [child for child in self.treeview.children]:
                self.treeview.remove_widget(child)
    

    *.kv

    <TreeviewGroup>:
        treeview: treeview
        title: "Select"
        title_size: 17
        size: 800, 800
        auto_dismiss: False
        scroll: scroll
    
        BoxLayout
            orientation: "vertical"
            ScrollView:
                id: scroll
                size_hint: 1, .9
    
                BoxLayout:
                    size_hint_y: None
                    id: treeview
    
            GridLayout:
                cols : 2
                row_default_height: '20dp'
                size_hint: .5, 0.2
                pos_hint: {'x': .25, 'y': 1}
    
                Button:
                    text: 'Ok'
                    on_release: root.dismiss()
    
                Button:
                    text: 'Cancel'
                    on_release: root.dismiss()
    

    Recommendation, use appropriate names, for example the name of the BoxLayout seems to indicate the name of the TreeView, and this causes confusion making its code less readable.