So in Kivy the way you usually delete buttons is by accessing their id or name or something. Is there any way which you could access the info of a button that is pressed so that it may delete itself when pressed? Assuming you have very many buttons and you don't know the id or you have 100 buttons and it would take forever?
Remove a widget
Use remove_widget() to remove a widget from the children list.
self.parent.remove_widget(self)
Remove all widgets / buttons
Use clear_widgets() to remove all children / buttons from a widget
self.parent.clear_widgets()
Many Buttons
Implement a class with inheritance of Button, and a method on_touch_down with collide_point() function to check for collision of the touch with our widget.
Kivy » Touch event basics
By default, touch events are dispatched to all currently displayed
widgets. This means widgets receive the touch event whether it occurs
within their physical area or not.
...
In order to provide the maximum flexibility, Kivy dispatches the
events to all the widgets and lets them decide how to react to them.
If you only want to respond to touch events inside the widget, you
simply check:
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
# The touch has occurred inside the widgets area. Do stuff!
pass
Snippets
class CustomButton(Button):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
print(f"\nCustomButton.on_touch_down: text={self.text}")
self.parent.remove_widget(self) # remove a widget / button
# self.parent.clear_widgets() # remove all children/ buttons
return True # consumed on_touch_down & stop propagation / bubbling
return super(CustomButton, self).on_touch_down(touch)
Example
main.py
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.lang import Builder
Builder.load_string("""
<Demo>:
cols: 10
""")
class CustomButton(Button):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
print(f"\nCustomButton.on_touch_down: text={self.text}")
self.parent.remove_widget(self) # remove a widget / button
# self.parent.clear_widgets() # remove all children / buttons
return True # consumed on_touch_down & stop propagation / bubbling
return super(CustomButton, self).on_touch_down(touch)
class Demo(GridLayout):
def __init__(self, **kwargs):
super(Demo, self).__init__(**kwargs)
self.create_buttons()
def create_buttons(self):
for i in range(100):
self.add_widget(CustomButton(id="Button" + str(i), text="Button"+str(i)))
class TestApp(App):
def build(self):
return Demo()
if __name__ == "__main__":
TestApp().run()
Output
I am not very good at Kivy, but there is a pretty good explanation here.
Here is the gist of what you might need:
First, you must pass the button instance explicitly to the method when it is called from the kv:
on_press: app.Pressbtn(self)
You can then use the instance reference to modify the button or see its attributes, you do not need the id. If you want to get the id, you can only do it using the ids dictionary of the button parent.
Related
When clicking on the button generated by the *.KV file, the buttons are generated in a grid (2). When clicking on the button of the "Create" class, the buttons are generated superimposed.
How do I solve the overlay buttons problem?
test.py
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
class Test(App):
def build(self):
return Window()
class Window(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 2
self.col_force_default = True
self.col_default_width = 200
self.row_force_default = True
self.row_default_height = 40
self.add_widget((Button(text="Create",
on_release=Window.addWidget)))
def addWidget(self):
count = 0
while count < 10:
count = count + 1
self.add_widget(Button(text=str(count)))
Test().run()
test.kv
<Window>:
BoxLayout:
Button:
on_release:root.addWidget()
In your python code the line:
on_release=Window.addWidget)))
arranges for Window.addWidget() to be called when the Button is released. When an on_release is specified, the Button that was release is passed as an argument to the specified method. So, when the Button is released, the actual call is executed as:
Window.addWidget(button_instance)
Now the addWidget() method of your Window class is an instance method, meaning that it expects the first argument passed to it is the self (the instance of the Window class). Since you are not calling it properly as an instance method, the passed in button_instance is mistaken for the Window class instance, and the created Buttons are added to the button_instance instead of the Window instance.
You can fix this by correctly calling the addWidget() method like this:
on_release=lambda button: self.addWidget())))
The lambda is just to remove the added button_instance argument.
A different fix is to add *args to the addWidget(), like this:
def addWidget(self, *args):
Then the on_release line is simpler:
on_release=self.addWidget)))
Is there a way to make it so when my ModalView/ Popup widget is active but none of the buttons behind it will be triggered?
Below is an example of a ModalView/popup widget that will notify the user they entered something incorrect. I want the user to be able to click anywhere and the ModalView will disappear. The problem is that when this ModalView is visiable the buttons in the background are still active.
In this case, the BaseBoxLayout has a Label on top and Button on the bottom. I click on the button to trigger the ModalView/popup. If the user clicks on the top, the ModalView/popup disappears – as designed by my on_touch_down method.
However, if the user clicks anywhere in the bottom half (when the ModalView/popup is already triggered) then the ModalView/popup gets removed but then added again because the button in the background is still active.
How do I prevent this?
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.modalview import ModalView
KV = '''
Screen:
canvas.before:
Color:
rgb: [127/255,160/255,100/255]
Rectangle:
pos: self.pos
size: self.size
BaseBoxLayout:
orientation: "vertical"
Label:
text: "BaseBoxLayout "
Button:
text: "Ok button"
on_press: self.parent.call_popup()
<CustomPopup>
BoxLayout:
Label:
text: "You can do this!"
'''
class BaseBoxLayout(BoxLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
def call_popup(self):
print('created popup')
self.popup = CustomPopup()
self.popup.open()
class CustomPopup(ModalView):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def on_touch_down(self,touch):
print('removed popup')
self.parent.remove_widget(self)
class MainApp(MDApp):
def build(self):
return Builder.load_string(KV)
if __name__ == '__main__':
MainApp().run()
As kivy gives you maximum flexibility in dispatching touch events, you can decide and implement that as you want.
Let's consider some typical situations you might come across in this scenario.
Your ModalView (I'll call it popup) needs to be dismissed as soon as you press (corresponds to on_touch_down event) anywhere on the screen (window). But you don't want any underlying widget to interfere at this point. Here, this means you don't want to dispatch the touch event further down the widget tree. You can do this by returning True from method on_touch_down which will digest the event and stop it propagate further.
def on_touch_down(self,touch):
self.parent.remove_widget(self)
return True
You want the popup to be dismissed as before but also you don't want to override any default behavior. Call the superclass method. This is more preferable than in 1.
def on_touch_down(self,touch):
self.parent.remove_widget(self)
return super().on_touch_down(touch)
Perhaps your popup will not cover whole window. In that case you want it to be dismissed only if touch is within its physical boundary otherwise not. Go as in 1 or 2. But what if the touch is outside its physical boundary ? Return True to digest the event. If you don't handle that youself, call super (but remember it will dismiss the popup unless you set auto_dismiss to False).
def on_touch_down(self,touch):
if self.collide_point(*touch.pos):
self.parent.remove_widget(self)
# Otherwise stop propagation and digest.
return True
# Or for default handling.
# return super().on_touch_down(touch)
There might be countable (if not infinite) no. of situations you can face here. So better stop here.
I'm trying to make a button and I'm doing this print as a test, I'm not why it prints twice?
I'm new to Kivy so I'm not too sure on what I'm doing, any help is appreciated thank you.
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
def bannedBooksMain(self, instance):
print('The button test is being pressed')
class mainApp(GridLayout):
def __init__(self, **kwargs):
super(mainApp, self).__init__(**kwargs)
self.cols = 2
btn1 = Button(text='Home Page')
btn1.bind(state=bannedBooksMain)
self.add_widget(btn1)
class MyApp(App):
def build(self):
return mainApp()
MyApp().run()
btn1.bind(state=bannedBooksMain)
This (state=) will initiate a callback whenever the state property changes, this happens for both button press and release, which is why you're seeing it twice
You should use on_press= or on_release= instead, depending on when you want the action fired.
Personally, I prefer the on_release= combined with always_release being false. That way, if I inadvertently press the button, I can move the mouse point away from the button before releasing, and there will be no callback.
See here for further detail.
So I am very new to Kivy, and it is pretty frustrating so far.....anyway, I am trying to now make a popup that I can drag and move around, and I don't understand what is going on.... when I call popup.open() in the onButtonPress function, the popup is closeable through the dismiss action, though I lose dragable functionality....when I add the popup directly to the main window through self.layout.add_widget(popup), I am able to move the popup but then am not able to close it....I'm guessing the open() call is redefining the dragable window? Is this true? If not what is happening and how do I fix it?
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.uix.behaviors import DragBehavior
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.popup import Popup
class PopupExample(App):
# override the build method and return the root widget of this App
def build(self):
# Define a grid layout for this App
self.layout = FloatLayout()
# Add a button
self.button = Button(text ="Click for pop-up")
self.layout.add_widget(self.button)
# Attach a callback for the button press event
self.button.bind(on_press = self.onButtonPress)
return self.layout
def onButtonPress(self, button):
# print('opening')
layout = GridLayout(cols = 1, padding = 10)
img = Image(source="temp_plot.png")
closeButton = Button(text = "Close the pop-up")
layout.add_widget(img)
layout.add_widget(closeButton)
popup = MoveableImage( title ='Demo Popup', content = layout, size_hint =(None, None), size = (400,400))
popup.open()
#self.layout.add_widget(popup)
#closeButton.bind(on_press = self.remove_widget(popup))
class MoveableImage(DragBehavior,Popup):
def __init__(self, **kwargs):
super(MoveableImage, self).__init__(**kwargs)
self.drag_timeout = 10000000
self.drag_distance = 0
self.drag_rectangle = [self.x, self.y, self.width, self.height]
def on_pos(self, *args):
self.drag_rectangle = [self.x, self.y, self.width, self.height]
def on_size(self, *args):
self.drag_rectangle = [self.x, self.y, self.width, self.height]
# Run the app
if __name__ == '__main__':
PopupExample().run()
If you want to drag the Popup, adding it to the App layout is probably the best way to go. Of course, then you don't really need to use the Popup, just use some Layout. If you go that route, deleting the Popup can be accomplished by defining the Button binding like this:
closeButton.bind(on_press = lambda x: self.layout.remove_widget(popup))
The lambda is just there to suck up the button instance arg that on_press adds, so you won't get errors about too many arguments to the remove_widget() method.
The modalView class (base of the Popup) has a method that keeps the Popup centered. Looks like the DragBehavior will work, if you over-ride that method. So, if your add:
def _align_center(self, *l):
pass
to your MoveableImage class, I think you will then be able to drag it. The open() method of the ModalView does some bindings to insure that the _align_center() method is called on any position or size change. Since adding the Popup to a Layout does not call the open() method, those bindings are never set up.
I am a beginner just learning coding and Python.
I am not able to remove a widget that I just have created.
I can still create new button. This is actually just little side test.
Eventually I want to have a scrollview where I can add and remove buttons. There are some add/remove widget code snippets but I must be able to add/remove widgets in another layout.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.uix.widget import Widget
KV = """
BoxLayout
id:aaa
Button
text: 'Add'
on_press: app.add_more()
Button:
text:'Remove'
on_press: app.remove()
BoxLayout:
id:abc
"""
class MyApp(App):
def build(self):
self.root = Builder.load_string(KV)
def add_more(self):
print('wass')
addbutton = self.root.ids.abc
addbutton.add_widget(Button(text='hello'))
def remove(self):
print('hello')
self.remove_widget(self.children[0])
MyApp().run()
I got this error message when clicking the remove button:
MyApp' object has no attribute 'remove_widget'
AttributeError
self.remove_widget(self.children[0])
AttributeError: 'MyApp' object has no attribute 'remove_widget'
Root Cause
The App class inherited by MyApp, does not have the method, remove_widget(). Only a root widget, which usually has children that can have children of their own.
Question
remove widget inside another layout
Solution
Replace self.remove_widget(...) to self.root.ids.abc.remove_widget(...)
Replace self.children[0] with self.root.ids.abc.children[0]
Check that there are children inside the layout before we invoke remove_widget()
Snippets
def remove(self):
print('hello')
if len(self.root.ids.abc.children) > 0: # check for children
self.root.ids.abc.remove_widget(self.root.ids.abc.children[0]) # remove child FIFO
Kivy Widget » remove_widget()
Widgets in Kivy are organized in trees. Your application has a root
widget, which usually has children that can have children of
their own. Children of a widget are represented as the children
attribute, a Kivy ListProperty.
The widget tree can be manipulated with the following methods:
add_widget(): add a widget as a child
remove_widget():
remove a widget from the children list
clear_widgets(): remove
all children from a widget
Since you load the layout as self.root = Builder.load_string(KV), you can remove the first child with self.root.remove_widget(self.root.children[0])