How to get nested Kivy widgets to interact between each other? - python-3.x

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

Related

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

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

Kivy - Black screen issue

Why am I getting black screen for the below code in kivy?
MyGrid class is not instantiating.
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
class MyGrid(GridLayout):
def __int__(self, **kwargs):
super(MyGrid, self).__init__(**kwargs)
self.cols = 2
self.add_widget(Label(text="First name="))
self.firstName = TextInput(multiline=False)
self.add_widget(self.firstName)
class Myapp(App):
def build(self):
return MyGrid()
if __name__ == '__main__':
Myapp().run()
You've written __int__ instead of __init__

Python Kivy Panel Header unable to render content from class

I am trying to create a simple TabbedPanel with 4 tabs. I want each tab to render the widgets from 4 classes. Kivy seems to not be rendering this simple Label. I want to do this with the libraries and not by creating a .kv file and importing it with Builder.load_file(file). In the code I provided, I only show the default_tab. Thanks in advance.
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.gridlayout import GridLayout
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelHeader
kivy.require('1.10.1')
class DeployScreen(GridLayout):
def __init__(self, **kwargs):
super(DeployScreen, self).__init__(**kwargs)
self.cols = 1
self.add_widget(Label(text='Deploy'))
return(None)
class NucleusPanel(TabbedPanel):
tab_pos = "top_left"
nuc_panel = TabbedPanel()
nuc_panel.default_tab_text = "Deploy"
nuc_panel.default_tab_content = DeployScreen()
class NucleusApp(App):
def build(self):
return(NucleusPanel())
if __name__ == "__main__":
NucleusApp().run()
Add a constructor for class NucleusPanel()
main.py
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.tabbedpanel import TabbedPanel
class DeployScreen(GridLayout):
def __init__(self, **kwargs):
super(DeployScreen, self).__init__(**kwargs)
self.cols = 1
self.add_widget(Label(text='Deploy'))
class NucleusPanel(TabbedPanel):
def __init__(self, **kwargs):
super(NucleusPanel, self).__init__(**kwargs)
self.tab_pos = "top_left"
self.default_tab_text = "Deploy"
self.default_tab_content = DeployScreen()
class NucleusApp(App):
def build(self):
return NucleusPanel()
if __name__ == "__main__":
NucleusApp().run()
Output

Kivy: Manipulating dynamically created widgets in update function

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!

Resources