I create a sample kivy script that dynamically adding button to the widget then remove it automatically using clock.schedule_once function.
I was able to referred to the widget children to change the button text. However when i try to remove the widget itself, it did not work. Instead it gave me RecursionError. Please help me out on how to remove the widget. I added some comment that causes the error in the script :
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
class nbutton(Button):
buttonid=ObjectProperty(None)
class MyWidget(BoxLayout):
childs = []
def __init__(self,*args):
Clock.schedule_once(self.add_widgets,1)
Clock.schedule_once(self.remove_widget,2)
return super().__init__(*args)
def add_widgets(self,*args):
#self.r = BoxLayout(size_hint=(1,1),id="nice")
#self.add_widget(self.r)
self.add_widget(Button(text="Hello",id='dasd'))
self.add_widget(Button(text="World",id='q'))
self.add_widget(Button(text="nice",id='dz'))
self.add_widget(Button(text="good",id='m'))
def remove_widget(self,*args):
#m = App.get_running_app().root.ids.nice
#self.remove_widget(self.children.ids.dasd)
#self.clear_widgets()
#for child in [child for child in self.children if child.text != "Hello"]:
#self.remove_widget(child)
#print(child)
#self.remove_widget(self.child.m)
for chi in self.children:
self.childs.append(chi)
print(chi)
#print(self.childs.text)
#try:
self.childs[2].text = 'hihih'
#------------------------ this part is broken ------------------------------
self.remove_widget(self.childs[2])
#------------------------ this part is broken ------------------------------
# error message:
# RecursionError: maximum recursion depth exceeded while calling a Python object
# except:
# pass
# for c in childs:
# self.remove_widget(c)
#for child in self.children:
#self.remove(child)
#print(child.id)
# if child.text == 'Hello':
# #print(child)
# self.remove_widget(child)
#pass
class MpApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
MpApp().run()```
Within your remove_widget() method, you are recursing into remove_widget() with the call:
self.remove_widget(self.childs[2])
If you are going to recurse like that, you need some test to end the recursion, otherwise it will recurse infinitely.
Also, you are redefining the remove_widget() method of MyWidget (which is a BoxLayout) without ever calling the actual remove_widget() method of the superclass (BoxLayout). So the child widget never actually gets removed. Perhaps naming your remove_widget() in MyWidget to something like my_remove_widget() might help.
Related
I've got a problem tying callback funtions to the on_focus event of a TextInput.
I want it to trigger a validation event when the focus from the input widget is removed. And, in doing so, calling another method (via the on_validate_text method)
Here is the code:
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.app import App
class MyTextInput(TextInput):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.multiline = False
self.unfocus_on_touch = True
def on_focus(self, instance, value):
if not value: # DEFOCUSED
print('Focus is off')
class MainLayout(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 2
#First row
self.top_label = Label(text = 'No text')
self.add_widget(self.top_label)
self.top_input = MyTextInput(on_text_validate=self.change_top_label)
#Here im trying to trigger the validate event when the on_focus gets called
self.top_input.bind(on_focus=self.top_input.on_text_validate)
self.add_widget(self.top_input)
#Second row
self.bottom_label = Label(text='Bottom Label')
self.add_widget(self.bottom_label)
self.bottom_input = MyTextInput(on_text_validate=self.create_popup)
self.bottom_input.bind(on_focus=self.bottom_input.on_text_validate)
self.add_widget(self.bottom_input)
def change_top_label(self, instance):
self.top_label.text = instance.text
instance.text = ''
def create_popup(self, instance):
self.my_popup = Popup(title=instance.text, size_hint=(.5, .5))
self.my_popup.content = Button(text='CLOSE', on_release=self.my_popup.dismiss)
self.my_popup.open()
instance.text = ''
if __name__ == '__main__':
class MainApp(App):
def build(self):
return MainLayout()
MainApp().run()
In this case, when de top input gets defocused, I want it to call the change_top_label method through the validation event.
In the same way, when the bottom input gets defocused, the create_popup method should get called through the validation event.
I need both input to call a callback function when unfocused. But I can not define that function inside the on_focus method, because it needs to be different for every instance of MyTextInput.
I've tried binding on_text_validate to on_focus, and calling on_text_validate inside the on_focus metyhod, but it does not work.
Clearly there is something I'm missing.
If you could help me out here, It'd be great.
First of all, your code seems already doing what you wanted in the following (after the method on_text_validate is being called),
In this case, when de top input gets defocused, I want it to call the change_top_label method through the validation event...
Secondly,
I've tried binding on_text_validate to on_focus...
This seems confusing to me. The method on_text_validate gets called when you hit 'enter' (and if multiline is set to False) and that will also unfocus the TextInput. Also on_focus is kind of a default method that is called whenever the attr. focus changes. So and finally if you want just this,
I need both input to call a callback function when unfocused. But...
You can do that as TextInput_instance.bind(focus = some_method).
In my app, I create instances of ModalView that contain child widgets with callbacks that are bound to widget properties or scheduled with Clock. Here is an example code to demonstrate this. I find that dismiss() method of the ModalView instance leaves intact the callback bindings and the Clock scheduled callbacks of its child widgets. I have to take care of unbinding and unscheduling these myself. This can get messy when I bind to callbacks that take args (I then have to use fbind and funbind_uid methods whilst keeping track of uids). Similarly, Clock scheduled callbacks that take args are tricky to unschedule since they are anonymous then, having been scheduled either using a lambda or a partial.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.uix.modalview import ModalView
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.properties import ObjectProperty
from kivy.clock import Clock
import datetime
Builder.load_string('''
#: kivy 1.9.2
<MainWidget>:
Button:
text: 'Push the button (pu-push the button)'
on_press:
root.showtime()
''')
class MyWidget(FloatLayout):
text=StringProperty() # this can be bound to 'text' property of a child widget for observation
timenow=ObjectProperty()
def __init__(self, **kwargs):
super(FloatLayout, self).__init__(**kwargs)
self.bind(timenow=self.update_text)
Clock.schedule_interval(self.poll_datetime, .5)
def poll_datetime(self, dt):
self.timenow = datetime.datetime.now()
print "polling datetime"
def update_text(self, *args):
self.text=self.timenow.strftime("%Y-%m-%d %H:%M:%S")
print "updating text"
def cleanup(self, *args):
self.unbind(timenow=self.update_text)
Clock.unschedule(self.poll_datetime)
print "cleaning up"
class MainWidget(FloatLayout):
def __init__(self, **kwargs):
super(MainWidget, self).__init__(**kwargs)
def showtime(self):
overlay = ModalView()
container=MyWidget()
timelabel=Label()
container.bind(text=timelabel.setter('text'))
container.bind(pos=timelabel.setter('pos'))
container.add_widget(timelabel)
cancelbutton=Button(text='Cancel', size_hint=(None, None))
cancelbutton.bind(on_press=container.cleanup)
cancelbutton.bind(on_press=overlay.dismiss)
container.add_widget(cancelbutton)
overlay.add_widget(container)
overlay.open()
class MyApp(App):
def build(self):
mw=MainWidget()
return mw
if __name__ == '__main__':
MyApp().run()
Am I doing this right? Does ModalView's dismiss() method leave other objects behind that I am not even aware of? What is a good way to detect such objects being left behind? Is there a way to ensure complete destruction of child widgets of ModalView instance upon dismiss() is called?
I think you are doing it right. When the ModalView is dismissed, its reference in the parent window is removed, and if no other references to it are held anywhere, it will be garbage collected, and any references that it holds will also be garbage collected. However, the Clock.schedule_interval() is holding a reference to the ModalView, so it does not get garbage collected. This is the correct behavior, as your call to schedule events means that you want those scheduled events to continue until they are cancelled.
An easier way to cancel the scheduled events is to use:
self.sched = Clock.schedule_interval(self.poll_datetime, .5)
in the __init__() method of MyWidget. Then in the cleanup() method use:
self.sched.cancel()
You don't need to unbind the timenow binding as that will disappear with the garbage collection that will happen after the cancel.
i was trying to bind on_press method to gridLayout in kv language but i got this error AttributeError: pressed. I did some research even in the kivy doc but no help .So if any one has a solution to this problem please you may be a good resource
here is my testApp.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
class screendb(BoxLayout):
def mycall_back(self):
print('hello')
class testApp(App):
def build(self):
return screendb()
if __name__=='__main__':
testApp().run()
here is my testApp.kv
<Screendb#BoxLayout>:
GridLayout:
on_pressed:root.Mycall_back()
In your py file:
# Behaviors let you add behavior from one widget to another widget
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.gridlayout import GridLayout
# We create a new widget that is a GridLayout with access to Button Behaviors
# Like Button's 'on_press' method
class ButtonGrid(ButtonBehavior, GridLayout):
pass
class screendb(BoxLayout):
def mycall_back(self):
print('hello')
class testApp(App):
def build(self):
return screendb()
In your kv file:
# We create a Root Widget for the ButtonGrid
<ButtonGrid>:
<Screendb#BoxLayout>:
# We add an instance of the ButtonGrid class to our main layout
ButtonGrid:
# We can now use on_press because it is a part of the ButtonBehavior class, that makes up your ButtonGrid class.
on_press:root.mycall_back()
Note: There were a few minor mistakes in your post as well. For example, there is no method 'on_pressed', only 'on_press', you also wrote your callback as 'mycall_back' in your py file while writing it as 'Mycall_back' in your kv file, which refers to a different method that exists. Make sure your letter cases match.
video example
Let's say I define on the fly in Kivy a few widgets (buttons) and dynamically assign their id.
I'm not using kv language in this use case.
I can keep a reference of a widget id without keeping track of the widget itself : then I'd like to access the widget through its id.
Can I do something like "get widget by id" ?
(If I had defined the widget in a kv file, I could have used self.ids.the_widget_id to access the widget itself through its id)
Kivy widgets make tree structure. Children of any widget are avaiable through children atribute. If you want, you can keep reference only to root window and then iterate over it's widgets using walk method:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
class MyWidget(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
button = Button(text="...", id="1")
button.bind(on_release=self.print_label)
l1 = BoxLayout(id="2")
l2 = BoxLayout(id="3")
self.add_widget(l1)
l1.add_widget(l2)
l2.add_widget(button)
def print_label(self, *args):
for widget in self.walk():
print("{} -> {}".format(widget, widget.id))
class MyApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
MyApp().run()
walk() and walk_reverse() method were added to kivy.uix.widget.Widget in 1.8.1 version of Kivy. For older versions you need to recursively parse tree yourself:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
class MyWidget(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
button = Button(text="...", id="1")
button.bind(on_release=self.print_label)
l1 = BoxLayout(id="2")
l2 = BoxLayout(id="3")
self.add_widget(l1)
l1.add_widget(l2)
l2.add_widget(button)
def print_label(self, *args):
children = self.children[:]
while children:
child = children.pop()
print("{} -> {}".format(child, child.id))
children.extend(child.children)
class MyApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
MyApp().run()
You can retrieve the widget using directly the ids. For example in your code you can modify the Button text with the following snippet:
self.ids.2.ids.3.ids.1.text = '!!!!'
You can change properties of each widget using ids:
self.ids['order_number'].text='S1212'
Trying to follow this guide : https://kivy.org/docs/guide/lang.html#accessing-widgets-defined-inside-kv-lang-in-your-python-code
I am attempting to access a widget using an id definition. This works well inside the root widget but it doesn't seem to work outside of it.
As an example here's a bare minimum code representing my issue :
GUI.kv file :
<PlotBox#BoxLayout>:
graph2:graph2_id
BoxLayout:
id:graph2_id
<RootWidget#BoxLayout>:
graph:graph_id
BoxLayout:
id:graph_id
PlotBox:
python file :
#kivy imports
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
class PlotBox(BoxLayout):
graph2 = ObjectProperty(None)
def __init__(self,**kwargs):
super(PlotBox,self).__init__(**kwargs)
self.graph2.add_widget(Button(text="This doesn't work"))
class RootWidget(BoxLayout):
graph = ObjectProperty(None)
def __init__(self,**kwargs):
super(RootWidget,self).__init__(**kwargs)
self.graph.add_widget(Button(text='This works'))
class GUIApp(App):
def build(self):
self.root = RootWidget()
return self.root
if __name__ == "__main__":
GUIApp().run()
I get the error :
AttributeError: 'NoneType' object has no attribute 'add_widget'
On the RootWidget, it works even if I don't use graph = ObjectProperty(None).
On my other widget, it's like the id doesn't get created.
According to the docs:
The # character is used to separate your class name from the classes you want to subclass. [...]
From what is concluded that it is an equivalent way to do inheritance in the .kv similar to python so you should only select one of those ways. That causes PlotBox from .py to never be invoked.
Another error, according to the docs, I do not know if it's your error but the .kv must be gui.kv, with lowercase.
children are not loaded directly after executing the parent's constructor so adding it in the constructor can generate problems, a recommendation and a very common practice in kivy is to use Clock.
All the above I have implemented in the following codes:
gui.kv
<PlotBox>:
graph2:graph2_id
BoxLayout:
id:graph2_id
<RootWidget>:
graph:graph_id
BoxLayout:
id:graph_id
PlotBox:
main.py
#kivy imports
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
from kivy.clock import Clock
class PlotBox(BoxLayout):
graph2 = ObjectProperty(None)
def __init__(self,**kwargs):
super(PlotBox,self).__init__(**kwargs)
Clock.schedule_once(lambda dt: self.graph2.add_widget(Button(text="This now works")))
class RootWidget(BoxLayout):
graph = ObjectProperty(None)
def __init__(self,**kwargs):
super(RootWidget,self).__init__(**kwargs)
self.graph.add_widget(Button(text='This works'))
class GUIApp(App):
def build(self):
root = RootWidget()
return root
if __name__ == "__main__":
GUIApp().run()
Output:
I think self.graph2 just hasn't been set yet during the __init__ - the __init__ has to return before any children can be added.
You can work around this by doing something like Clock.schedule_once(function_that_adds_the_button, 0).
I'm working under the assumption that you want this code to run when the app is being created, no later.
kv.
<PlotBox>:
BoxLayout:
id:graph2_id
<RootWidget>:
BoxLayout:
id:graph_id
PlotBox:
id: plot
py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
class PlotBox(BoxLayout):
pass
class RootWidget(BoxLayout):
pass
class GUIApp(App):
def build(self):
root = RootWidget()
# We can add things to the Root during build before we return it
# This means we add this information before the user sees anything
root.ids.graph_id.add_widget(Button(text='This works'))
# See end of answer for more about this
root.ids.plot.ids.graph2_id.add_widget(Button(text='This works!'))
return root
if __name__ == "__main__":
GUIApp().run()
Firstly, you don't need Object Properties to access ids, you can do it through ids or children:
self.ids.IDGOESHERE
OR
self.children[INDEXOFIDGOESHERE]
As for this line:
root.ids.plot.ids.graph2_id.add_widget(Button(text='This works!'))
Root has an instance of the plotbox class with the id 'plot'. The Plot class (and therefore all instances of the plot class) have an instance of the BoxLayout with the id graph that we can access.
So what we're doing is:
Root -> Plot -> Graph2
If we were to add another plotbox with the id 'big_plot', then we could do either what we did before to get one Graph2 or we could get a different graph2 because it belongs to a different instance of the plotbox.
What we did before
Root -> Plot -> Graph2
A different id, therefore a different widget.
Root -> big_plot -> Graph2
Unless you're invoking super, you'll rarely need to use the init method in a Kivy Widget Class (or so in my experience anyway).
Edit:
If you're going to access super long addresses repeatedly, you can wrap them in a function to get them.
example:
Not great:
def func_one(self):
newtext = 'new'
self.ids.IDONE.ids.IDTWO.ids.IDTHREE.ids.IDFOUR.text = newtext
def func_two(self):
newtext = 'newtwo'
self.ids.IDONE.ids.IDTWO.ids.IDTHREE.ids.IDFOUR.text = newtext
def func_three(self):
newtext = 'newthree'
self.ids.IDSONE.Ids.IDTWO.ids.IDTHREE.ids.IDFOUR.text = newtext
Better:
def long_address(self):
return self.ids.IDSONE.ids.IDSTWO.ids.IDTHREE.ids.IDFOUR
def func_one(self):
newtext = 'new'
self.long_address().text = newtext
def func_two(self):
newtext = 'newtwo'
self.long_address().text = newtext
def func_three(self):
newtext = 'newthree'
self.long_address().text = newtext