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 newest text line. But for some reason it only works every second time and with one single line. When there are multiple lines (see example) some lines disappear behind the scrollview's edge. The text switches between jumping to the top and jumping to the bottom edge. 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 bottom 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 \n and even more \n and more")
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=(1, None))
self.add_widget(self.layout)
self.text_history = Label(size_hint_y=None, markup=True)
self.scrolllabel = Label()
self.layout.add_widget(self.text_history)
self.layout.add_widget(self.scrolllabel)
@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 in advance!
You can do this by using more binding. First, bind the height of the GridLayout
to its own calculation of minimum_size
. Then bind its size
to a method that adjusts the scroll_y
property:
self.layout = GridLayout(cols=1, size_hint=(1, None))
self.layout.bind(minimum_height=self.layout.setter('height'))
self.layout.bind(size=self.on_size)
and the on_size()
method of ScrollableInfo
:
def on_size(self, *args):
self.scroll_y = 0
Now, you don't have to do the size calculations in your update_scrollview()
method, so it becomes just:
@mainthread
def update_scrollview(self, newinfo):
self.text_history.text += '\n' + newinfo