In this code, On pressing button "CREATE", three buttons "Remove","Btn1","Btn2" are created... On pressing "Remove" button, all the three buttons ("Remove","Btn1","Btn2") should be removed. I tried many ways but I can't find a way to make the new buttons accessible to other functions that are under the same class.
Main Code:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.screenmanager import Screen,ScreenManager
class Main(Screen):
def remove(self):
self.ids.Fl.remove_widget(Btnremove)
self.ids.Fl.remove_widget(Btn1)
self.ids.Fl.remove_widget(Btn2)
def addme(self):
Btnremove=Button(text="Remove",font_size=18,size_hint=(.2,.1),pos_hint={"center_x": .5, "center_y": .7})
Btn1=Button(text= "Btn1",font_size=18,size_hint=(.2,.1),pos_hint={"center_x":.4,"center_y":.2})
Btn2=Button(text="Btn2",font_size=18,size_hint=(.2, .1),pos_hint={"center_x": .6, "center_y": .2})
self.ids.Fl.add_widget(Btnremove)
self.ids.Fl.add_widget(Btn1)
self.ids.Fl.add_widget(Btn2)
Btnremove.bind(on_press=self.remove())
class Manager(ScreenManager):
pass
kv=Builder.load_file("test2.kv")
screen=Manager()
screen.add_widget(Main(name="main"))
class Test(App):
def build(self):
return screen
Test().run()
Kv Code:
<Main>:
name: "main"
FloatLayout:
id: Fl
Button:
id: create
text: "CREATE"
size_hint: (.55,.175)
pos_hint: {"center_x":.5,"center_y":.5}
on_press:
root.addme()
The answered solution of Parvat iterates over all children (buttons) inside your widget and removes all of them, that have not the text CREATE
. So it implicitly works without knowing the buttons created before.
Even if this is expected to work, but it does not really explain your issue here.
It works only, because it has no knowledge of the buttons. Only thing it assumes, that there is one button CREATE
to preserve (not delete).
Unfortunately the CREATE
button was defined even outside code in the config. If you change the text there (e.g. to CREATE BUTTONS
), your function will not work as expected anymore.
You claim that your remove
method does not work as expected.
And further that you don't know how to make buttons accessible to other functions.
In you instance method remove(self)
you're addressing the button objects (e.g. Btnremove
) like they were global variables.
def remove(self):
self.ids.Fl.remove_widget(Btnremove)
self.ids.Fl.remove_widget(Btn1)
self.ids.Fl.remove_widget(Btn2)
But they aren't. They are local variables, only visible inside the other instance method addme(self)
:
def addme(self):
Btnremove=Button(text="Remove",font_size=18,size_hint=(.2,.1),pos_hint={"center_x": .5, "center_y": .7})
Btn1=Button(text= "Btn1",font_size=18,size_hint=(.2,.1),pos_hint={"center_x":.4,"center_y":.2})
Btn2=Button(text="Btn2",font_size=18,size_hint=(.2, .1),pos_hint={"center_x": .6, "center_y": .2})
chance
Your bound action for on_press
event was defined like calling a function, i.e. self.remove()
. Instead define it as reference to the function/method by simply passing the name like self.remove
(without parentheses!).
See Kivy's documentation on Button
:
To attach a callback when the button is pressed (clicked/touched), use bind:
def callback(instance): print('The button <%s> is being pressed' % instance.text) btn1 = Button(text='Hello world 1') btn1.bind(on_press=callback)
This callback
is simply the method-name (without parentheses). In your case:
# before: will result in `None` bound and raise an error when pressed
Btnremove.bind(on_press=self.remove())
# after: will call the function by name
Btnremove.bind(on_press=self.remove)
Congrats, you found the bug yourself and fixed it.
Because when binding and defining the callback as on_press=self.remove()
the method is immediately called and returns None
(the method does not return an object). Then None
is bound instead of the function-reference.
So adversely two things happen that you did not expect:
None
can not be called. Hence the error:AssertionError: None is not callable
There are different ways to solve this (which have nothing to do with Kivy):
# adding a parameter `widgets`
def remove(self, widgets):
for w in widgets:
self.ids.Fl.remove_widget(w)
# calling the method with argument
def addme(self):
# omitted code
buttons_to_remove = [Btnremove, Btn1, Btn2] # creating a list with your buttons
Btnremove.bind(on_press=self.remove(buttons_to_remove)) # pass the list as argument to the method
⚠️ Warning: This binding on_press=self.remove(buttons_to_remove)
will not work, since a callback has to be passed as method-reference by name only. No custom arguments can be passed like buttons_to_remove
. The only argument that is passed implicitly to the method by Kivy is instance
as reference to the button instance itself.
Over these instance variables (the instance scope is marked with prefix self.
) we can share state inside the object. So we can share state between all instance methods because all have access to the instance object self
. So they have also access to everything inside self
like self.buttons_to_delete
.
# accessing instance variable `buttons_to_remove`
def remove(self):
# accessing the shared instance variable using instance-prefix `self.`
for btn in self.buttons_to_remove:
self.ids.Fl.remove_widget(btn)
# adding buttons to the instance-variable `buttons_to_remove`
def addme(self):
# omitted code
self.buttons_to_remove = [Btnremove, Btn1, Btn2] # creating a list with your buttons
Btnremove.bind(on_press=self.remove()) # the method has access to this list
⚠️ Warning: If you haven't declared the instance variable self.buttons_to_remove
before (e.g. in constructor), and you now call remove
before addme
, then inside remove
it will raise an error because your instance-variable does not yet exist and is unknown.
A similar question was already asked and answered: How to address remove_widget what widget to remove inside another layout in Kivy
The call of Kivy's remove_widget
method is right, expects a child (of type Widget
or here: Button
) passed to it.
Like in this example from the docs about remove_widget
:
>>> from kivy.uix.button import Button
>>> root = Widget()
>>> button = Button()
>>> root.add_widget(button)
>>> root.remove_widget(button)
Notice here, that the local variable button
is visible & accessible to the method remove_widget
because it's passed as argument.