Search code examples
pythonscrollkivyscrollviewscrollable

Why I have to click twice to scroll? Scrollable Label in kivy, python


I have a following problem: I need to write an app, where I will show proper answers for every question. I wrote with kivy some code and I'm struggling with one thing. I created a page. There is a button for showing answers, but after one press I only see a part of my answers and I can't scroll. But, when I press a button second time, everything is good. Could you tell me why is that? How to repair it? I would like to see all answers after pressing a button once and be able to scroll.

import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.scrollview import ScrollView
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
from kivy.config import Config
Config.set('graphics', 'resizable', True)
import os
import sys

class MyApp(App):
    def build(self):
        self.screen_manager = ScreenManager()

        self.answers = Answers()
        screen = Screen(name = "Answers")
        screen.add_widget(self.answers)
        self.screen_manager.add_widget(screen)

        return self.screen_manager

class Answers(GridLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.rows = 3

        self.label = Label(text = "Answers: ", font_size = 40)
        self.add_widget(self.label)

        self.button = Button(text="Show answers")
        self.button.bind(on_press=self.showanswers)
        self.add_widget(self.button)

        self.scroll = ScrollableLabel(height = Window.size[1]*0.75, size_hint_y = None)
        self.add_widget(self.scroll)


    def showanswers(self, instance):
        f = open("text.txt", "r")
        lines = f.readlines()

        ScrollableLabel.update(self.scroll, lines)
        myapp.screen_manager.current = "Answers"


class ScrollableLabel(ScrollView):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.layout = GridLayout(cols = 1,  size_hint_y = None)
        self.add_widget(self.layout)

        self.lines = Label(size_hint_x = 1, size_hint_y = None, text_size = (self.width, None))

        self.scroll_to_point = Label()
        self.scroll_to_point.bind(texture_size=self.scroll_to_point.setter('size'))

        self.layout.add_widget(self.lines)
        self.layout.add_widget(self.scroll_to_point)


    def update(self, lines):
        self.lines.text = '\n'

        for i in range(len(lines)):
            self.lines.text += '\n ' +str(i+1) + ". " + lines[i]

        self.layout.height = self.lines.texture_size[1]
        self.lines.height = self.lines.texture_size[1]
        self.lines.text_size = (self.lines.width*0.75, None)

        self.scroll_to(self.scroll_to_point)

f = open("text.txt", 'a+')
for i in range(30):
    f.write("Important text \n")
f.close()

myapp = MyApp()
myapp.run()


Solution

  • I think your widget heights are not updating correctly, and to correct that requires doing some binding. Since the 'kv' language automatically does bindings, I have provided an answer that uses 'kv':

    from kivy.app import App
    from kivy.lang import Builder
    from kivy.uix.label import Label
    from kivy.uix.gridlayout import GridLayout
    from kivy.uix.button import Button
    from kivy.uix.screenmanager import Screen, ScreenManager
    from kivy.uix.scrollview import ScrollView
    from kivy.core.window import Window
    from kivy.config import Config
    Config.set('graphics', 'resizable', True)
    
    class MyApp(App):
        def build(self):
            self.screen_manager = ScreenManager()
    
            self.answers = Answers()
            screen = Screen(name = "Answers")
            screen.add_widget(self.answers)
            self.screen_manager.add_widget(screen)
    
            return self.screen_manager
    
    class Answers(GridLayout):
        def __init__(self, **kwargs):
            super().__init__(**kwargs)
    
            self.rows = 3
    
            self.label = Label(text = "Answers: ", font_size = 40)
            self.add_widget(self.label)
    
            self.button = Button(text="Show answers")
            self.button.bind(on_press=self.showanswers)
            self.add_widget(self.button)
    
            self.scroll = ScrollableLabel(height = Window.size[1]*0.75, size_hint_y = None)
            self.add_widget(self.scroll)
    
    
        def showanswers(self, instance):
            f = open("text.txt", "r")
            lines = f.readlines()
    
            self.scroll.update(lines)
            myapp.screen_manager.current = "Answers"
    
    
    class ScrollableLabel(ScrollView):
    
        def update(self, lines):
            self.ids.lines.text = '\n'
            for i in range(len(lines)):
                self.ids.lines.text += '\n ' +str(i+1) + ". " + lines[i]
    
    Builder.load_string('''
    <ScrollableLabel>:
        size_hint_y: None
        GridLayout:
            cols: 1
            size_hint_y: None
            height: self.minimum_height  # adjust height to handle the Label
            Label:
                id: lines
                size_hint_y: None
                height: self.texture_size[1]  # set height based on text
    ''')
    
    f = open("text.txt", 'a+')
    for i in range(30):
        f.write("Important text \n")
    f.close()
    
    myapp = MyApp()
    myapp.run()
    

    I believe the two instances of size_hint_y: None and the corresponding height rules are the key.

    Note that I also changed:

    ScrollableLabel.update(self.scroll, lines)
    

    to:

    self.scroll.update(lines)