I have the following Kivy app and I'm trying to change the text of a Label based on another widget's variable.
I mean, if the variable testing of the class TestApp changes, I want also the value of the variable text of the class TestLabel to change.
To do so, I've created a BooleanProperty in the TestLabel class that points to the testing variable of the TestApp class. The problem is that this callback is never executed despite being changing it each time I press the button.
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.properties import BooleanProperty
Builder.load_string('''
<MainApp>:
orientation: 'horizontal'
rows: 2
TestButton:
text: 'Change value'
on_release: self.change_value()
TestLabel:
''')
class TestLabel(Label):
testing = BooleanProperty()
def __init__(self, **kwargs):
super(TestLabel, self).__init__(**kwargs)
self.app = App.get_running_app()
self.testing = self.app.testing
self.bind(testing=self.changed_value)
def changed_value(self, _instance, newvalue):
self.text = str(newvalue)
class TestButton(Button):
def __init__(self, **kwargs):
super(TestButton, self).__init__(**kwargs)
self.app = App.get_running_app()
def change_value(self):
self.app.testing = not self.app.testing
class MainApp(BoxLayout):
pass
class TestApp(App):
testing = BooleanProperty(False)
def build(self):
return MainApp()
if __name__ == '__main__':
TestApp().run()
It is not necessary to create a testing property in TestLabel since when you do: self.bind(testing = self.changed_value) you are connecting the testing of TestLabel and not the testing of TestApp , so as it never changes testing after the bind then it never gets call the callback.
The bind has to be done using the object that has that property, and in your case the testing belongs to the App, so you must the App.
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.properties import BooleanProperty
Builder.load_string('''
<MainApp>:
orientation: 'horizontal'
rows: 2
TestButton:
text: 'Change value'
on_release: self.change_value()
TestLabel:
''')
class TestLabel(Label):
def __init__(self, **kwargs):
super(TestLabel, self).__init__(**kwargs)
self.app = App.get_running_app()
self.app.bind(testing=self.changed_value)
def changed_value(self, _instance, newvalue):
self.text = str(newvalue)
class TestButton(Button):
def __init__(self, **kwargs):
super(TestButton, self).__init__(**kwargs)
self.app = App.get_running_app()
def change_value(self):
self.app.testing = not self.app.testing
class MainApp(BoxLayout):
pass
class TestApp(App):
testing = BooleanProperty(False)
def build(self):
return MainApp()
if __name__ == '__main__':
TestApp().run()
Related
EDIT: so this is a very simple version of what it is like in my app but i think you get the point. Basically i want to destroy the Buttons created in the for loop with the Button on the destroywidgets screen.
.kv:
MainWindow:
<MainWindow>
FloatLayout:
size_hint: 1, .1
Button:
text:"next screen"
size_hint:.1,1
pos_hint:{"x": 0, "y": 0}
on_release: app.root.current = "destroywidgets"
Button:
text:"laodwidgets"
on_release: root.create_widgets()
size_hint:.1, 1
pos_hint:{"x": .5, "y": 0}
.py:
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.scrollview import ScrollView
class MainWindow(Screen):
name = "mainwindow"
def __init__(self, **kwargs):
super(MainWindow, self).__init__()
self.scrl_view_1 = ScrollView(
size_hint_y=.85,
pos_hint={"x": 0, "y": .15},
do_scroll_x=False,
do_scroll_y=True,
size_hint_x=1
)
self.scrl_child_1 = GridLayout(
size_hint_x=1,
size_hint_y=None,
cols=2,
height=1000,
row_default_height=150,
row_force_default=True
)
self.add_widget(self.scrl_view_1)
self.scrl_view_1.add_widget(self.scrl_child_1)
def create_widgets(self):
print("creating widgets")
for i in range(0, 6):
btn = Button(
text=str(i)
)
self.scrl_child_1.add_widget(btn)
print("added")
class DestroyWidgets(Screen):
name = "destroywidgets"
def __init__(self, **kwargs):
super(DestroyWidgets, self).__init__()
btn_destroy_widgets = Button(
text="Destroy children of Mainwindow",
#some on release function to clear all children from scrl_child_1 in Mainwindow
)
self.add_widget(btn_destroy_widgets)
class ShoppingList(App):
def build(self):
self.sm = ScreenManager()
self.sm.add_widget(MainWindow(name="mainwindow"))
self.sm.add_widget(DestroyWidgets(name="destroywidgets"))
return self.sm
if __name__ == "__main__":
main_app = ShoppingList()
main_app.run()
So I have to reference a GridLayout which is inside of a ScrollView created in class A in class B. Since you cannot give Layouts an ID in python code and reference it with self.ids i can't figure out how to do it. I tried suggestions from another post with the weakref.ref method as example but i couldn't get it to work. The whole point is that i have to destroy all children of Layouts from other classes in a function somehow.
Heres just a little snippet of my code which i think will be enough. If you need more just write me. Thanks for all the help in advance!
class SelfMadePlans(Screen):
name = "selfmadeplans"
def __init__(self, **kwargs):
super(SelfMadePlans, self).__init__()
self.scrl_view_2 = ScrollView(
size_hint_y=.85,
pos_hint={"x": 0, "y": 0},
do_scroll_x=False,
do_scroll_y=True,
size_hint_x=1
)
self.scrl_child_2 = GridLayout(
size_hint_x=1,
size_hint_y=None,
cols=3,
height=20000,
row_default_height=150,
row_force_default=True,
)
self.add_widget(self.scrl_view_2)
self.scrl_view_2.add_widget(self.scrl_child_2)
and then something in another class like:
class B:
def destroy_children(self):
MDApp.get_running_app().sm.get_screen("selfmadeplans").ids.scrl_child_2.children.clear()
First of all you must pass var. no. of kwargs in __init__ in order to use and get all the default functionalities.
...
def __init__(self, **kwargs):
super(MainWindow, self).__init__(**kwargs)
...
etc.
Next to access certain screen from ScreenManager you can use method get_screen as follows,
...
btn_destroy_widgets = Button(
text="Destroy children of Mainwindow",
#some on release function to clear all children from scrl_child_1 in Mainwindow
)
btn_destroy_widgets.bind(on_release = self.destroy_widgets_on_main)
self.add_widget(btn_destroy_widgets)
def destroy_widgets_on_main(self, *args):
main_window = self.manager.get_screen("mainwindow")
main_window.scrl_child_1.clear_widgets()
How to resolve widget overlapping in kivy framework, I am adding widgets in for cycle so I have more than one widget but all of them comes to be in one place, how can I prevent this?
My python code:
from kivy.app import App
from kivymd.app import MDApp
from kivy.uix.label import Label
from kivy.clock import Clock
from kivy.graphics import Color
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivymd.uix.card import MDCard
from kivy.properties import StringProperty
from kivy.lang import Builder
import requests, time, collections
class request_sender():
return 'DATA'
class CustomLabel(Label):
pass
class CustomBox(BoxLayout):
pass
class AuctionCard(Widget):
auc_timer = ''
auc_img = ''
auc_name = ''
def __init__(self, **kwargs):
super(AuctionCard, self).__init__(**kwargs)
with self.canvas.before: Color(1, 0, .4, mode='rgb')
Clock.schedule_once(self.load_info)
def load_info(self, dt):
print(self.auc_name)
self.size_hint = None, None
box = BoxLayout(orientation='vertical', size = (800, 600))
box.size_hint_x = 50;
box.height = 100
AuctionName = CustomLabel(text=self.auc_name, pos_hint={'top': 300.9})
AuctionImage = CustomLabel(text=self.auc_img)
AuctionTimer = CustomLabel(text=self.auc_name)
box.add_widget(AuctionName)
box.add_widget(AuctionTimer)
box.add_widget(AuctionImage)
self.add_widget(box)
class MyWidget(Widget):
prop = StringProperty('')
array_of_labels = []
prop1 = StringProperty('Second card')
n = 0
all_cards = collections.defaultdict()
def __init__(self, **kwargs):
super(MyWidget, self).__init__(**kwargs)
self.screen_load()
def timer_le(self, dt):
returned_data = request_sender().sender_update('ajax.php')
for key in self.all_cards:
for data in returned_data:
if data['pid'] == key:
self.all_cards[key][0].text = str(data['remaining_time'])
def screen_load(self):
returned_data = request_sender().sender_update('ajax.php')
box = GridLayout(cols=2)
self.size_hint = None, None
for data in returned_data:
AucCard = AuctionCard()
AucCard.auc_name = str(data['auc_name'])+'\n\n'
AucCard.auc_timer = str(data['remaining_time'])+'\n\n'
AucCard.auc_img = str(data['auc_img'])+'\n\n'
box.add_widget(AucCard)
print('Widget added')
self.add_widget(box)
#self.all_cards[str(data['pid'])] = [AucCard]
#Clock.schedule_interval(self.timer_le, 1/30)
class TestApp(App):
def build(self):
box = GridLayout(cols=2)
return MyWidget()
TestApp().run()
My kv code:
<CustomLabel>:
size_hint_y: None
text_size: self.width, None
height: self.texture_size[1]
Result:
Kind of result that I want to create:
Actually I found easy way to solve this issue. The problem was in parent class of my AuctionCard and MyWidget classes, I set parent class to Widget but for the AuctionCard it should be BoxLayout and for MyWidget GridLayout. So from there I managed to set cols = 2 and size to window.size. From here it works exactly how it should work.
from kivy.lang import Builder
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.clock import Clock
from kivy.uix.gridlayout import GridLayout
class AuctionCard(BoxLayout):
auc_timer = ''
auc_img = ''
auc_name = ''
def __init__(self, **kwargs):
super(AuctionCard, self).__init__(**kwargs)
Clock.schedule_once(self.load_info)
def load_info(self, dt):
self.orientation = 'vertical'
AuctionName = Label(text=self.auc_name)
AuctionImage = Label(text=self.auc_img)
AuctionTimer = Label(text=self.auc_timer)
self.add_widget(AuctionName)
self.add_widget(AuctionTimer)
self.add_widget(AuctionImage)
class MyWidget(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 2
self.size = (Window.size[0], self.size[1])
self.load_app()
print('MyWidget size: '+str(self.size))
def load_app(self):
self.size_hint = None, None
returned_data = [{'auc_name':'name 1', 'remaining_time':'100', 'auc_img':'https://img.src'},
{'auc_name':'name 2', 'remaining_time':'200', 'auc_img':'https://img.src'},
{'auc_name':'name 3', 'remaining_time':'300', 'auc_img':'https://img.src'}]
for data in returned_data:
AucCard = AuctionCard()
AucCard.auc_name = str(data['auc_name'])+'\n\n'
AucCard.auc_timer = str(data['remaining_time'])+'\n\n'
AucCard.auc_img = str(data['auc_img'])+'\n\n'
self.add_widget(AucCard)
print('Widget added')
class MyTestApp(App):
def __init__(self, **kwargs):
self.title = "My Material Application"
super().__init__(**kwargs)
def build(self):
return MyWidget()
if __name__ == "__main__":
MyTestApp().run()
Result:
Result
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()
I'm trying to bind the following function to a Button in Kivy.
def auth(self):
print(self.username)
if self.username == "Hendricko":
print("self.username == Hendricko")
popup = Popup(title="success",
content=Label(text="Howdy !"),
size=(100, 100),
size_hint=(0.3, 0.3),
auto_dismiss=False)
popup.open()
I've tried
class Foo():
def initUI(self):
self.add_widget(Button(text="Auth User and Password", on_press=self.auth))
but this doesn't work. What am I doing wrong?
here is my whole code
from kivy.uix.popup import Popup
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.stacklayout import StackLayout
class LoginScreen(GridLayout):
def __init__(self, **kwargs):
super(LoginScreen, self).__init__(**kwargs)
self.cols = 2
self.row = 2
self.add_widget(Label(text='User Name'))
self.username = TextInput(multiline=False)
self.add_widget(self.username)
self.add_widget(Label(text='password'))
self.password = TextInput(password=True, multiline=False)
self.add_widget(self.password)
self.hello = Button(text="hello", on_press=self.auth)
self.add_widget(self.hello)
def auth(self):
if self.username == "Hendricko":
popup = Popup(title="success",
content=Label(text="Howdy !"),
size=(100, 100),
size_hint=(0.3, 0.3),
auto_dismiss=False)
popup.open()
class MyApp(App):
def build(self):
return LoginScreen()
if __name__ == '__main__':
MyApp().run()
I don't think any of the answers are very clear. Neither explains that problem is that the callback given to on_press gets called with a parameter, the instance of button, so LoginScreen.auth must accept a parameter after the self:
def auth(self, button):
print('button pressed:', instance)
The problem is not that on_press must be given via Button.bind or that the callback must be a function, it can be a bound method, and the docs cited by other answer and by comments link to ButtonbBhavior which indicates that OP use of on_press in constructor was fine:
self.hello = Button(text="hello", on_press=self.auth)
would have worked if auth had been as described above.
If you read the Button documentation, the key seems to be to use the bind function:
def callback(instance):
print('The button <%s> is being pressed' % instance.text)
btn1 = Button(text='Hello world 1')
btn1.bind(on_press=callback)
Replace line
self.hello = Button(text="hello", on_press=lambda a:self.auth())
of your code and use this :
self.hello = Button(text="hello", on_press=lambda a:self.auth())
Also add below line in auth function to see if its called :)
print "auth called"
and There are many ways to perform a particular task .Above code will be to fix your code in minimum effort , However If you would like to do it in another way . Just use code below .
from kivy.uix.popup import Popup
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.stacklayout import StackLayout
class LoginScreen(GridLayout):
def __init__(self, **kwargs):
super(LoginScreen, self).__init__(**kwargs)
self.cols = 2
self.row = 2
self.add_widget(Label(text='User Name'))
self.username = TextInput(multiline=False)
self.add_widget(self.username)
self.add_widget(Label(text='password'))
self.password = TextInput(password=True, multiline=False)
self.add_widget(self.password)
self.hello = Button(text="hello")
self.hello.bind(on_press=self.auth)
self.add_widget(self.hello)
def auth(self,instance):
print "auth called"
if self.username == "Hendricko":
popup = Popup(title="success",
content=Label(text="Howdy !"),
size=(100, 100),
size_hint=(0.3, 0.3),
auto_dismiss=False)
popup.open()
class MyApp(App):
def build(self):
return LoginScreen()
if __name__ == '__main__':
MyApp().run()
I report an example of buttons created dynamically within the main class, and then triggered into a single listener:
class allMyApp(TabbedPanel):
def __init__(self, **kwargs):
super(allMyApp, self).__init__(**kwargs)
#[...]
for y in sorted(set(emissionYears)):
btn = Button(text = y,
size_hint_y = None,
height = '48dp',
on_release = lambda btn: self.ids.choseEmissionDate.select(btn.text))
btn.bind(on_press = self.sortByYear)
self.ids.choseEmissionDate.add_widget(btn)
def sortByYear(self, instance):
year = instance.text
print(year)