How can I implement a scrolling label in Kivy without using Builder (or a .kv file)? - kivy

I am trying to implement a scrolling label in a Kivy program, and found this example (slightly modified) that works:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
from kivy.properties import StringProperty
from kivy.lang import Builder
long_text = "".join(["this is a long line "+str(n)+"\n" for n in range(1,101)])
Builder.load_string('''
<ScrollableLabel>:
Label:
size_hint_y: None
height: self.texture_size[1]
text_size: self.width, None
text: root.text
''')
class ScrollableLabel(ScrollView):
text = StringProperty('')
class ScrollApp(App):
def build(self):
return ScrollableLabel(text=long_text)
if __name__ == "__main__":
ScrollApp().run()
Partly for my own education, I am trying to convert this sample to not use Builder (and not resort to a .kv file). I have modified the above example to:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
from kivy.properties import StringProperty
long_text = "".join(["this is a long line "+str(n)+"\n" for n in range(1,101)])
class ScrollableLabel(ScrollView):
text = StringProperty('')
def __init__(self, **kwargs):
super(ScrollableLabel, self).__init__(**kwargs)
self.label = Label(size_hint_y=None, text=self.text)
self.label.height = self.label.texture_size[1]
self.label.text_size = (self.label.width, None)
self.add_widget(self.label)
class ScrollApp(App):
def build(self):
return ScrollableLabel(text=long_text)
if __name__ == "__main__":
ScrollApp().run()
To my obviously untutored eye, these programs look like they should be equivalent. However, my (second) version doesn't work correctly (on several fronts).
So my question is two-fold: why doesn't the second version work the same as the first, and (if the answer isn't obvious from the first), how can I make it do so?
Thanks! -David

Try this:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
from kivy.properties import StringProperty
from kivy.clock import Clock
long_text = "".join(["this is a long line "+str(n)+"\n" for n in range(1,101)])
class ScrollableLabel(ScrollView):
text = StringProperty('')
def __init__(self, **kwargs):
super(ScrollableLabel, self).__init__(**kwargs)
self.label = Label(size_hint_y=None, text=self.text)
self.add_widget(self.label)
Clock.schedule_once(self.update, 1)
def update(self, *args):
self.label.text_size = (self.label.width, None)
self.label.height = self.label.texture_size[1]
class ScrollApp(App):
def build(self):
return ScrollableLabel(text=long_text)
if __name__ == "__main__":
ScrollApp().run()
the output now is the same as your first

Related

I want write codes for counting numbers app. My app can count the numbers of pages. I want to add button for cleaning screen

from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
class TestScreen(Screen):
def __init__(self, **kwargs):
Screen.__init__(self, **kwargs)
layout = BoxLayout(orientation="vertical")
self.add_widget(layout)
layout.add_widget(Label(text=self.name, font_size="150sp"))
button = Button(text="Count",font_size='30sp')
layout.add_widget(button)
button.bind(on_press=self.add_screen)
def add_screen(self, *args):
n = len(self.manager.screen_names)
screen = TestScreen(name="{}".format(n))
self.manager.add_widget(screen)
self.manager.current = screen.name
# Create the screen manager
sm = ScreenManager()
sm.add_widget(TestScreen(name=''))
class TestApp(App):
def build(self):
return sm
if __name__ == '__main__':
TestApp().run()
I can't add button with a view to cleaning screen. I know this app not for counting. But i only need add button for cleaning numbers.

Kivy property observer objects left behind after ModalView is dismissed

I display in a popup (ModalView) a dynamically changing value. I use a method in my main widget class to open/dismiss the popup, and bind a Kivy StringProperty to a Label in the popup. There is a problem - each time the popup is dismissed, something is left behind. Listing all observers of the StringProperty shows how with each cycle of open/dismiss the number of objects accumulates. See the example code below. When I run this on Raspberry Pi 2 under Raspbian Jessie (Pixel) with 128M allocated for VRAM, within about a minute the progam stops functioning correctly - popup starts to show a black screen. Am I doing something silly in my code?
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.modalview import ModalView
from kivy.clock import Clock
from kivy.properties import StringProperty
from random import randint
Builder.load_string('''
#:kivy 1.9.2
<MainWidget>:
BoxLayout:
Button:
''')
class MainWidget(BoxLayout):
value_str = StringProperty()
def show_popup(self, even=True):
if even:
popup = ModalView(size_hint=(None, None), auto_dismiss=False, size=(700,480))
popup_label = Label(font_size = 200, text_size=self.size, halign='center', valign='center')
self.bind(value_str=popup_label.setter('text')) # value_str must be a Kivy StringProperty
popup.add_widget(popup_label)
self.value_str = str(randint(0,100))
popup.open()
else: # find all instances of ModalView and dismiss them
for widget in App.get_running_app().root_window.children:
if isinstance(widget, ModalView):
print "observers of value_str property:"
observers = self.get_property_observers('value_str')
for observer in observers:
print observer
widget.dismiss(force=True, animation=False)
Clock.schedule_once(lambda dt: self.show_popup(not even), 0.25)
class MyApp(App):
def build(self):
mw=MainWidget()
Clock.schedule_once(lambda dt: mw.show_popup(),0)
return mw
if __name__ == '__main__':
MyApp().run()
I found a workaround, inspired by this How to unbind a property automatically binded in Kivy language?
I now preserve the Label child by removing it from the ModalView and adding it to the MainWidget before ModalView is dismissed, then reversing this for the next popup. This way the property binding takes place only once so no new observers are created. The label can be made invisible by assigning an empty string to the bound property.
I think this may be a bug - ModalView dismiss() method should not leave behind observers, but cannot test with latest Kivy version (1.10.1.dev0).
Here's the code:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.modalview import ModalView
from kivy.clock import Clock
from kivy.properties import StringProperty
from random import randint
Builder.load_string('''
#:kivy 1.9.2
<MyLabel>:
font_size: 100
text_size: self.size
halign: 'center'
valign: 'center'
<MainWidget>:
Button:
background_color: 0.5, 0.5, 1, 1
''')
class MyLabel(Label):
pass
class MainWidget(FloatLayout):
value_str = StringProperty()
popup_label = MyLabel()
def __init__(self, **kwargs):
super(MainWidget, self).__init__(**kwargs)
self.bind(value_str=self.popup_label.setter('text')) # value_str must be a Kivy StringProperty
self.add_widget(self.popup_label)
def show_popup(self, even=True):
if even:
popup = ModalView(size_hint=(None, None), auto_dismiss=False, size=(500,380))
self.remove_widget(self.popup_label)
popup.add_widget(self.popup_label)
self.value_str = str(randint(0,100))
popup.open()
else: # find all instances of ModalView and dismiss them
for widget in App.get_running_app().root_window.children:
if isinstance(widget, ModalView):
print "observers of value_str property:"
observers = self.get_property_observers('value_str')
for observer in observers:
print observer
widget.remove_widget(self.popup_label)
self.add_widget(self.popup_label)
self.value_str =''
widget.dismiss(force=True, animation=False)
Clock.schedule_once(lambda dt: self.show_popup(not even), 0.25)
class MyApp(App):
def build(self):
mw=MainWidget()
Clock.schedule_once(lambda dt: mw.show_popup(),0)
return mw
if __name__ == '__main__':
MyApp().run()

How to use random with button in Kivy

I just want to have 2 buttons in my KIVY APP.
One with text "Hello" and other having a random number from 0-9.
My code
#!/usr/bin/kivy
import kivy
kivy.require('1.7.2')
from random import random
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from random import random
Builder.load_string("""
<Highest>:
r1c1: "hello"
r1c2: random.randrange(10)
GridLayout:
cols: 1
Button:
text: root.r1c1
Button:
text: root.r1c2
""")
class Highest(Screen):
pass
# Create the screen manager
sm = ScreenManager()
sm.add_widget(Highest(name='Highest'))
class TestApp(App):
def build(self):
return sm
if __name__ == '__main__':
TestApp().run()
My code works if I just have one button with text - Hello. Random seems not working.
Perhaps it's because randrange isn't returning a string, but an int. You could try:
r1c2: str(random.randrange(10))
OR
Try adding it as a function to your root widget:
class Highest(Screen):
def get_rand(self):
return str(random.randrange(10))
And your kv would look like this:
r1c2: root.get_rand()

How to change popup label in kivy

My application appends a variable score by one on each click. I want to display a popup after each click to show score.
My attempt:
#!/usr/bin/kivy
import kivy
kivy.require('1.7.2')
from random import random
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from random import random
from random import choice
from kivy.properties import StringProperty
import time
score=0
my_popup = Popup(title='Test popup',
content=Label(text=str(score)),
size_hint=(None, None))
Builder.load_string("""
<Highest>:
GridLayout:
cols: 1
Button:
id: btn_0
text: "0"
on_press: root.new()
Label:
""")
class Highest(Screen):
def new(self):
global score
score=score+1
self.ids['btn_0'].text = str(score)
my_popup.open()
# Create the screen manager
sm = ScreenManager()
sm.add_widget(Highest(name='Highest'))
class TestApp(App):
def build(self):
return sm
if __name__ == '__main__':
TestApp().run()
However, score is always displayed as 0. Score is increasing correctly and can be seen on button text.
You would probably need to define my_popup as a function with score as argument:
def my_popup(updated_score):
pop = Popup(title='Test popup', content=Label(text=str(updated_score)),
size_hint=(None,None))
pop.open()
Then call it at function "new" passing the updated score:
class Highest(Screen):
def new(self):
global score
score += 1
self.ids['btn_0'].text = str(score)
my_popup(score)

How do I update the Color of a **dynamically added** Ellipse (not using Builder) according to Widget Kivy properties?

This is very related to this other question. The only difference is that I am adding the Ellipse dynamically with with self.canvas instead of using the Builder (Builder.load_string or Builder.load_file). So here is the code that does work. When you click in the Ellipse it moves and change Color:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty
from kivy.graphics import Color, Ellipse
Builder.load_string("""
<CircleWidget>:
canvas:
Color:
rgba: self.r,1,1,1
Ellipse:
pos: self.pos
size: self.size
""")
class CircleWidget(Widget):
r = NumericProperty(0)
def __init__(s, **kwargs):
s.size= [50,50]
s.pos = [100,50]
super(CircleWidget, s).__init__(**kwargs)
def on_touch_down(s, touch):
if s.collide_point(touch.x,touch.y):
s.pos = [s.pos[1],s.pos[0]] # this works
s.r = 1.0 # this also works
class TestApp(App):
def build(s):
parent = Widget()
parent.add_widget(CircleWidget())
return parent
if __name__ == '__main__':
TestApp().run()
If I try to do the same without using the Builder, it doesn't work anymore:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty
from kivy.graphics import Color, Ellipse
class CircleWidget(Widget):
r = NumericProperty(0)
def __init__(s, **kwargs):
s.size= [50,50]
s.pos = [100,50]
super(CircleWidget, s).__init__(**kwargs)
with s.canvas:
Color(s.r,1,1,1)
Ellipse(pos = s.pos, size = s.size)
def on_touch_down(s, touch):
if s.collide_point(touch.x,touch.y):
s.pos = [s.pos[1],s.pos[0]] # This doesn't work anymore
s.r = 1.0 # Neither do this
class TestApp(App):
def build(s):
parent = Widget()
parent.add_widget(CircleWidget())
return parent
if __name__ == '__main__':
TestApp().run()
The code runs and the event is actually called. More over, the Widget is moved (even if it is visually not clear) but the Instructions of the canvas are not updated.
Any ideas?
The first version works because kv lang automatically bind the canvas recreation to properties in expressions, so these lines:
Color:
rgba: self.r,1,1,1
do more than this one:
Color(s.r,1,1,1)
what you can do, howether, is to bind self.r to auto rebuild your canvas instructions.
in __init__
self.bind(r=self.redraw)
self.bind(pos=self.redraw)
self.bind(size=self.redraw)
and move the
with s.canvas:
Color(s.r,1,1,1)
Ellipse(pos = s.pos, size = s.size)
Part to a method named redraw, with a self.canvas.clear() call before.
full result:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty
from kivy.graphics import Color, Ellipse
class CircleWidget(Widget):
r = NumericProperty(0)
def __init__(s, **kwargs):
super(CircleWidget, s).__init__(**kwargs)
s.bind(r=s.redraw)
s.bind(pos=s.redraw)
s.bind(size=s.redraw)
s.size = [50, 50]
s.pos = [100, 50]
def redraw(s, *args):
s.canvas.clear()
with s.canvas:
Color(s.r, 1, 1, 1)
Ellipse(pos = s.pos, size = s.size)
def on_touch_down(s, touch):
if s.collide_point(touch.x, touch.y):
print "gotcha"
s.pos = [s.pos[1], s.pos[0]]
s.r = 1.0
class TestApp(App):
def build(s):
parent = Widget()
parent.add_widget(CircleWidget())
return parent
if __name__ == '__main__':
TestApp().run()

Resources