dynamic binding of functions not working in kivy - python-3.x

I wanted to make a dynamic class of a group of widget so whenever I add them to my main app, I had to only bring in the changes from somewhere(e.g. a python file, class etc, here I've done them in explicit lists), but binding the widget with properties like "on_text" which respond to events didn't really work, other properties like 'text', 'hint_text' worked perfectly but on_text doesn't really budge. I can't figure out the cause for this because I've checked the correct object being passed along the functions, below is my code:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
class Dynamic_TextInput(BoxLayout):
def __init__(self,changes=None, **kwargs):
super().__init__(**kwargs)
self.widgets = {'Mylabel':self.ids.mylabel,
'Myinput': self.ids.mytext}
self.Change(changes)
def Change(self, changes=None, **kwargs):
if changes:
for change in changes:
curwidget = self.widgets[change[0]]
cur_properties = change[1]
for attr in cur_properties.keys():
if attr=='bind':
print("The cur properties are: ")
print(cur_properties[attr])
curwidget.bind(**(cur_properties[attr]))
else:
setattr(curwidget, attr, cur_properties[attr])
class mainwidget(BoxLayout):
myobj1 = ObjectProperty()
myobj2 = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
change1=[('Mylabel', {'text':'firstchange',
'bind':{'on_text':lambda *_: print('something')}
}),
('Myinput', {'hint_text': 'Changed hint text'})
]
self.add_widget(Dynamic_TextInput(changes=change1))
self.add_widget(Dynamic_TextInput())
class MainApp(App):
def build(self):
return mainwidget()
if __name__ == '__main__':
MainApp().run()
And, in the kivy file:
#:kivy 1.10.0
<Dynamic_TextInput>:
myobj1: mylabel
myobj2: mytext
orientation: 'horizontal'
Label:
id: mylabel
text: 'testlable'
TextInput:
id: mytext
hint_text: 'some test'
What is the cause? And how can I fix it?

There are 2 ways to make a binding of a property:
*.py
object.bind(property=callback)
*.kv
object:
on_property: callback
So in the case of making the connection in python you should not use on_text, but text.
To verify the change, the Label can not be edited from the GUI, so I will use the TextInput to write on the Label:
*.py
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
class Dynamic_TextInput(BoxLayout):
def __init__(self,changes=None, **kwargs):
super().__init__(**kwargs)
self.widgets = {'Mylabel':self.ids.mylabel,
'Myinput': self.ids.mytext}
self.Change(changes)
def Change(self, changes=None, **kwargs):
if changes:
for change in changes:
curwidget = self.widgets[change[0]]
cur_properties = change[1]
for attr in cur_properties.keys():
if attr=='bind':
print("The cur properties are: ")
curwidget.bind(**(cur_properties[attr]))
print(cur_properties[attr])
else:
setattr(curwidget, attr, cur_properties[attr])
class mainwidget(BoxLayout):
myobj1 = ObjectProperty()
myobj2 = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
change1=[('Mylabel', {'text':'firstchange',
'bind':{'text':lambda *_: print('something')}
}),
('Myinput', {'hint_text': 'Changed hint text'})
]
self.add_widget(Dynamic_TextInput(changes=change1))
self.add_widget(Dynamic_TextInput())
class MainApp(App):
def build(self):
return mainwidget()
if __name__ == '__main__':
MainApp().run()
*.kv
#:kivy 1.10.0
<Dynamic_TextInput>:
myobj1: mylabel
myobj2: mytext
orientation: 'horizontal'
Label:
id: mylabel
text: mytext.text # <----
TextInput:
id: mytext
hint_text: 'some test'

Related

Change TextInput to display only function for Kivy Python

I'm still new to kivy and still relatively new to Python.
I have main. py. I plan to have WindowGUI take a number of inputs from main.py and display them in its textbox.
Here is what I have:
from os import curdir
from posixpath import dirname
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout # one of many layout structures
from kivy.uix.textinput import TextInput # allow for ...text input.
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.spinner import Spinner
from kivy.uix.dropdown import DropDown
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from kivy.properties import ObjectProperty
kivy.require('2.0.0')
class MainGUI(Screen):
encoder = ObjectProperty()
mileage = ObjectProperty()
voltage = ObjectProperty()
power = ObjectProperty()
camera = ObjectProperty()
engaged = ObjectProperty()
idle = ObjectProperty()
log = ObjectProperty()
clear = ObjectProperty()
status = ObjectProperty(None)
def __init__(self, saySomething):
super().__init__() #Go up to __init__ of App
self.saySomething = saySomething
def power_option(self):
self.power.text = "Power"
def print_status(self):
self.ids.Status_screen.text = self.saySomething
return self.ids.Status_screen.text
class CameraGUI(Screen):
def __init__(self, **kwargs): #kwargs is just a variable. it could be "banana" or "apple"
super().__init__() #Go up to __init__ of App
class GUIManager(ScreenManager):
pass
kv = Builder.load_file('newWindow.kv')
class WindowGUI(App):
def __init__(self):
super().__init__()
# self.say = say
print("TRY ME!!!!")
def build(self):
self.title = 'IamHero'
return kv
if __name__ == '__main__':
myWindowGUI = WindowGUI()
myMainGUI = MainGUI('I am hero!')
myWindowGUI.run()
###########################_newWindow.kv_#############################
GUIManager:
MainGUI:
CameraGUI:
<MainGUI>:
name: "main"
status: Status_screen
GridLayout:
cols: 2
size: root.width, root.height #covers entire window
padding: 15
GridLayout:
cols:1
spacing: 10
GridLayout:
cols:2
size_hint: .25,.1
Label:
id: Engaged
text: "Engaged"
Label:
id: Idle
text: "Idle"
TextInput:
# size_hint: 1
id: Status_screen
multiline: True
readonly: True
background_color: .6, .7, 0, 1
text: root.print_status()
#on_text: root.print_status()
GridLayout:
cols:2
size_hint: .25,.1
Button:
id: Log
text: "Log status"
# size_hint_x: .5
Button:
id: Clear
text: "Clear"
However, I got the following error:
Traceback (most recent call last):
File "c:/Users/Jojo/#RaspberryPi4/Test/WindowGUI.py", line 57, in <module>
kv = Builder.load_file('newWindow.kv')
File "C:\Users\Jojo\Anaconda3\lib\site-packages\kivy\lang\builder.py", line 306, in load_file
return self.load_string(data, **kwargs)
File "C:\Users\Jojo\Anaconda3\lib\site-packages\kivy\lang\builder.py", line 408, in load_string
self._apply_rule(
File "C:\Users\Jojo\Anaconda3\lib\site-packages\kivy\lang\builder.py", line 659, in _apply_rule
child = cls(__no_builder=True)
TypeError: __init__() got an unexpected keyword argument '__no_builder'
Any ideas about what I did wrong?
You cannot require a positional argument in the __init__() of a class that you intend to use in kv. Your __init__() for MainGUI:
def __init__(self, saySomething):
requires an argument for saySomething. An easy way around that is to just make saySomething into a StringProperty of MainGUI. Then you don't even need to implement an __init__():
class MainGUI(Screen):
encoder = ObjectProperty()
mileage = ObjectProperty()
voltage = ObjectProperty()
power = ObjectProperty()
camera = ObjectProperty()
engaged = ObjectProperty()
idle = ObjectProperty()
log = ObjectProperty()
clear = ObjectProperty()
status = ObjectProperty(None)
saySomething = StringProperty('Hi')
# def __init__(self, saySomething):
# super().__init__() #Go up to __init__ of App
# self.saySomething = saySomething
Then you can set the value of saySomething just like any other property in the kv:
<MainGUI>:
name: "main"
status: Status_screen
saySomething: 'I am hero!'
or in python you can do:
MainGUI(saySomething='I am hero!')
Also, the line:
myMainGUI = MainGUI('I am hero!')
(which will no longer work) is creating an instance of MainGUI that is unused. So that line of code can be deleted.

Kivy: Error while trying to capture options from MDDropDownMenu

I am new to kivy and am trying to add a DropDown list and trying to capture the options being clicked in a variable, but I seem to run into many errors while doing so.
This is the error that I am getting:
This is my main.py file:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty
from kivy.vector import Vector
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.uix.menu import MDDropdownMenu
from kivymd.uix.behaviors import RectangularElevationBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivy.config import Config
from kivymd.app import MDApp
from kivy.lang import Builder
Config.set('graphics', 'resizable', False)
class CustomToolbar(ThemableBehavior, RectangularElevationBehavior, MDBoxLayout,):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.md_bg_color = self.theme_cls.primary_color
class MainApp(MDApp):
def __init__(self, **kwargs):
self.VARIABLE = ""
super().__init__(**kwargs)
self.screen = Builder.load_file("main.kv")
menu_items = [{"viewclass": "MDMenuItem","text": "Label1"},{"viewclass":
"MDMenuItem","text": "Label2"}]
self.menu = MDDropdownMenu(
caller=self.screen.ids.drop_item,
items=self.menu_items,
position="center",
width_mult=3,
)
self.menu.bind(on_release=self.menu_callback)
def change_variable(self, value):
print("\nvalue=", value)
self.VARIABLE = value
print("\tself.VARIABLE=", self.VARIABLE)
def menu_callback(self, instance_menu, instance_menu_item):
instance_menu.dismiss()
def build(self):
return self.screen
MainApp().run()
This is my .kv file:
<ScreenManagement>:
id: scr_mngr
MainAppScreen:
name: 'main'
id: main
<CustomToolbar>:
#some widgets here
BoxLayout:
#some widgets here
<MDMenuItem>:
on_release: app.root.change_variable(self.text)
Screen:
MDRaisedButton:
id: drop_item
text: "W"
pos_hint: {"center_x": .9, "center_y": .68}
on_release: app.menu.open()
I am stuck at this for 2 days, please help. Thanks in advance :D
I think your code will work if you build the GUI in the build() method and use Clock.schedule_once() to setup the menu, like this:
class MainApp(MDApp):
def change_variable(self, value):
print("\nvalue=", value)
self.VARIABLE = value
print("\tself.VARIABLE=", self.VARIABLE)
def menu_callback(self, instance_menu, instance_menu_item):
instance_menu.dismiss()
def build(self):
self.VARIABLE = ""
self.screen = Builder.load_file("main.kv")
Clock.schedule_once(self.setup_menu)
return self.screen
def setup_menu(self, dt):
menu_items = [{"viewclass": "MDMenuItem","text": "Label1"},{"viewclass":
"MDMenuItem","text": "Label2"}]
self.menu = MDDropdownMenu(
caller=self.screen.ids.drop_item,
items=menu_items,
position="center",
width_mult=3
)
self.menu.bind(on_release=self.menu_callback)

Kivy Builder.load_file() not working as expected

I am trying to run a small kivy app. When I use Builder.load_string() my app runs okay, when I use Builder.load_file() all I get is a blank screen. Here is my code
main.py - Using Builder.load_string()
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.chip import MDChip
from kivy.uix.screenmanager import ScreenManager, Screen
Builder.load_string("""
<MenuScreen>:
BoxLayout:
Button:
text: 'Goto settings'
on_press:
root.manager.transition.direction = 'left'
root.manager.current = 'settings'
Button:
text: 'Quit'
<SettingsScreen>:
BoxLayout:
Button:
text: 'My settings button'
Button:
text: 'Back to menu'
on_press:
root.manager.transition.direction = 'right'
root.manager.current = 'menu'
""")
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
class MyWidget(MDChip):
def __init__(self, **kwargs):
super().__init__(**kwargs)
class MainApp(MDApp):
def __init__(self, **kwargs):
self.title = "My Material Application"
super().__init__(**kwargs)
def build(self):
return sm
if __name__ == "__main__":
MainApp().run()
Here is the same code with Builder.load_file()
main.py
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.chip import MDChip
from kivy.uix.screenmanager import ScreenManager, Screen
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
class MyWidget(MDChip):
def __init__(self, **kwargs):
super().__init__(**kwargs)
class MainApp(MDApp):
def __init__(self, **kwargs):
self.title = "My Material Application"
super().__init__(**kwargs)
def build(self):
self.root = Builder.load_file('main.kv')
return sm
if __name__ == "__main__":
MainApp().run()
main.kv
<MenuScreen>:
BoxLayout:
Button:
text: 'Goto settings'
on_press:
root.manager.transition.direction = 'left'
root.manager.current = 'settings'
Button:
text: 'Quit'
<SettingsScreen>:
BoxLayout:
Button:
text: 'My settings button'
Button:
text: 'Back to menu'
on_press:
root.manager.transition.direction = 'right'
root.manager.current = 'menu'
All I get when I run the code is a blank screen.
What am I doing wrong?
Apparently you have to include the following code in def build(self)
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
So final main.py file will be
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.chip import MDChip
from kivy.uix.screenmanager import ScreenManager, Screen
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class MyWidget(MDChip):
def __init__(self, **kwargs):
super().__init__(**kwargs)
class MainApp(MDApp):
def __init__(self, **kwargs):
self.title = "My Material Application"
super().__init__(**kwargs)
def build(self):
self.root = Builder.load_file('main.kv')
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
return sm
if __name__ == "__main__":
MainApp().run()

How do you get a list or dict of user-defined properties in a Kivy Widget instance?

I want to create a method that can directly access the user-defined properties of a Kivy Widget Class as a list or dict. (In order to pass them in as an argument into another instance of the same class).
python file:
from kivy.uix.behavior import ButtonBehavior
from kivy.uix.image import Image
from kivy.properties import StringProperty
class ExampleWidget(ButtonBehavior, Image):
name = StringProperty()
hometown = StringProperty()
school = StringProperty()
def clone_self(self):
# Psuedo-Code
args = [v for v in self.user_defined_properties]
clone = ExampleWidget(*args)
return clone
I have tried a number of things, such as dir, var and dict as well using the .get() value as defined in the kivy properties docs all to no avail.
My current set up has two methods, one that returns a manually defined dict of properties, the other which creates and returns the clone using the first method.
Using this, my code is functional however I'm quite sure that a lot of it is unnecessary due to some built in method I'm not entirely sure how to access.
Thank you for reading.
You declare your properties on class level. See this code:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.properties import StringProperty, NumericProperty, Property
Builder.load_string('''
<MyWidget>:
Button:
text: 'Print properties'
on_press: root.print_properties()
''')
class MyWidget(BoxLayout):
name = StringProperty()
hometown = StringProperty()
school = StringProperty()
num = NumericProperty()
def print_properties(self):
for k, v in vars(self.__class__).items():
if isinstance(v, Property):
print(k)
class TestApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
TestApp().run()
Compare it with
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.properties import StringProperty, NumericProperty, Property
Builder.load_string('''
<MyWidget>:
Button:
text: 'Print properties'
on_press: root.print_properties()
''')
class MyWidget(BoxLayout):
def __init__(self, *args):
super(BoxLayout, self).__init__(*args)
self.name = StringProperty()
self.hometown = StringProperty()
self.school = StringProperty()
self.num = NumericProperty()
def print_properties(self):
for k, v in vars(self).items():
if isinstance(v, Property):
print(k)
class TestApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
TestApp().run()
EDIT: As I've just discovered, there's also properties() method that should be used instead of vars():
for k, v in self.properties().items():
print(type(v))

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