Search code examples
pythonkivy

ScrollView child widgets not showing in Kivy


I'm new to programming and is designing a framework for dictionary apps that you can search and select words in the left part of the window, and can read the info of the selected word in the right part of the window. I was trying to use a TextInput and a Recycle on the left and a ScrollView on the right to achieve these objects, it was going well until I began to work on the right part: the right part, which is the ScrollView part, is always blank somehow.

The whole script is here:

from kivy.app import App
from kivy.lang import Builder
from kivy.properties import BooleanProperty
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout

kv_string = '''
<MainLayout>:
    orientation: 'horizontal'
    BoxLayout:
        orientation: 'vertical'
        size_hint_x: 0.35
        TextInput:
            hint_text: 'Search'
            size_hint_y: 0.05
            multiline: False
            padding_y: 10
        RV:
            size_hint_y: 0.95
            viewclass: 'SelectableLabel'
            SelectableRecycleBoxLayout:
                default_size: None, dp(56)
                default_size_hint: 1, None
                size_hint_y: None
                height: self.minimum_height
                orientation: 'vertical'
                multiselect: False
                touch_multiselect: False                              
<SelectableLabel>:
    color: (0, 0, 0, 1)
    canvas.before:
        Color:
            rgba: (.5, .7, .6, .5) if self.selected else (1, 1, 1, 1)
        Rectangle:
            pos: self.pos
            size: self.size        
'''

my_dict = {'A': {'info': 'a'}, 'B': {'info': 'b'}, 'C': {'info': 'c'}, 'D': {'info': 'd'}, 'E': {'info': 'e'}}

class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior, RecycleBoxLayout):
    pass

class SelectableLabel(RecycleDataViewBehavior, Label):
    index = None
    selected = BooleanProperty(False)
    selectable = BooleanProperty(True)
    def refresh_view_attrs(self, rv, index, data):
        self.index = index
        self.data = data
        self.text = data['letter']
        return super(SelectableLabel, self).refresh_view_attrs(rv, index, data)
    
    def on_touch_down(self, touch):
        if super(SelectableLabel, self).on_touch_down(touch):
            return True
        if self.collide_point(*touch.pos) and self.selectable:
            return self.parent.select_with_touch(self.index, touch)
        
    def apply_selection(self, rv, index, is_selected):
        self.selected = is_selected
        if is_selected:
            MyApp().display_info(self.data)

class RV(RecycleView):
    def __init__(self, **kwargs):
        super(RV, self).__init__(**kwargs)  
        for letter, info in my_dict.items():
            data_piece = {'letter': letter, 'info': info}
            self.data.append(data_piece)

class MainLayout(BoxLayout):
    pass

class MyApp(App):
    def __init__(self, **kwargs):
        super(MyApp, self).__init__(**kwargs)
        self.info_layout = GridLayout(
            size_hint = (1, None),
            cols = 1,
            height = 500
        )
    def build(self):   
        Builder.load_string(kv_string)
        root = MainLayout()
        info_sv = ScrollView(
            size_hint_x = 0.65
        )
        info_sv.add_widget(self.info_layout)
        root.add_widget(info_sv)
        return root
    
    def display_info(self, data):
        self.info_layout.clear_widgets()
        label = Label(
            text = data['info']['info'],
            color = (0, 0, 1, 1)
        )
        self.info_layout.add_widget(label)
        print(data)
        
if __name__ == '__main__':
    MyApp().run()

As you see I added a 'print(data)' in the displayinfo() method to test if it can receive data and turned out to work perfectly fine. And I tried to add a button in the info_layout and it's also not showing. So I suppose the problem is that info_layout didn't show up or not updated.


Solution

  • The problem is in your code for apply_selection(). In that code, you have a line:

    MyApp().display_info(self.data)
    

    The MyApp() portion of this creates a new instance of MyApp, and calls the display_info() method of that new instance. However, that new instance has nothing to do with what you see on your screen. You actually need to use the instance of MyApp that is displayed on your screen. Try changing that method to:

    def apply_selection(self, rv, index, is_selected):
        self.selected = is_selected
        if is_selected:
            App.get_running_app().display_info(self.data)
            # MyApp().display_info(self.data)