Search code examples
pythonbuttonkivyscrollviewautoscroll

Kivy Scrollview Autoscroll to new text. Prevent from scrolling up


I could really really need some help with my actually quite simple Python Kivy Problem! I have a Kivy scrollview and want to add some text by clicking a button. After every click the scrollview should automatically scroll to the new text line. But for some reason it only works every second time. The text switches between jumping to the top and jumping to the new text line. And when the text is at the bottom and you scroll manually (or just click into the scrollview field) then the text jumps to the top. I want to be able to scroll by hand, but the text should not jump without clicking "Add text". When "Add text" is clicked however then it should scroll to the new text line as described.

import kivy
from kivy.config import Config
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 ScreenManager, Screen
from kivy.core.window import Window
from kivy.uix.scrollview import ScrollView
from kivy.clock import mainthread
import threading

kivy.require("2.0.0")
Config.set('kivy', 'keyboard_mode', 'systemandmulti')

class MainMenu(GridLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.cols = 1
        self.rows = 3
        
        self.infowindow = ScrollableInfo(height=Window.size[1]*0.8, size_hint_y=None)
        self.add_widget(self.infowindow)

        self.addtextbutton = Button(text="Add Text")
        self.addtextbutton.bind(on_press=self.add_text_thread)
        self.add_widget(self.addtextbutton)

    def addtext(self, *_):
        self.infowindow.update_scrollview(f"This is some new Text")
     

    def add_text_thread(self, *args):
        threading.Thread(target=self.addtext, daemon=True).start()



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

        self.text_history = Label(size_hint_y=None, markup=True)

        self.layout.add_widget(self.text_history)

    @mainthread
    def update_scrollview(self, newinfo):
        self.text_history.text += '\n' + newinfo
        
        self.layout.height = self.text_history.texture_size[1]+15
        self.text_history.height = self.text_history.texture_size[1]
        self.text_history.text_size = (self.text_history.width*0.98, None)

        self.scroll_y = 0

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

        self.mainmenu_page = MainMenu()
        screen = Screen(name="MainMenu")
        screen.add_widget(self.mainmenu_page)
        self.screen_manager.add_widget(screen)

        return self.screen_manager

if __name__ == "__main__":
    counting_app = Textadding()
    counting_app.run()

Thank you very much in advance for any help!


Solution

  • If I understand you correctly, you want something like this. Your code has a lot of bugs and is very cumbersome, so I've rewritten it completely. I also do not recommend changing the UI in a secondary thread (add widgets, play animations) this is a bad practice and in kivy == 2.1.0dev0 it can cause an error.

    from kivy.app import App
    from kivy.lang import Builder
    
    kv = """
    Screen:
        BoxLayout:
            spacing: 10
            orientation: "vertical"
            
            ScrollView:
                id: scroll_view
                always_overscroll: False
                BoxLayout:
                    size_hint_y: None
                    height: self.minimum_height
                    orientation: 'vertical'
                    Label:
                        id: label
                        size_hint: None, None
                        size: self.texture_size 
    
            Button:
                text: "Add Text"
                size_hint_y: 0.2
                on_release: app.add_text()
            
    """
    
    
    class TextAdding(App):
        def __init__(self, **kwargs):
            super().__init__(**kwargs)
            self.text_counter = 0
    
        def build(self):
            return Builder.load_string(kv)
    
        def add_text(self):
            self.root.ids.label.text += f"Some text {self.text_counter}\n"
            self.text_counter += 1
            self.root.ids.scroll_view.scroll_y = 0
    
    
    if __name__ == "__main__":
        TextAdding().run()