Kivy property observer objects left behind after ModalView is dismissed - kivy

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()

Related

How to know if user is scrolling up or scrolling down in Kivy?

I am trying to make an Application in kivy which uses ScrollView. Is there any way through which I can know if user is scrolling down or scrolling up.
You can store mouse position received in on_scroll_move and then determine the direction by comparing value you have now with value you've saved before.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
Builder.load_string('''
<MyWidget>:
ScrollView:
on_scroll_start: root.scroll_pos_y = args[1].pos[1]
on_scroll_move: root.scroll_direction(args[1].pos[1])
Label:
text: 'test'
size_hint_y: None
height: 1000
''')
class MyWidget(BoxLayout):
scroll_pos_y = 0
def scroll_direction(self, new_scroll_pos_y):
if new_scroll_pos_y - self.scroll_pos_y < 0:
print('up')
else:
print('down')
self.scroll_pos_y = new_scroll_pos_y
class MyApp(App):
def build(self):
return MyWidget()
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()

Implement android back button function in Kivy

Firstly, i have gone thru many examples, but could not figure out this, so asking here.
My app is to be run on android. Screen 1 have a button which will go to screen 2 on click.
All i need is code to move back to screen 1 on pressing back button on screen 2
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.boxlayout import BoxLayout
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
from kivy.clock import Clock
from functools import partial
from kivy.utils import platform
from kivy.core.window import Window
Builder.load_string("""
<MenuScreen>:
Button:
text: "move to next screen 2"
on_press: root.manager.current = 'game_mode'
<GameMode>:
Label:
text: "screen 2"
""")
class MenuScreen(Screen):
pass
class GameMode(Screen):
pass
sm = ScreenManager()
menu_screen = MenuScreen(name='menu')
sm.add_widget(menu_screen)
sm.add_widget(GameMode(name='game_mode'))
class TestApp(App):
def build(self):
self.bind(on_start=self.post_build_init)
return sm
def post_build_init(self,ev):
if platform == 'android':
import android
android.map_key(android.KEYCODE_BACK, 1001)
win = Window
win.bind(on_keyboard=self.key_handler)
def key_handler(self, window, keycode1, keycode2, text, modifiers):
if keycode1 == 27 or keycode1 == 1001:
sm.go_back()
return True
return False
if __name__ == '__main__':
TestApp().run()
Please help. I want solution based on screen manager. I would really appreciate if you can improve my code to provide solution.
Finally, figured it out
#!/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.boxlayout import BoxLayout
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
from kivy.clock import Clock
from functools import partial
from kivy.core.window import Window
Builder.load_string("""
<MenuScreen>:
Button:
text: "move to next screen 2"
on_press: root.manager.current = 'game_mode'
<GameMode>:
Label:
text: "screen 2"
""")
class MenuScreen(Screen):
pass
class GameMode(Screen):
pass
sm = ScreenManager()
menu_screen = MenuScreen(name='menu')
sm.add_widget(menu_screen)
sm.add_widget(GameMode(name='game_mode'))
class TestApp(App):
def build(self):
self.bind(on_start=self.post_build_init)
return sm
def post_build_init(self,ev):
from kivy.base import EventLoop
EventLoop.window.bind(on_keyboard=self.hook_keyboard)
def hook_keyboard(self, window, key, *largs):
if key == 27:
print sm.current
if(sm.current=='menu'):
App.get_running_app().stop()
sm.current='menu'
return True
if __name__ == '__main__':
TestApp().run()
ScreenManager has a previous() method that should solve your problem:
Builder.load_string("""
<MenuScreen>:
Button:
text: "move to next screen 2"
on_press: root.manager.current = 'game_mode'
<GameMode>:
BoxLayout:
orientation: "vertical"
Button:
text: "go back"
on_press: root.manager.current = root.manager.previous()
Label:
text: "screen 2"
""")

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)

Change button text value for on_press event in Kivy

My application has a single button with text value as '2'. I want to change the text value to 100 on on_press event
My attempt:
#!/usr/bin/kivy
import kivy
kivy.require('1.7.2')
from random import random
from random import choice
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
r1c2=random()
Builder.load_string("""
<Highest>:
r1c2: str(2)
GridLayout:
cols: 1
Button:
text: root.r1c2
on_press: root.new()
""")
class Highest(Screen):
def new(self):
r1c2=str(100)
# 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()
Error: Currently, nothing happens when the button is pressed. Please help
You should be using kivy properties available. See kivy.properties for more information.
Add this import for access to string property:
from kivy.properties import StringProperty
And your Highest class should be:
class Highest(Screen):
r1c2 = StringProperty(str(2))
def new(self):
self.r1c2 = str(100)
At initialization r1c2 value is equal to '2'. When the function new() is called value of r1c2 will become '100'. Button text is bind to the string property r1c2 so it will automatically change.
You don't need r1c2=str(2) in your builder string.
Builder.load_string("""
<Highest>:
GridLayout:
cols: 1
Button:
text: root.r1c2
on_press: root.new()
""")

Resources