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.
Related
I could really really need some help with my actually quite simple Python Kivy Problem! I wrote a program that first announces counting to 5 and then should start counting from 1 to 5. The info should be shown in a scrollview-Label. The code roughly does its job but does not update the scrollview step-by-step but all at once after time is elapsed...can anybody please help? Thank you in advance!
import kivy
from kivy.config import Config
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.core.window import Window
from kivy.uix.scrollview import ScrollView
import time
kivy.require("2.0.0")
Config.set('kivy', 'keyboard_mode', 'systemandmulti')
class MainMenu(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 1
self.rows = 2
self.infowindow = ScrollableInfo(height=Window.size[1]*0.8, size_hint_y=None)
self.add_widget(self.infowindow)
self.ButtonCheckConnection = Button(text="Start Counting to 5")
self.ButtonCheckConnection.bind(on_press=self.countingtofive)
self.add_widget(self.ButtonCheckConnection)
def countingtofive(self, *_):
self.infowindow.update_scrollview(f"Counting to 5 is going to start in 3 seconds")
time.sleep(3)
countingmaximum = 5
for i in range(countingmaximum):
currentnumber = i+1
self.infowindow.update_scrollview(str(currentnumber))
time.sleep(1)
class ScrollableInfo(ScrollView):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.layout = GridLayout(cols=1, size_hint_y=None)
self.add_widget(self.layout)
self.connectioninfo_history = Label(size_hint_y=None, markup=True)
self.layout.add_widget(self.connectioninfo_history)
def update_scrollview(self, newinfo):
self.connectioninfo_history.text += '\n' + newinfo
self.layout.height = self.connectioninfo_history.texture_size[1]+15
self.connectioninfo_history.height = self.connectioninfo_history.texture_size[1]
self.connectioninfo_history.text_size = (self.connectioninfo_history.width*0.98, None)
class Counting(App):
def build(self):
self.screen_manager = ScreenManager()
self.mainmenu_page = MainMenu()
screen = Screen(name="MainMenu")
screen.add_widget(self.mainmenu_page)
self.screen_manager.add_widget(screen)
return self.screen_manager
if __name__ == "__main__":
counting_app = Counting()
counting_app.run()
The problem is that you are running your countingtofive() method on the main thread. Since Kivy uses the main thread to update the GUI, it cannot do that until you release the main thread (by returning from the countingtofive() method). That is why you never see anything until that method completes.
To fix that, run the countingtofive() method in another thread, like this:
def start_counting_thread(self, *args):
Thread(target=self.countingtofive, daemon=True).start()
And change the Button to bind to the start_counting_thread() method:
self.ButtonCheckConnection.bind(on_press=self.start_counting_thread)
And one minor change to the update_scrollview() method (add the #mainthread decorator):
#mainthread
def update_scrollview(self, newinfo):
The #mainthread decorator forces the decorated method to be run on the main thread. The same can be accomplished by using Clock.schedule_once(), but the decorator is easier. Just the piece of the code that actually updates the GUI must be run on the main thread. Generally, you should try to avoid long running methods on the main thread.
Goal: when I click then release the click on the switch widget, it fires me an event on the release.
Problem: I used the on_touch_down() Switch method but:
it release 2 event on the release of the click and not only one.
when I release and it shows "off", it says "True" and it shows "False" when it's on "on" !
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
KV = '''
MyGridLayout:
Switch:
id: switch_ecs
on_touch_up:
root.on_switch("ecs")
'''
class MyGridLayout(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def on_switch(self,element_id):
print(self.ids["switch_" + element_id].active)
class MyApp(MDApp):
def build(self):
self.screen = Builder.load_string(KV)
return self.screen
if __name__ == '__main__':
MyApp().run()
Kivy has some obscure logic that results in touch up events getting dispatched twice to some widgets (like Buttons and Switches). The fix for your code is to use a different trigger for your on_switch() method. You can use:
on_touch_down:
root.on_switch("ecs")
or:
on_active:
root.on_switch("ecs")
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.
I would like to know how to change screens using an on_press event binded to a button, without using a KV file/KV language.
I have read through the Kivy documentation, but have only been able to find solutions using a KV file.
Example:
on_press: root.manager.current = 'screen2'
I can also change the screen in the main python file using:
screenmanager.current = 'screen2'
But I cant figure out how to achieve the same using a button.
A working example with two screens, no kv file everything done in Python:
import kivy
kivy.require('1.8.0')
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
class ScreenOne(Screen):
def __init__ (self,**kwargs):
super (ScreenOne, self).__init__(**kwargs)
my_box1 = BoxLayout(orientation='vertical')
my_label1 = Label(text="BlaBlaBla on screen 1", font_size='24dp')
my_button1 = Button(text="Go to screen 2",size_hint_y=None, size_y=100)
my_button1.bind(on_press=self.changer)
my_box1.add_widget(my_label1)
my_box1.add_widget(my_button1)
self.add_widget(my_box1)
def changer(self,*args):
self.manager.current = 'screen2'
class ScreenTwo(Screen):
def __init__(self,**kwargs):
super (ScreenTwo,self).__init__(**kwargs)
my_box1 = BoxLayout(orientation='vertical')
my_label1 = Label(text="BlaBlaBla on screen 2",font_size='24dp')
my_button1 = Button(text="Go to screen 1",size_hint_y=None, size_y=100)
my_button1.bind(on_press=self.changer)
my_box1.add_widget(my_label1)
my_box1.add_widget(my_button1)
self.add_widget(my_box1)
def changer(self,*args):
self.manager.current = 'screen1'
class TestApp(App):
def build(self):
my_screenmanager = ScreenManager()
screen1 = ScreenOne(name='screen1')
screen2 = ScreenTwo(name='screen2')
my_screenmanager.add_widget(screen1)
my_screenmanager.add_widget(screen2)
return my_screenmanager
if __name__ == '__main__':
TestApp().run()
One simple way to accomplish this is to define your own button subclass:
class ScreenButton(Button):
screenmanager = ObjectProperty()
def on_press(self, *args):
super(ScreenButton, self).on_press(*args)
self.screenmanager.current = 'whatever'
The on_press method is automatically called when the button is pressed, so the screenmanager's current property will be changed.
Then you can have code something like:
sm = ScreenManager()
sc1 = Screen(name='firstscreen')
sc1.add_widget(ScreenButton(screenmanager=sm))
sc2 = Screen(name='whatever')
sc2.add_widget(Label(text='another screen'))
sm.add_widget(sc1)
sm.add_widget(sc2)
Clicking the button should switch the screens as required.
Another way (which is probably how kv language actually does it) would be to manually use the bind method.
def switching_function(*args):
some_screen_manager.current = 'whatever'
some_button.bind(on_press=switching_function)
This would mean that switching_function is called whenever some_button is pressed. Of course there is a lot of flexibility here regarding how and when you define the function, so (for instance) you could do something more general like pass the screenmanager as the first argument to the function.
I didn't test this code and it isn't a complete app, but hopefully the meaning is clear. Either method should work fine, you can choose the way that seems most sensible. I might construct a more complete example later.
Another solution, was to use the setter method of EventDispatcher, to get a reference to the setter function for screen_manager.current
button.bind(on_press=partial(sm.setter('current'), (sm, 'whatever'))
of course, it's not very sexy, that's why kv is often a cleaner solution to these things, but it should work.
ps: in case you don't know about it, partial comes from the functools module, and it's often useful to build these kind of callbacks with a preloaded parameter.
Does kivy support MouseEvent that is triggered on mouse_pos change without pressing mouse button?
I found in documentation this:
def on_motion(self, etype, motionevent):
# will receive all motion events.
pass
Window.bind(on_motion=on_motion)
You can also listen to changes of the mouse position by watching mouse_pos.
However I cant implement it. I managed to bind it and add to on_motion function 'print('Hello world')' but it was triggered only by pressing-type events.
Thanks in advance
Solution
Bind mouse_pos to a callback. Please refer to example for details.
Window.bind(mouse_pos=self.mouse_pos)
Example
main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.core.window import Window
class MousePosDemo(BoxLayout):
def __init__(self, **kwargs):
super(MousePosDemo, self).__init__(**kwargs)
self.label = Label()
self.add_widget(self.label)
Window.bind(mouse_pos=self.mouse_pos)
def mouse_pos(self, window, pos):
self.label.text = str(pos)
class TestApp(App):
title = "Kivy Mouse Pos Demo"
def build(self):
return MousePosDemo()
if __name__ == "__main__":
TestApp().run()
Output
You actually want to do:
Window.bind(mouse_pos=on_motion)