Jacob
Jacob

Reputation: 65

Kivy scrollable Label in ScrollView, just Python code without KV

I'm trying to create simple GUI and one of my purposes is to create scrollable label that acts like kind of info-console, so new text/lines will be added dynamically all the time. Text starts showing from the left top and when it reaches the bottom of ScrollView, vertical scrolling should activate (and it happens because label height start to be larger than ScrollView height) and user should be able to scroll overflowed text to see newest or oldest lines. ScrollView should scroll automatically to the bottom as well. Like mentioned in the title I'm not using external KV file, however I'm willing to change this if there are any limitations from not doing this. Currently I achieved satisfying result, however I'm struggling with bombarding me with large amount of same error which sounds:

[CRITICAL] [Clock ] Warning, too much iteration done before the next frame. Check your code, or increase the Clock.max_iteration attribute. Remaining events: <ClockEvent (-1.0) callback=<bound method ScrollView.update_from_scroll of <kivy.uix.scrollview.ScrollView object at 0x000001E2C25689F0>>>

The GUI acts fine and functions it controls, but below worries me. Here you can see essentials from my code:

class MyLabel(Label):
   def on_size(self, *args):
       self.size = self.texture_size

class MyApp(App):
    def build(self):
        root_widget = BoxLayout(orientation='vertical')
        scroll_label = ScrollView(size_hint_y=None, size=(500, 200), do_scroll_y=True)
        output_label = MyLabel(size_hint_y=None, width=500, halign="left", valign="top", color=(0,50,0,1), padding=10)
        scroll_label.add_widget(output_label)
            
        def find_image(instance):
            try:
                plus_list = list(pyautogui.locateAllOnScreen('test.png', confidence=0.90, grayscale=False))

                if (len(plus_list) != 0):
                    for i in plus_list:
                        output_label.text += "Result: " + str(tuple(i)) + "\n"
                        #alternative function
                        if (output_label.texture_size[1] >= 200):
                            scroll_label.scroll_y = 0

            except BaseException as error:
                if "Could not locate the image" in format(error):
                    output_label.text += ("Error: Couldn't find the image\n")
                else:
                    output_label.text = "Error: No description\n"

        #(...)
        root_widget.add_widget(scroll_label)
        root_widget.add_widget(button_grid)
        root_widget.add_widget(paths_grid)

        return root_widget

if __name__ == '__main__':
    MyApp().run()

As you can see I found out about method to use on_size event inside class that inherits from Label. I tried to replace this event with normal function and invoke it right after adding new text (see #alternative function in code) but it doesn't work correctly, I suspect maybe eventloop is not able to pass on time with this update etc. What's really interesting is what I found in this video ScrollView kivy with TextInput. Creator uses KV file and sets height settings for label just there and everything seems to work fine without errors in Pycharm console. Does it work like kind of binding? The creator seems to use more functionalities like Builder that may have impact on this, I'm not sure. My questions is if it's possible to get rid of this error when using just Python without KV language. I use Python 3.12.2, Kivy 2.3.0, PyCharm 2023.3.3.

Upvotes: 0

Views: 47

Answers (2)

Jacob
Jacob

Reputation: 65

I solved it for now with these changes, as you can see I resigned from on_size event:

class MyLabel(Label):
   def __init__(self, **kwargs):
       super().__init__(**kwargs)
       Clock.schedule_interval(self.update_height, 0.1)

   def update_height(self, *args):
       self.height = self.texture_size[1]

And I also forgot before to set text_size width right after creating MyLabel object to align text to the left:

output_label.text_size=(500, None)

I hope no more issues will come..

Upvotes: 0

Tranbi
Tranbi

Reputation: 12701

Since you immediately call on_size again by setting self.size = self.texture_size, the method keeps calling itself. You could set a minimal call interval for this method to avoid wasting too much resource (see there for example):

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView
from time import time

class call_control:
    def __init__(self, max_call_interval):
        self._max_call_interval = max_call_interval
        self._last_call = time()
    def __call__(self, function):
        def wrapped(*args, **kwargs):
            now = time()
            if now - self._last_call > self._max_call_interval:
                self._last_call = now
                function(*args, **kwargs)
        return wrapped

class MyLabel(Label):
    @call_control(max_call_interval=0.1)
    def on_size(self, *args):
        self.size = self.texture_size

class MyApp(App):
    def build(self):
        root_widget = BoxLayout(orientation='vertical')
        scroll_label = ScrollView(size_hint_y=None, size=(500, 200), do_scroll_y=True)
        output_label = MyLabel(size_hint_y=None, width=500, halign="left", valign="top", color=(0,50,0,1), padding=10)
        scroll_label.add_widget(output_label)
            
        def find_image(instance):
            try:
                plus_list = list(pyautogui.locateAllOnScreen('test.png', confidence=0.90, grayscale=False))

                if (len(plus_list) != 0):
                    for i in plus_list:
                        output_label.text += "Result: " + str(tuple(i)) + "\n"
                        #alternative function
                        if (output_label.texture_size[1] >= 200):
                            scroll_label.scroll_y = 0

            except BaseException as error:
                if "Could not locate the image" in format(error):
                    output_label.text += ("Error: Couldn't find the image\n")
                else:
                    output_label.text = "Error: No description\n"

        #(...)
        root_widget.add_widget(scroll_label)

        return root_widget

if __name__ == '__main__':
    MyApp().run()

Alternativelvy, schedule the change with Clock:

class MyLabel(Label):
    def on_size(self, *args):
        Clock.schedule_once(lambda x:
            setattr(self, 'size', self.texture_size), 0.1)

Upvotes: 1

Related Questions