Kivy: Manipulating dynamically created widgets in update function - python-3.x

I am trying to extend the code in the answer here provided by Nykakin where widgets are defined on the fly in Kivy, dynamically assigned IDs, and then manipulated based on ID.
The 'end game' is to implement some basic 2D physics by changing position of widgets based on ID.
I am not able to 'walk' the widget tree from the update function (errors inline).
Is it possible to do such a thing?
Here is the code:
#code begins here
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.stacklayout import StackLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.clock import Clock
from kivy.factory import Factory
class MyWidget(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
button = Button(text="Print IDs", id="PrintIDsButton")
button.bind(on_release=self.print_label)
self.add_widget(button)
# crate some labels with defined IDs
for i in range(5):
self.add_widget(Button(text=str(i), id="button no: " + str(i)))
# update moved as per inclement's recommendation
Clock.schedule_interval(MyApp.update, 1 / 30.0)
def print_label(self, *args):
children = self.children[:]
while children:
child = children.pop()
print("{} -> {}".format(child, child.id))
# we can change label properties from here! Woo!
children.extend(child.children)
if child.id == "PrintIDsButton":
child.text = child.text + " :)"
class MyApp(App):
def build(self):
return MyWidget()
def update(self, *args):
#ERROR# AttributeError: 'float' object has no attribute 'root'
children = self.root.children[:]
#ERROR# TypeError: 'kivy.properties.ListProperty' object is not subscriptable
#children = MyWidget.children[:]
#while children:
# child = children.pop()
# print("{} -> {}".format(child, child.id))
# children.extend(child.children)
#ERROR# TypeError: 'kivy.properties.ListProperty' object is not iterable
#for child in MyWidget.children:
# print("{} -> {}".format(child, child.id))
if __name__ == '__main__':
MyApp().run()
Windows 7 SP1
Kivy 1.8.0 / Python 3.3
My apologies if my terminology is incorrect! (and thank you for your time)

children = MyWidget.children[:]
MyWidget is the class itself, not an instance of it, and so the children is a ListProperty object and not the actual list you want.
You want instead the children of the MyWidget instance that is your root widget, self.root.children.
Also, you clock schedule the update function at class level; I think this is bad practice, and could lead to subtle bugs. It would be more normal to do it in the __init__ method of the widget instead.

So putting the update function within the 'MyWidget' class and then accessing elements within 'MyWidget' by calling self.children worked!
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.stacklayout import StackLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.clock import Clock
from kivy.factory import Factory
class MyWidget(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
button = Button(text="Print IDs", id="PrintIDsButton")
button.bind(on_release=self.print_label)
self.add_widget(button)
# crate some labels with defined IDs
for i in range(5):
self.add_widget(Button(text=str(i), id="button no: " + str(i)))
# update moved as per inclement's recommendation
Clock.schedule_interval(self.update, 1 / 5.0)
def print_label(self, *args):
children = self.children[:]
while children:
child = children.pop()
print("{} -> {}".format(child, child.id))
# Add smiley by ID
children.extend(child.children)
if child.id == "PrintIDsButton":
child.text = child.text + " :)"
def update(self, *args):
children = self.children[:]
while children:
child = children.pop()
# remove smiley by ID
if child.id == "PrintIDsButton":
child.text = "Print IDs"
class MyApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
MyApp().run()
Thanks again to inclement!

Related

Python kivy RecursionError: maximum recursion depth exceeded in comparison

I am trying to create a simple app with kivy in python
but when i run this code i get following error
RecursionError: maximum recursion depth exceeded in comparison
import wikipedia
from kivy.app import App
from kivy.uix.popup import Popup
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
class GridLayout(GridLayout):
def __init__(self, **kwargs):
super(GridLayout, self).__init__()
# Number of columns
self.cols = 1
# Second grid Layout
self.second_layout = GridLayout()
self.second_layout.cols = 2
# Creating a text field to show the result of entered query
self.query_result = TextInput(text='', size_hint_y=0.8)
self.second_layout.add_widget(self.query_result) # Adding query result on the screen
# Creating a text input field to get the query from user
self.query = TextInput(text='', multiline=False, hint_text="Enter your Query", size_hint_y=0.1, font_size=20)
self.second_layout.add_widget(self.query)
# Adding Second layout on the screen
self.add_widget(second_layout)
# Creating a submit button
self.submit_button = Button(text="Submit", size_hint_y=0.1, font_size=40, on_press=self.submit)
self.add_widget(self.submit_button)
def submit(self, instance):
try:
query_result_from_wikipedia = wikipedia.page(self.query.text).summary
self.query_result.text = query_result_from_wikipedia
except:
popup = Popup(title='Query Not Found',
content=Label(text='Try to Search Anything else'),
size_hint=(None, None), size=(400, 400))
popup.open()
class MyApp(App):
def build(self):
return GridLayout()
if __name__ == '__main__':
MyApp().run()
But when i remove the second gridlayout from it it runs without errors
The first problem is the name of your class. Don't name a class the same as its subclass:
class GridLayout(GridLayout):
is likely to cause problems. Just change it to something like:
class MyGridLayout(GridLayout):

Why is Kivy FocusBehavior applied to Buttons not working?

In the kivy.FocusBehavior documentation (https://kivy.org/doc/stable/api-kivy.uix.behaviors.focus.html), an example with a FocusButton(FocusBehavior, Button) is given. But using the tab key on Windows 10 to cycle between the buttons added to the GridLayout does not work. What is wrong in the code below ?
from kivy.app import App
from kivy.uix.behaviors.focus import FocusBehavior
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
class FocusButton(FocusBehavior, Button):
def _on_focus(self, instance, value, *largs):
print(self.text)
class FocusBehaviorGUI(GridLayout):
def __init__(self, **kwargs):
super().__init__( **kwargs)
self.cols = 4
self.rows = 2
for i in range(8):
self.add_widget(FocusButton(text=str(i)))
# clicking on a widget will activate focus, and tab can now be used
# to cycle through
class FocusBehaviorApp(App):
def build(self):
return FocusBehaviorGUI()
if __name__ == '__main__':
FocusBehaviorApp().run()
It is working. try changing:
def _on_focus(self, instance, value, *largs):
to:
def on_focus(self, instance, value, *largs):

Kivy - TextInput text show in Label text when Button is clicked (Not KV Lang)

How to make text that I enter in the TextInput field (textA) will show in the Label (labelA) by using a button?
Every time there is new inputs, when the button is clicked, the labels will show the latest inputs.
Problem here when I use:
self.textA = TextInput(text='ss')
When I type new text and I click the button, the label always show 'ss'. Its not updating the new input.
Hope someone can show the method - just in python, not the Kivy Languange
Thanks
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.label import Label
class One(BoxLayout):
def __init__(self, **kwargs):
super(One, self).__init__(**kwargs)
self.orientation = 'vertical'
# text input
self.textA = TextInput(font_size='60sp')
self.add_widget(self.textA)
self.add_widget(Two())
class Two(BoxLayout):
def __init__(self, **kwargs):
super(Two, self).__init__(**kwargs)
self.orientation = 'vertical'
# Button
self.btnA = Button(text='Show')
self.btnA.bind(on_press=show)
self.add_widget(self.btnA)
# Output
self.labelA = Label(text='Result Here From Input')
self.add_widget(self.labelA)
def show(value):
Two().labelA.text = str(One().textA.text)
x = Two().labelA.text
y = One().textA.text
print(x)
print(y)
class TestApp(App):
def build(self):
return One()
if __name__ == '__main__':
TestApp().run()
Whenever you use One() or Two() you are creating new instances of One or Two, and these new instances are unrelated to the instances that are in your GUI. If you want to access the instances in your GUI, you must arrange for that. For example, in the One class, you can save reference to the Two instance:
class One(BoxLayout):
def __init__(self, **kwargs):
super(One, self).__init__(**kwargs)
self.orientation = 'vertical'
# text input
self.textA = TextInput(font_size='60sp')
self.add_widget(self.textA)
self.two = Two() # save a reference to the Two instance
self.add_widget(self.two)
Since the instance of One is the root of the App (returned by the build() method), you can access it from the App. Then your show() method can become:
def show(value):
one = App.get_running_app().root
two = one.two
two.labelA.text = one.textA.text
x = two.labelA.text
y = one.textA.text
print(x)
print(y)

Why is my kivy program not calling the function from another class?

I think this is more of a python question than a kivy question.
class Keyboard is sharing a method from class GUI. I created a GUI instance called self.a to connected the 2 classes in class Keyboard. Also, I created a keyboard class instance, self.key in class MainApp.
When I use this method, print ("Return button is pressed") the "return" button was able to do the print statement. I understand why it works. When I use the self.a.up() in the method, the return button does not call the up() method from class GUI. It's probably a small detail or a concept that I am missing. This is not an error from the rest of the program. Please help and thanks in advance. Code:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.lang.builder import Builder
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.modules import keybinding
from kivy.core.window import Window
class GUI(BoxLayout):
main_display = ObjectProperty()
text_input = ObjectProperty()
def plus_1(self):
self.value = int(self.main_display.text)
self.main_display.text = str(self.value + 1)
print (self.main_display.text)
def minus_1(self):
self.value = int(self.main_display.text)
self.main_display.text = str(self.value - 1)
def up(self):
self.main_display.text = self.text_input.text
self.text_input.text = ''
class Keyboard(Widget):
def __init__(self,):
super().__init__()
self.a = GUI()
self.keyboard = Window.request_keyboard(None, self)
self.keyboard.bind(on_key_down=self.on_keyboard_down)
def on_keyboard_down(self, keyboard, keycode, text, modifiers):
if keycode[1] == 'enter':
self.a.up()
# print ("Return button is pressed")
return True
class MainApp(App):
def build(self):
key = Keyboard()
return GUI()
if __name__=="__main__":
app = MainApp()
app.run()
I think it works fine but in another object that you can't see. The problem is that you show one object of GUI class on screen. And for the Keyboard class you have created another object that you can't see. So the solution is to use one object of GUI class and operate with it in both of MainApp and Keyboard classes. Something like:
class MainApp(App):
def build(self):
# here we create an object...
self.gui = GUI()
# ...send it to Keyboard class
key = Keyboard(self.gui)
# and return it
return self.gui
class Keyboard(Widget):
def __init__(self, gui):
super().__init__()
# and here we receive that object instead of creating new one
self.a = gui

How to get nested Kivy widgets to interact between each other?

I'm playing around with the examples provided in the Kivy tutorial. I was able to create nested widgets, but can't seem to get the buttons to modify objects in the other parts of the screen.
In this example, I've tried to modify the Kivy example script to turn it into a simple login window with a numpad drawn from buttons (let's ignore the vkeyboard for this case, I'm trying to get a hold of buttons...). In short, buttons on the left, login textinput on the right. I cannot seem to have the actions on the buttons affect the text input on the right. It sounds very simple, but I can't seem to figure it out. Should I use a global variable? Am I creating my objects incorrectly for this purpose?
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
class AddNumpad(GridLayout):
def __init__(self, **kwargs):
super(AddNumpad, self).__init__(**kwargs)
self.cols = 3
self.padding = 50
def callback(instance):
print('The button <%s> is being pressed' % instance.text)
# TODO: trying to populate the password textinput here
for i in range(1,10):
btn = Button(text=str(i))
btn.bind(on_press=callback)
self.add_widget(btn)
self.add_widget(Label(text=''))
btn = Button(text='0')
btn.bind(on_press=callback)
self.add_widget(btn)
class LoginScreen(GridLayout):
def __init__(self, **kwargs):
super(LoginScreen, self).__init__(**kwargs)
self.password = TextInput(password=True, multiline=False)
self.cols = 2
self.numpad = AddNumpad()
self.add_widget(self.numpad)
self.entry = ''
self.password.text = self.entry
self.add_widget(self.password)
class MyApp(App):
userInput = ''
def build(self):
return LoginScreen()
if __name__ == '__main__':
MyApp().run()
You can use a custom property and bind it to a callback in your LogginScreen class:
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.properties import StringProperty
class AddNumpad(GridLayout):
passwIn = StringProperty() # <<<<<<<<<<<<<
def __init__(self, **kwargs):
super(AddNumpad, self).__init__(**kwargs)
self.cols = 3
self.padding = 50
for i in range(1, 10):
btn = Button(text=str(i))
btn.bind(on_press=self.callback)
self.add_widget(btn)
self.add_widget(Label(text=''))
btn = Button(text='0')
btn.bind(on_press=self.callback)
self.add_widget(btn)
def callback(self, instance):
self.passwIn += instance.text # <<<<<<<<<<<<<
class LoginScreen(GridLayout):
def __init__(self, **kwargs):
super(LoginScreen, self).__init__(**kwargs)
self.password = TextInput(password=True, multiline=False)
self.cols = 2
self.numpad = AddNumpad()
self.numpad.bind(passwIn=self.numpad_pressed) # <<<<<<<<<<<<<
self.add_widget(self.numpad)
self.add_widget(self.password)
def numpad_pressed(self, instance, value):
self.password.text = value
class MyApp(App):
def build(self):
return LoginScreen()
if __name__ == '__main__':
MyApp().run()

Resources