Here is a basic sample that I am working with. The label displays as I would expect, but the text never changes, even though I do see the print statements in the console showing that the Clock.schedule_interval is ticking away. Any thoughts as to what have gone wrong???
Thank you and Happy New Year!
First the .kvlang file
<Demo>:
button_text: my_button
BoxLayout:
Label:
id: my_button
text: 'Initial Text!'
And my Python.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty, StringProperty
from kivy.clock import Clock
import random
class Demo(BoxLayout):
button_text = ObjectProperty
def change_text(self, dt):
self.button_text.text = str(random.randint(1, 10))
print('Should have changed button text to {}'.format(self.button_text.text))
def start(self):
Clock.schedule_interval(self.change_text, 10)
class TutorialApp(App):
def build(self):
foo = Demo()
foo.start()
return Demo()
if __name__ == "__main__":
TutorialApp().run()
You are missing parenthesis
button_text = ObjectProperty
change to
button_text = ObjectProperty(None) # Ha! :)
Also you should return foo and not create another Demo
def build(self):
foo = Demo()
foo.start()
#return Demo() change to...
return foo
Since the later Demo will not be updated...
Related
I have a question about kivy. Is it possible to create screens as variables? Lets say I want to create a new screen with a button inside the kivy application. Is it possible to do that? If so how could you do it??
You just have to create a Screen object that you want to create and add it to the ScreenManager:
screen = Your_Screen(name="some_name")
your_screen_manager.add_widget(screen)
Example:
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))
button = Button(text="{}: Add Screen".format(self.name))
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="screen {}".format(n))
self.manager.add_widget(screen)
self.manager.current = screen.name
# Create the screen manager
sm = ScreenManager()
sm.add_widget(TestScreen(name='screen 0'))
class TestApp(App):
def build(self):
return sm
if __name__ == '__main__':
TestApp().run()
I am just trying to get code working where I have two screens in a Python Kivy app that can switch back and forth, without using the .kv file stuff.
On this page: https://kivy.org/docs/api-kivy.uix.screenmanager.html, the second block of code from the top is what I am trying to accomplish, except I want to do it without the 'Builder.load_string("""' section, and instead just instantiate buttons normally.
Here is my attempt at doing so, except I can't get it to work:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
class MenuScreen(Screen):
def build(self):
def switchScreen():
root.manager.current = 'settings'
f = FloatLayout()
button1 = Button(text = "My settings button")
button2 = Button(text = "Back to menu", on_press = switchScreen)
f.add_widget(button1)
f.add_widget(button2)
class SettingsScreen(Screen):
def build(self):
def switchScreen():
root.manager.current = 'menu'
f = FloatLayout()
button1 = Button(text = "My settings button")
button2 = Button(text = "Back to menu", on_press = switchScreen)
f.add_widget(button1)
f.add_widget(button2)
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
class MainApp(App):
def build(self):
return sm
if __name__ == '__main__':
MainApp().run()
Running this code just creates a blank page that produces no errors.
Is there a way to designate it to draw a certain screen to begin with that I am missing? I'm not really sure where my issue is.
What you did wrong:
If you want to create Widget content from Python code you should place it inside Widget __init__ method, not build
You're creating a layout and then discarding it. You need to use self.add_widget(f) to actually use it after its creation
You're binding to your switchScreen method, so it needs to accept caller widget as an argument. Or you can simply use *args and not worry about it.
You're not in kv anymore, so there's no root. Use self instead.
Putting this all together:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
class MenuScreen(Screen):
def __init__(self, **kwargs):
super(MenuScreen, self).__init__(**kwargs)
def switchScreen(*args):
self.manager.current = 'settings'
f = FloatLayout()
button1 = Button(text = "My settings button")
button2 = Button(text = "Back to menu", on_press = switchScreen)
f.add_widget(button1)
f.add_widget(button2)
self.add_widget(f)
class SettingsScreen(Screen):
def __init__(self, **kwargs):
super(SettingsScreen, self).__init__(**kwargs)
def switchScreen(*args):
self.manager.current = 'menu'
f = FloatLayout()
button1 = Button(text = "My settings button")
button2 = Button(text = "Back to menu", on_press = switchScreen)
f.add_widget(button1)
f.add_widget(button2)
self.add_widget(f)
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
class MainApp(App):
def build(self):
return sm
if __name__ == '__main__':
MainApp().run()
Trying to populate the text of three buttons with the content of myList["One", "Two", "Three"]
btn1 = myList[1] etc
myList will be populated fron csv file
from kivy.app import App
from kivy.lang import Builder
kv = """
BoxLayout:
Button:
id: btn1
Button:
id: btn2
Button:
id: btn3
"""
class TestApp(App):
def build(self):
my_box = Builder.load_string(kv)
my_ShowList = ['My Way', 'Wine Drinker', 'Boots']
'''
This is where I get lost
want to use for loop to populate Button text with my_ShowList items
'''
return my_box
if __name__ == '__main__':
TestApp().run()enter code here`
You can do it entirely in python code:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
class TestClass(App):
def build(self):
my_box = BoxLayout()
my_show_list = ["My Way", "Wine Drinker", "Boots"]
my_box.my_buttons = [] # if you want to keep an "easy" reference to your buttons to do something with them later
#kivy doesnt crashes because it creates the property automatically
for message in my_show_list:
button = Button(text=message)
my_box.my_buttons.append(button)
my_box.add_widget(button)
return my_box
if __name__ == "__main__":
TestClass().run()
This should solve this problem:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
class MyLayout(BoxLayout):
def __init__(self, **kwargs):
super(MyLayout, self).__init__(**kwargs)
self.btn_list = ['My Way', 'Wine Drinker', 'Boots']
for btn_text in self.btn_list:
self.add_widget(Button(text=btn_text))
class MyApp(App):
def build(self):
return MyLayout()
if __name__ == '__main__':
MyApp().run()
The class kivy.event.EventDispatcher has a method called apply_property() which allows adding a property to a class at runtime.
The docs contain a warning:
This method is not recommended for common usage because you
should declare the properties in your class instead of using this
method.
It is not clear to me why this usage is discouraged, and declaration in class preferred. Can anyone enlighten me?
apply_property() is used to add property to a single class instance but has a side effect of polluting properties of other instances as well. Consider following code:
#!/usr/bin/kivy
import kivy
kivy.require('1.9.1')
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import NumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
Builder.load_string('''
<MyButton>:
on_press: print('np' in self.properties())
''')
class MyButton(Button):
pass
class MainApp(App):
def build(self):
mb1 = MyButton(text="mb1")
mb2 = MyButton(text="mb2")
mb1.apply_property(np=NumericProperty(100))
layout = BoxLayout()
layout.add_widget(mb1)
layout.add_widget(mb2)
return layout
if __name__ == '__main__':
MainApp().run()
As you can see, even though I used apply_property on mb1 instance, mb2 reports that it has such property as well. This is because properties are added to the class, not the instance. However, only mb1 has an actual value:
#!/usr/bin/kivy
import kivy
kivy.require('1.9.1')
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import NumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
Builder.load_string('''
<MyButton>:
on_press: print(self.np)
''')
class MyButton(Button):
pass
class MainApp(App):
def build(self):
mb1 = MyButton(text="mb1")
mb2 = MyButton(text="mb2")
mb1.apply_property(np=NumericProperty(100))
layout = BoxLayout()
layout.add_widget(mb1)
layout.add_widget(mb2)
return layout
if __name__ == '__main__':
MainApp().run()
Trying to get the value of np property from mb2 instance yields an AttributeError. A safe alternative:
#!/usr/bin/kivy
import kivy
kivy.require('1.9.1')
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import NumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
Builder.load_string('''
<MyButton>:
on_press: print(self.np)
''')
class MyButton(Button):
np=NumericProperty(100)
class MainApp(App):
def build(self):
mb1 = MyButton(text="mb1")
mb2 = MyButton(text="mb2")
layout = BoxLayout()
layout.add_widget(mb1)
layout.add_widget(mb2)
return layout
if __name__ == '__main__':
MainApp().run()
Source: this issue.
In addition to #Nykakin's example, apply_property also interferes with inheritance in a way 'regular' class variables don't, which might therefore lead to unexpected behaviour. Here, MyButton2 is a subclass of MyButton1 and instances get attributes pp and oo. While assigning a class variable oo in MyButton1 does not interfere with instances of MyButton2, adding a Property with apply_property to an instance of MyButton1 suddenly makes pp inaccessible in MyButton2:
from kivy.properties import NumericProperty
from kivy.app import App
from kivy.uix.button import Button
from kivy.lang import Builder
class MyButton1(Button):
pass
class MyButton2(MyButton1):
def __init__(self, **kwargs):
super(MyButton2, self).__init__(**kwargs)
self.pp = 11
self.oo = 42
bl = Builder.load_string("""BoxLayout:
MyButton1:
id: hb01
text: "MyButton1"
MyButton2:
id: hb02
text: "MyButton2"
""")
def on_press(sender):
on_press.counter += 1
print "in on_press:"
try:
print " Now pp == {}".format(sender.pp)
except:
print " no attribute 'pp'"
prop = sender.property('pp', quiet=True)
sender.apply_property(pp=NumericProperty(on_press.counter))
print " Applying property ... Now it's {}".format(sender.pp)
try:
print " Now oo == {}".format(sender.oo)
except:
print " no attribute 'oo'"
sender.__class__.oo = on_press.counter + 17
on_press.counter = 0
def on_press2(sender):
print "on_press2:"
try:
print " Now pp == {}".format(sender.pp)
except:
print " no attribute 'pp'"
try:
print " Now oo == {}".format(sender.oo)
except:
print " no attribute 'oo'"
class T01App(App):
def build(self):
return bl
def on_start(self):
self.root.ids.hb01.bind(on_press=on_press)
self.root.ids.hb02.bind(on_press=on_press2)
if __name__ == '__main__':
T01App().run()
I want to make a game and I have chosen Kivy for my GUI, as I have written my backend in Python. I am currently using runTouchApp(appwindow) to run the application, where appwindow is a FloatLayout() object. The way I update my screen is that I run appwindow.clear_widgets() and then appwindow.add_widget(new_screen) where new_screen is the layout object which contains all other widgets.
It worked fine till now, but for some reason I can't understand the widget I add gets loaded properly (according to the cmd running in the background) but won't display till I change the screen size.
Here is a sample code:
import kivy
kivy.require('1.1.1')
from kivy.base import runTouchApp
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
appwindow = FloatLayout()
class MenuScreen():
def build(self):
bg = Image(source = 'Images/bg/bg.jpg',allow_stretch= True,keep_ratio= False)
box = BoxLayout(orientation = 'vertical')
menu_items = []
for i in range (0,4):
button = Button(text = 'Menu item no:'+str(i))
button.bind(state = checkMenuPress)
menu_items.append(button)
for button in menu_items:
box.add_widget(button)
floater = FloatLayout(size = bg.size)
floater.add_widget(bg)
floater.add_widget(box)
floater.size_hint = 1,1
return floater
class SecondScreen():
def build(self):
bg = Image(source = 'Images/bg/bg.jpg',allow_stretch= True,keep_ratio= False)
box = BoxLayout(orientation = 'vertical')
box.add_widget(bg)
return box
def checkMenuPress(button,*args):
if button.state == 'normal':
sc = SecondScreen().build()
appwindow.clear_widgets()
appwindow.add_widget(sc)
if __name__ == '__main__':
menuscreen = MenuScreen().build()
appwindow.add_widget(menuscreen)
runTouchApp(appwindow)
Okay, there are a few things wrong here. To create an app, you should subclass kivy.app.App and have it's build method return the "root" widget. Only this class needs build method: any others are redundant.
Once you have a root widget, it's just a matter of inserting and removing widgets. This is how I would approach it.
import kivy
kivy.require('1.1.1')
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy.app import App
class MenuScreen(App):
floater = None
def build(self):
bg = Image(source='Images/bg/bg.jpg',
allow_stretch= True,
keep_ratio= False)
box = BoxLayout(orientation='vertical')
menu_items = []
for i in range(0, 4):
button = Button(text='Menu item no:'+str(i))
button.bind(state=self.checkMenuPress)
menu_items.append(button)
for button in menu_items:
box.add_widget(button)
self.floater = FloatLayout(size=bg.size)
self.floater.add_widget(bg)
self.floater.add_widget(box)
self.floater.size_hint = 1, 1
return self.floater
def checkMenuPress(self, button,*args):
if button.state == 'normal':
self.floater.clear_widgets()
self.floater.add_widget(SecondScreen())
class SecondScreen(FloatLayout):
def __init__(self, **kwargs):
super(SecondScreen, self).__init__(**kwargs)
self.add_widget(Image(source='Images/bg/bg.jpg',
allow_stretch=True,
keep_ratio=False))
if __name__ == '__main__':
MenuScreen().run()
Okay, so here's a quick rework of the example using Screens. It's difficult without knowing what you are trying to do, so if this does not help, try writing summary of the behavior you are looking for.
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
class MenuScreen(Screen):
"""
The Screen containing the menu
"""
def __init__(self, sm, **kwargs):
super(MenuScreen, self).__init__(**kwargs)
self.sm = sm # Store a reference to the screen manager
bg = Image(source='Images/bg/bg.jpg',
allow_stretch= True,
keep_ratio= False)
box = BoxLayout(orientation='vertical')
menu_items = []
for i in range(0, 4):
button = Button(text='Menu item no:'+str(i))
button.bind(state=self.check_menu_press)
menu_items.append(button)
for button in menu_items:
box.add_widget(button)
floater = FloatLayout(size=bg.size)
floater.add_widget(bg)
floater.add_widget(box)
floater.size_hint = 1, 1
self.add_widget(floater)
def check_menu_press(self, button, *args):
if button.state == 'normal':
self.sm.current = "second"
class SecondScreen(Screen):
def __init__(self, sm, **kwargs):
super(SecondScreen, self).__init__(**kwargs)
self.add_widget(Image(source='Images/bg/bg.jpg',
allow_stretch=True,
keep_ratio=False))
self.sm = sm # Store a reference to the screen manager
def on_touch_down(self, touch):
self.sm.current = "menu"
class MainApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(MenuScreen(sm, name="menu"))
sm.add_widget(SecondScreen(sm, name="second"))
return sm
if __name__ == '__main__':
MainApp().run()
looks like i have found a source of the crashing problem. I had used multi-threading for a server client interaction. i had been using two extra threads, one for the server and another for the client. If i try to integrate the client thread with the GUI it raises another problem. even if the socket.recv() is after the ScreenManager.current = "newscreen" line, the app's screen does not change. it freezes in the way i left it. it resumes only after it gets a message from the server.