Object has no attribute 'root' - python

I am following a tutorial https://www.youtube.com/watch?v=ceMVwnKCtOU and almost copied his code, but I´ve remade it a bit but I get this error:
File "C:\Users\Daniel\PycharmProjects\stopwatch_timer\main.py", line 15, in stopwatch
self.root.ids.counter.text = str(int(self.root.ids.counter.text) + 1)
AttributeError: 'Stopwatch' object has no attribute 'root'
My main file:
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
class Stopwatch(BoxLayout):
def start_stopwatch(self):
Clock.schedule_interval(self.stopwatch, 1)
def stopwatch(self, *args):
self.root.ids.counter.text = str(int(self.root.ids.counter.text) + 1)
class TimerApp(App):
pass
TimerApp().run()
My kivy file:
Stopwatch:
<Stopwatch>:
Button:
text: "Start Stopwatch"
on_press: root.start_stopwatch()
Label:
id: counter
text: "0"
What am I doing wrong?

You said "almost copied" but code in video is different.
You have code in Stopwatch but video has in MainApp - and this makes all problem.
MainApp needs self.root to access elements in GridLayout but GridLayout would need only self to access elements in GridLayout
The same with your code: TimerApp would need self.root to access elements in Stopwatch but Stopwatch needs only self to access elements in Stopwatch
If you use self.ids instead of self.root.ids then it will work
class Stopwatch(BoxLayout):
def start_stopwatch(self):
Clock.schedule_interval(self.update_watch, 1)
def update_watch(self, *args):
self.ids.counter.text = str(int(self.ids.counter.text) + 1)
class TimerApp(App):
pass
BTW:
If you would move Clock.schedule_interval to TimerApp then it would need self.root.stopwatch instead of self.stopwatch to run Stopwatch.stopwatch
class Stopwatch(BoxLayout):
def update_watch(self, *args):
self.ids.counter.text = str(int(self.ids.counter.text) + 1)
class TimerApp(App):
def start_stopwatch(self):
Clock.schedule_interval(self.root.update_watch, 1)
and timer.kv would need app instead of root
on_press: app.start_stopwatch()
EDIT:
Full version with self.root
main.py
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
class Stopwatch(BoxLayout):
def update_watch(self, *args):
self.ids.counter.text = str(int(self.ids.counter.text) + 1)
class TimerApp(App):
job = None # to keep access to running Clock
def start_stop_watch(self, *args):
if not self.job:
self.root.ids.counter.text = "0"
self.job = Clock.schedule_interval(self.root.update_watch, 1)
self.root.ids.button.text = "Stop"
else:
self.job.cancel() # stop clock
self.job = None # to use again in `if not self.job:`
self.root.ids.button.text = "Start"
TimerApp().run()
timer.kv
Stopwatch:
<Stopwatch>:
Button:
id: button
text: "Start"
on_press: app.start_stop_watch()
Label:
id: counter
text: "0"
Full version without self.root
main.py
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
class Stopwatch(BoxLayout):
job = None
def start_stop_watch(self, *args):
if not self.job:
self.ids.counter.text = "0"
self.job = Clock.schedule_interval(self.update_watch, 1)
self.ids.button.text = "Stop"
else:
self.job.cancel()
self.job = None
self.ids.button.text = "Start"
def update_watch(self, *args):
self.ids.counter.text = str(int(self.ids.counter.text) + 1)
class TimerApp(App):
pass
TimerApp().run()
timer.kv
Stopwatch:
<Stopwatch>:
Button:
id: button
text: "Start"
on_press: root.start_stop_watch()
Label:
id: counter
text: "0"
Full version with all code in TimerApp
main.py
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
class Stopwatch(BoxLayout):
pass
class TimerApp(App):
job = None
def start_stop_watch(self, *args):
if not self.job:
self.root.ids.counter.text = "0"
self.job = Clock.schedule_interval(self.update_watch, 1) # without `root`
self.root.ids.button.text = "Stop"
else:
self.job.cancel()
self.job = None
self.root.ids.button.text = "Start"
def update_watch(self, *args):
self.root.ids.counter.text = str(int(self.root.ids.counter.text) + 1)
TimerApp().run()
timer.kv
Stopwatch:
<Stopwatch>:
Button:
id: button
text: "Start"
on_press: app.start_stop_watch()
Label:
id: counter
text: "0"

Related

Adding labels for each OneLineAvatarListItem Python Kivy

Im trying to add labels for each item in OneLineAvatarListItem, but item adding only for last OneLineListItem, can i do it using python only?
My code:
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.image import Image
from kivy.uix.scrollview import ScrollView
from kivy.core.window import Window
from kivymd.uix.screen import Screen
from kivymd.uix.list import MDList, OneLineAvatarListItem
class MyApp(MDApp):
def build(self):
Window.clearcolor = (100, 100, 100, 1)
window = BoxLayout()
screen = Screen()
scroll = ScrollView()
list_view = MDList()
scroll.add_widget(list_view)
for i in range(10):
items = OneLineAvatarListItem(text=str(i))
label = Label(text='www', color=[.1, .1, .1, 1])
items.add_widget(label)
list_view.add_widget(items)
screen.add_widget(scroll)
window.add_widget(screen)
return window
MyApp().run()
Im getting that
Using example from documentation - CustomItem - I created custom ListItem with label which use ILeftBody to display on left side of standard text.
from kivymd.app import MDApp
from kivymd.uix.list import OneLineAvatarListItem, ILeftBody
from kivymd.uix.label import MDLabel
from kivy.lang import Builder
KV = '''
<MyItemList>:
LeftLabel:
id: left_label
BoxLayout:
ScrollView:
MDList:
id: scroll
'''
class MyItemList(OneLineAvatarListItem):
'''Custom list item.'''
class LeftLabel(ILeftBody, MDLabel):
'''Custom left container.'''
class MainApp(MDApp):
def build(self):
return Builder.load_string(KV)
def on_start(self):
for i in range(30):
item = MyItemList(text=f"Item {i}", on_release=self.on_click_item)
#print(item.ids)
item.ids.left_label.text = str(i)
self.root.ids.scroll.add_widget(item)
def on_click_item(self, widget):
print('text:', widget.text, 'left_label.text:', widget.ids.left_label.text)
MainApp().run()
Result:
I tried to add other label with IRightBody but it didn't work for me with OneLineAvatarListItem but work with OneLineAvatarIconListItem (with Icon in name).
from kivymd.app import MDApp
from kivymd.uix.list import OneLineAvatarIconListItem, ILeftBody, IRightBody
from kivymd.uix.label import MDLabel
from kivy.lang import Builder
KV = '''
<MyItemList>:
LeftLabel:
id: left_label
RightLabel:
id: right_label
BoxLayout:
ScrollView:
MDList:
id: scroll
'''
class MyItemList(OneLineAvatarIconListItem):
'''Custom list item.'''
class LeftLabel(ILeftBody, MDLabel):
'''Custom left container.'''
class RightLabel(IRightBody, MDLabel):
'''Custom right container.'''
class MainApp(MDApp):
def build(self):
return Builder.load_string(KV)
def on_start(self):
for i in range(30):
item = MyItemList(text=f"Item {i}", on_release=self.on_click_item)
#print(item.ids)
item.ids.left_label.text = str(i)
item.ids.right_label.text = str(100+i)
self.root.ids.scroll.add_widget(item)
def on_click_item(self, widget):
print('text:', widget.text, 'left_label.text:', widget.ids.left_label.text, 'right_label.text:', widget.ids.right_label.text)
MainApp().run()
Result:
For Label you can use ILeftBody/IRightBody without Touch and it will run function assigned to ListItem. But if you want to add Button, CheckButton and assign function to this widget then it may need ILeftBodyTouch/IRightBodyTouch without Touch
from kivymd.app import MDApp
from kivymd.uix.list import OneLineAvatarIconListItem, ILeftBody, IRightBodyTouch
from kivymd.uix.label import MDLabel
from kivymd.uix.button import MDTextButton
from kivy.lang import Builder
KV = '''
<MyItemList>:
LeftLabel:
id: left_label
RightButton:
id: right_button
# on_release: app.on_click_right_button(self)
BoxLayout:
ScrollView:
MDList:
id: scroll
'''
class MyItemList(OneLineAvatarIconListItem):
'''Custom list item.'''
class LeftLabel(ILeftBody, MDLabel):
'''Custom left container.'''
class RightButton(IRightBodyTouch, MDTextButton):
'''Custom right container.'''
class MainApp(MDApp):
def build(self):
return Builder.load_string(KV)
def on_start(self):
for i in range(30):
item = MyItemList(text=f'Item {i}', on_release=self.on_click_item)
#print(item.ids)
item.ids.left_label.text = str(i)
item.ids.right_button.text = f'More {i}'
item.ids.right_button.on_release = lambda widget=item.ids.right_button:self.on_click_right_button(widget) # it needs `widget=...` because created in `for`-loop
self.root.ids.scroll.add_widget(item)
def on_click_item(self, widget):
print('--- on_click_item ---')
print('wdiget.text:', widget.text, 'left_label.text:', widget.ids.left_label.text, 'right_button.text:', widget.ids.right_button.text)
def on_click_right_button(self, widget):
print('--- on_click_right_button ---')
print('wdiget.text:', widget.text)
print('widget.parent.parent:', widget.parent.parent)
print('widget.parent.parent.text:', widget.parent.parent.text)
MainApp().run()

Lost focus by Enter Key detection (Python-Kivy)

I want to enhance usability of my Python-Kivy program. In the following program example, I want to allow users to operate it even if they don't have mouse. (by keyboard input.)
(1) In the first dialog (MyLayout2), user input in Textbox easily as the textbox has focus
(2) Hit Enter keyboard key to go to next dialog (MyLayout1)
(3) Hit Enter keyboard key to go to (1) (MyLayout2 again)
However in the 2nd (1), after (3), the focus of Textbox is lost. Any idea how to cope with this problem?
Main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.textinput import TextInput
from kivy.core.window import Window
sm = ScreenManager()
class MyLayout1(BoxLayout):
pass
class MyLayout2(BoxLayout):
pass
class MyScreen(Screen):
dialog_number = 0
def __init__(self, **kwargs):
super(MyScreen, self).__init__(**kwargs)
self.gridlayout = None
self.gridlayout = MyLayout2()
self.add_widget(self.gridlayout)
Window.bind(on_key_down=self._keydown)
def _keydown(self,*args):
if (args[2] == 40):
if self.dialog_number == 0:
self.button2_clicked()
elif self.dialog_number == 1:
self.button1_clicked()
def _create_layout(self):
if self.gridlayout is not None:
self.remove_widget(self.gridlayout)
self.add_widget(self.gridlayout)
def button1_clicked(self, *args):
if self.gridlayout is not None:
self.remove_widget(self.gridlayout)
self.gridlayout = MyLayout2()
self.add_widget(self.gridlayout)
self.dialog_number = 0
def button2_clicked(self, *args):
if self.gridlayout is not None:
self.remove_widget(self.gridlayout)
self.gridlayout = MyLayout1()
self.add_widget(self.gridlayout)
self.dialog_number = 1
def find_instance(self, layout):
c = None
for c in list(self.children):
if isinstance(c, layout):
break
return c
class myApp(App):
def build(self):
self.anschoi = MyScreen(name = 'anschoi')
sm.add_widget(self.anschoi)
sm.current = 'anschoi'
return sm
if __name__ == '__main__':
myApp().run()
my.kv
<MyScreen>:
BoxLayout:
orientation: 'vertical'
padding: 10,40,10,40
spacing: 40
<MyLayout1>:
Button1:
id: btn1
text: 'OK or ENTER key'
on_release: root.parent.button1_clicked()
<MyLayout2>:
txtinput: txtinput
orientation: 'vertical'
TextInput:
id: txtinput
text: ''
multiline: False
focus: True
button2:
id:Button2
text: 'OK or ENTER key'
on_release: root.parent.button2_clicked()
<Button0#Button>:
<Button1#Button>:
<button2#Button>:
This is exactly the same problem as your previous question. Just change:
Window.bind(on_key_down=self._keydown)
to:
Window.bind(on_key_up=self._keydown)
Again, to avoid the on_key_up event (that almost always follows a on_key_down event) stealing the focus from your TextInput.

TypeError: super(type, obj) when using exec()

Below is the whole (test) app written with Kivy.
This is something like app preview application:
user enters a text of kv markup (see variable self.kv) and a text of classes (see variable self.text). Then he clicks the "preview" button and sees the result on the right side of the application.
Loading kv is implemented using kivy Builder.load_string(). Class loading is implemented using exec(, globals()).
The main problem is that for some reason I get the following error when I click on the preview button for the third time (the first 2 clicks work without errors):
TypeError: super(type, obj): obj must be an instance or subtype of type
The error can be because of exec(), (without exec I don’t get this error).
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
KV = '''
BoxLayout:
BoxLayout:
orientation: 'vertical'
CodeEd
id: code_editor
Button:
text: 'Preview'
on_release: app.preview()
Preview:
id: preview_area
<CodeEd#TextInput>
text: app.text
<Preview#RelativeLayout>
'''
class MyApp(App):
def build(self):
self._kv_filename = 'KvEditor_internal.' + str(self.uid)
self.text = '''
class MyButton(Button):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
print (333)
super(MyButton, self).on_touch_down(touch)
'''
self.kv = 'MyButton'
self.root = Builder.load_string(KV)
def preview(self):
preview_area = self.root.ids.preview_area
#if 'MyButton' in globals():
# del globals()['MyButton']
#print ('===================')
#print ([i for i in dict(globals())])
try:
exec(self.text, globals())
except:
print ('some error when exec class ')
Builder.unload_file(self._kv_filename)
try:
preview_area.add_widget(Builder.load_string(self.kv, filename=self._kv_filename))
except Exception as e:
print (e.message if getattr(e, r"message", None) else str(e))
MyApp().run()
How to solve this problem?
Question - Preview area is RelativeLayout
It seems to work, but could you edit or add an example, please? I do
not need the number of buttons in the preview area increased. I just
want every time after pressing "Preview", in the preview area I have
content that just reflects the current text of self.kv and self.text.
Example
The following example has most of the enhancements applied and the Preview area is a RelativeLayout.
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.properties import NumericProperty, StringProperty
from kivy.factory import Factory
KV = '''
BoxLayout:
BoxLayout:
orientation: 'vertical'
CodeEd
id: code_editor
Button:
text: 'Preview'
on_release: app.preview()
Preview:
id: preview_area
<CodeEd#TextInput>
text: app.text
<Preview#RelativeLayout>
'''
class MyApp(App):
text = StringProperty('')
previous_text = StringProperty('')
def build(self):
self._kv_filename = 'KvEditor_internal.' + str(self.uid)
self.text = '''
class MyButton(Button):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
print (333)
return super(Button, self).on_touch_down(touch)
'''
self.previous_text = self.text
self.kv = 'MyButton'
self.root = Builder.load_string(KV)
def preview(self):
preview_area = self.root.ids.preview_area
self.text = self.root.ids.code_editor.text
try:
# Class loading is implemented using exec(, globals())
exec(self.text, globals())
except:
print('some error when exec class ')
Builder.unload_file(self._kv_filename)
try:
# check for code changes
if self.text != self.previous_text:
Factory.unregister(self.kv)
Factory.register(self.kv, cls=globals()[self.kv])
total_children = len(preview_area.children)
preview_area.clear_widgets()
for child in range(total_children):
preview_area.add_widget(Builder.load_string(self.kv, filename=self._kv_filename))
self.previous_text = self.text
preview_area.add_widget(Builder.load_string(self.kv, filename=self._kv_filename))
except Exception as e:
print(e.message if getattr(e, r"message", None) else str(e))
MyApp().run()
Output
Question 1 - TypeError
TypeError: super(type, obj): obj must be an instance or subtype of
type
Solution - TypeError
There are two solutions to the TypeError.
Method 1
Replace super(MyButton, self).on_touch_down(touch) with return False
Method 2
Replace super(MyButton, self).on_touch_down(touch) with return super(Button, self).on_touch_down(touch)
Question 2 - Support code changes to MyButton class
What if MyButton class exists, but I want to make changes to this
class, for example change some of its methods, and so on?
Solution - Support code changes to MyButton class
In order to support code changes to MyButton class, the following enhancements are required:
Add import statement for Kivy Properties, from kivy.properties import NumericProperty, StringProperty
Add import statement for Kivy Factory object, from kivy.factory import Factory
Add a new class attribute, previous_text of type, StringProperty to keep track of code changes.
Initialize self.previous_text
Update self.text when method preview() is invoked.
Check for code changes
Unregister previous class MyButton using Kivy Factory
Register new class MyButton using Kivy Factory
Save the total number of MyButton added
Remove previously added MyButton using clear_widgets(). If previously added MyButtons are not removed, it will not have the functionality of the new features/code changes.
Use for loop to re-add previously added MyButton with the new functionality / features.
Assign new code changes, self.text to self.previous_text
Snippets
from kivy.properties import NumericProperty, StringProperty
from kivy.factory import Factory
from kivy.logger import Logger
...
class MyApp(App):
i = NumericProperty(0)
text = StringProperty('')
previous_text = StringProperty('')
def build(self):
...
self.text = '''
...
return True # consumed on_touch_down & don't propagate
# return False
return super(Button, self).on_touch_down(touch)
'''
self.previous_text = self.text
...
def preview(self):
preview_area = self.root.ids.preview_area
self.text = self.root.ids.code_editor.text
...
try:
# check for code changes
if self.text != self.previous_text:
Factory.unregister(self.kv)
Factory.register(self.kv, cls=globals()[self.kv])
total_children = len(preview_area.children)
preview_area.clear_widgets()
for child in range(total_children):
btn = Builder.load_string(self.kv, filename=self._kv_filename)
btn.text = str(child + 1)
preview_area.add_widget(btn)
self.previous_text = self.text
Example
The following example illustrates a code editor supporting code changes to MyButton class, and MyButton widgets are added into a GridLayout.
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import NumericProperty, StringProperty
from kivy.uix.button import Button
from kivy.logger import Logger
from kivy.factory import Factory
KV = '''
BoxLayout:
BoxLayout:
orientation: 'vertical'
CodeEd
id: code_editor
Button:
text: 'Preview'
on_release: app.preview()
Preview:
id: preview_area
<CodeEd#TextInput>:
text: app.text
<Preview#GridLayout>:
cols: 3
'''
class MyApp(App):
i = NumericProperty(0)
text = StringProperty('')
previous_text = StringProperty('')
def build(self):
self._kv_filename = 'KvEditor_internal.' + str(self.uid)
self.text = '''
class MyButton(Button):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
print(f"touch.pos={touch.pos}")
print(f"Button.text={self.text}")
return True # consumed on_touch_down & don't propagate
# return False
return super(Button, self).on_touch_down(touch)
'''
self.previous_text = self.text
self.kv = 'MyButton'
self.root = Builder.load_string(KV)
def preview(self):
preview_area = self.root.ids.preview_area
self.text = self.root.ids.code_editor.text
try:
# Class loading is implemented using exec(, globals())
exec(self.text, globals())
except Exception as msg:
print('\nException: some error when exec class ')
Logger.error("KivyApp: Exception: some error when exec class")
print(msg)
quit()
Builder.unload_file(self._kv_filename)
try:
# check for code changes
if self.text != self.previous_text:
Factory.unregister(self.kv)
Factory.register(self.kv, cls=globals()['MyButton'])
total_children = len(preview_area.children)
preview_area.clear_widgets()
for child in range(total_children):
btn = Builder.load_string(self.kv, filename=self._kv_filename)
btn.text = str(child + 1)
preview_area.add_widget(btn)
self.previous_text = self.text
self.i += 1
btn = Builder.load_string(self.kv, filename=self._kv_filename)
btn.text = str(self.i)
preview_area.add_widget(btn)
except Exception as e:
print(e.message if getattr(e, r"message", None) else str(e))
MyApp().run()
Output

Python Kivy: bind a command inside popup executes command

I want to have a popup for confirming that the user really wants to quit the app. Now when I try to bind commands to the two buttons, I can only add the dismiss directly inside the function, not via a callback. That may be ok.
But I can only call my closing routine through a callback, not inside the function. When I bind quit_app() inside this function it gets directly executed when opening the popup. Why? It just should bind, not execute.
(Old script deleted.)
I have updated my script a bit and included a minimum kv file. It works basically (like previously) but looks a bit odd.
UI-Test.py:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
# Kivy imports:
import kivy
from kivy.app import App
from kivy.uix import popup
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty, ReferenceListProperty, ObjectProperty
from kivy.uix.tabbedpanel import TabbedPanel
VersionString = "DRAFT"
AppName = 'UI-Test'
def CloseProgram(Message, Level):
print('Closing, level {} ({})'.format(Level, Message))
sys.exit()
def OnClosing(self):
print('Closing...')
# ToDo: call popup
def init():
print('Starting {} Version {}.'.format(AppName, VersionString))
print('You are using Python version: {}'.format(sys.version))
class TestApp(App):
title = AppName + ' ' + VersionString
def on_pause(self):
return True
def quit_app(self,btn):
CloseProgram('Normal Closing', 'Debug')
class Pop(BoxLayout):
def __init__(self, **kwargs):
super(Pop, self).__init__(**kwargs)
self.up()
def callback(instance):
if instance.id == 'quit':
TestApp.quit_app(TestApp, 1)
def up(self):
print('popup')
qbutton = Button(text='Quit', id='quit')
abutton = Button(text='Return to Program', id='return')
blayout = BoxLayout()
blayout.add_widget(qbutton)
blayout.add_widget(abutton)
self.popup = kivy.uix.popup.Popup(title='Quit Program?', content=blayout, size_hint=(None, None), size=(400, 400))
abutton.bind(on_release=self.popup.dismiss)
qbutton.bind(on_release=TestApp.Pop.callback)
self.popup.open()
if __name__ == '__main__':
init()
TestApp().run()
Test.kv:
#:kivy 1.9
<Button>:
font_size: 15
# Main Layout:
BoxLayout:
orientation: 'vertical'
Button:
text: "Quit"
id: "quit_button"
size_hint: (0.1, None)
size: (150, 50)
on_release: app.Pop.up(self)
Question
How would you call this popup from a kv file? When in my version (see
updated script) Pop is not part of TestApp I can't access it from kv
file
Solution - with kv file
kv file
Add import statement, #:import Factory kivy.factory.Factory
Define class rule, <Pop>: and add widgets.
Register, instantiate, and open class Pop() using Factory.Pop().open()
Factory object
The factory can be used to automatically register any class or module
and instantiate classes from it anywhere in your project.
Python Code
Use App.get_running_app() to get an instance of the class TestApp()
Example - with kv file
main.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
# Kivy imports:
import kivy
kivy.require('1.11.0')
from kivy.app import App
from kivy.uix.popup import Popup
VersionString = "DRAFT"
AppName = 'UI-Test'
def CloseProgram(Message, Level):
print('Closing, level {} ({})'.format(Level, Message))
sys.exit()
def OnClosing(self):
print('Closing...')
# ToDo: call popup
def init():
print('Starting {} Version {}.'.format(AppName, VersionString))
print('You are using Python version: {}'.format(sys.version))
class Pop(Popup):
def callback(self, instance):
App.get_running_app().quit_app(instance)
class TestApp(App):
title = AppName + ' ' + VersionString
def on_pause(self):
return True
def quit_app(self, btn):
CloseProgram('Normal Closing', 'Debug')
if __name__ == '__main__':
init()
TestApp().run()
test.kv
#:kivy 1.11.0
#:import Factory kivy.factory.Factory
<Pop>:
title: 'Quit Program?'
size_hint: None, None
size: 400, 400
BoxLayout:
Button:
text: 'Quit'
on_release:
root.dismiss()
root.callback(self)
Button:
text: 'Return to Program'
on_release: root.dismiss()
<Button>:
font_size: 15
# Main Layout:
BoxLayout:
orientation: 'vertical'
Button:
text: "Quit"
id: "quit_button"
size_hint: (0.1, None)
size: (150, 50)
on_release: Factory.Pop().open()
Output - with kv file
Solution - without kv file
Bind all the buttons before calling Popup.open()
Use App.get_running_app() to get an instance of the class aempAPP
Snippet
def callback(self, instance):
print("\ncallback:")
self.popup.dismiss()
App.get_running_app().quit_app(1)
def up(self):
...
self.popup = Popup(title='Quit Program?', content=blayout, size_hint=(None, None), size=(400, 400))
abutton.bind(on_release=self.popup.dismiss)
qbutton.bind(on_release=self.callback)
self.popup.open()
...
class aempApp(App):
...
def quit_app(self, value):
print(value)
Example - without kv file
main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.uix.button import Button
class Pop(BoxLayout):
def __init__(self, **kwargs):
super(Pop, self).__init__(**kwargs)
self.up()
def callback(self, instance):
print("\ncallback:")
self.popup.dismiss()
App.get_running_app().quit_app(1)
def up(self):
print('popup')
qbutton = Button(text='Quit', id='quit')
abutton = Button(text='Return to Program', id='return')
blayout = BoxLayout()
blayout.add_widget(qbutton)
blayout.add_widget(abutton)
self.popup = Popup(title='Quit Program?', content=blayout, size_hint=(None, None), size=(400, 400))
abutton.bind(on_release=self.popup.dismiss)
qbutton.bind(on_release=self.callback)
self.popup.open()
class TestApp(App):
def build(self):
return Pop()
def quit_app(self, value):
print("value=", value)
if __name__ == "__main__":
TestApp().run()
Output - without kv file

How to bind a widget to a value from Kivy App

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

Categories

Resources