kivy ToggleButton : How can I trigger a method on the state changement? - python

Here is my actual code. It does what I want except when I change the state with a script, it doesn't give the information. It only gives the information when the change is made with a user interaction.
from kivymd.app import MDApp
from kivy.lang import Builder
KV = '''
BoxLayout:
ToggleButton:
id: toggle_button
text: "click on me"
'''
class ExampleApp(MDApp):
loading_layout = None
def build(self):
screen = Builder.load_string(KV)
screen.ids["toggle_button"].bind(on_press=lambda instance: self.on_click(instance))
print("I change the state and I want to be aware")
screen.ids["toggle_button"].state = "down"
print("on_click method isn't enough :(")
return screen
def on_click(self, instance):
print("State changed !", instance.state)
ExampleApp().run()
Question: How can I link a function to the state changement and not only on the click ?

What you could do is take advantage of the on_state method of the ToggleButton class. Here is an example:
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.togglebutton import ToggleButton
KV = '''
BoxLayout:
CustomToggle:
id: toggle_button
text: "click on me"
'''
class CustomToggle(ToggleButton):
def on_state(self, *args):
print('State changed!', self.state)
def on_press(self):
print("Button pressed!", self.state)
class ExampleApp(MDApp):
loading_layout = None
def build(self):
screen = Builder.load_string(KV)
screen.ids["toggle_button"].state = "down"
return screen
ExampleApp().run()
The on_state method is automatically bound to the state attribute, so that whenever the state is changed the function is called.

Related

Kivy - rebuild class/ Boxlayout with updated content

In my Kivy-App, i generate Buttons via a python-class based on a dictionary (in the following example i use a list but that's just an example for the underlying problem).
Within the App, the dictionary gets changed and i want to display that change (obviously) in my App (by adding/ removing/ rearranging the Buttons).
To achieve this, my approach is to either restart the entire App or only reload that particular BoxLayout. Unfortunately, non of my attempts worked out so far and i could not find any (working) solution on the internet.
This is my code example:
Python Code:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
buttonlist = []
counter = 0
class MainWindow(BoxLayout):
def addbutton(self):
global buttonlist
global counter
buttonlist.append(counter)
counter += 1
class ButtonBox(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation = "vertical"
global buttonlist
for button in buttonlist:
b = Button(text=str(button))
self.add_widget(b)
class KivyApp(App):
def build(self):
return MainWindow()
KivyApp().run()
KV Code:
<MainWindow>:
BoxLayout:
ButtonBox:
Button:
text: "add Button"
on_press: root.addbutton()
My closest attempt was something containing a restart-Method like:
def restart(self):
self.stop()
return KivyApp().run()
and calling:
App.get_running_app().restart()
But for some reason, this does not stop the App but opens a second instance of the App within the first one (resulting in App in App in App in App if pressed often)
You can rebuild the ButtonBox by first calling clear_widgets() on the ButtonBox instance. Here is a modified version of your code that does that:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
kv = '''
<MainWindow>:
BoxLayout:
ButtonBox:
id: box
Button:
text: "add Button"
on_press: root.addbutton()
'''
buttonlist = ['Abba', 'Dabba', 'Doo']
counter = 3
class MainWindow(BoxLayout):
def addbutton(self):
global buttonlist
global counter
buttonlist.append(str(counter))
counter += 1
self.ids.box.reload()
class ButtonBox(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation = "vertical"
self.reload()
def reload(self):
# method to rebuild the ButtonBox contents
global buttonlist
self.clear_widgets()
for button in buttonlist:
b = Button(text=str(button))
self.add_widget(b)
class KivyApp(App):
def build(self):
Builder.load_string(kv)
return MainWindow()
KivyApp().run()
I used your kv as a string, just for my own convenience.

how to print the text inputs from the loop in kivy

i want to print all user text inputs when i press the button. the problem when i press the button to print the inputs appear the error TypeError: p() takes 1 positional argument but 2 were given. just to know the number to text inputs may vary from user to other
from kivy.uix.textinput import TextInput
from kivy.app import App
from kivy.uix.button import Button
from kivy.lang.builder import Builder
kv=Builder.load_string('''
ScrollView:
GridLayout:
id:inputs
cols:1
row_force_default:True
row_default_height:30
size_hint_y:None
height:self.minimum_height
''')
class Accounting(App):
def build(self):return kv
def on_start(self):
self.return_list = [] #print this list
w = Button(text='print all the text inputs',on_press=self.p)
self.return_list.append(w)
self.root.ids.inputs.add_widget(w)
for i in range(5):
w = TextInput()
self.return_list.append(w)
self.root.ids.inputs.add_widget(w)
return self.return_list
def p(self):print(self.return_list)#here
Accounting().run()
When you click button then Kivy runs it with extra argument p(widget) so you can assign function to many buttons and you will see which button was clicked.
But it means you have to get this value in function
def p(self, widget):
Minimal working code.
If you want text only from TextInput then removed Button from return_list.
from kivy.uix.textinput import TextInput
from kivy.app import App
from kivy.uix.button import Button
from kivy.lang.builder import Builder
kv = Builder.load_string('''
ScrollView:
GridLayout:
id:inputs
cols:1
row_force_default:True
row_default_height:30
size_hint_y:None
height:self.minimum_height
''')
class Accounting(App):
def build(self):
return kv
def on_start(self):
self.return_list = [] #print this list
w = Button(text='print all the text inputs', on_press=self.p)
self.return_list.append(w)
self.root.ids.inputs.add_widget(w)
for i in range(5):
w = TextInput()
self.return_list.append(w)
self.root.ids.inputs.add_widget(w)
return self.return_list
def p(self, widget):
print('widget:', widget)
print('text :', widget.text)
for item in self.return_list:
print(item.text)
Accounting().run()

Instantiate data class from dropdown menu options using on_touch_up

I would like to populate MyData with information gathered from dropdown menus that show up in a popup window with "on_touch_up" in AddTouch. That data includes the position of "on_touch_up", in addition to the dropdown data. I am able to print the position within the AddTouch class, but I am having a hard time getting the data further down in my script using (for example: print('from MyMainApp: {}'.format(MyData.pos))).
I am also unable to get "mainbutton" or "dropdown" to show up in a popup window.
Hacking around with this I came up with the following which works, but doesn't do what i need
.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.uix.dropdown import DropDown
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.graphics import Color, Rectangle
class AddTouch(Widget):
def __init__(self, **kwargs):
super(AddTouch, self).__init__(**kwargs)
with self.canvas:
Color(1, 0, 0, 0.5, mode="rgba")
self.rect = Rectangle(pos=(0, 0), size=(10, 10))
def on_touch_down(self, touch):
self.rect.pos = touch.pos
def on_touch_move(self, touch):
self.rect.pos = touch.pos
def on_touch_up(self, touch):
# final position
self.pos = touch.pos
print(self.pos)
class MyPopup(Popup):
def __init__(self, **kwargs):
super(MyPopup, self).__init__(**kwargs)
# create a main button
self.mainbutton = Button(text='Hello', size_hint=(None, None))
# create a dropdown with 10 buttons
self.dropdown = DropDown()
for index in range(10):
btn = Button(text='Value %d' % index, size_hint_y=None, height=44)
btn.bind(on_release=lambda btn: self.dropdown.select(btn.text))
self.dropdown.add_widget(btn)
self.mainbutton.bind(on_release=self.dropdown.open)
self.dropdown.bind(on_select=lambda instance, x: setattr(self.mainbutton, 'text', x))
class MyData:
def __init__(self, **kwargs):
super(MyData, self).__init__(**kwargs)
self.pos=AddTouch.pos
# using kivy screen for consistency
class MainWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
kv = Builder.load_file("dropd.kv")
class MyMainApp(App):
def build(self):
return kv
print('from MyMainApp: {}'.format(MyData.pos))
if __name__ == "__main__":
MyMainApp().run()
.kv
WindowManager:
MainWindow:
<MainWindow>:
name: "main"
AddTouch:
on_touch_up:
#MyPopup gives 'MyPopup' is not defined, even if I add <MyPopup>: below
#root.MyPopup gives 'MainWindow' object has no attribute 'MyPopup'
I tried adding a simple dynamic Popup class in the .kv file based on this, but again it says 'MyPopup' is not defined:
.kv
AddTouch
on_touch_up:
MyPopup
<MyPopup#Popup>:
auto_dismiss: False
Button:
text: 'Close me!'
on_release: root.dismiss()
What am I missing (other than experience, ability, and general intelligence)?
In addition to adding the line:
self.content = self.mainbutton
to the MyPopup __init__() method, you can trigger the MyPopup creation by modifying your .kv file as:
#:import Factory kivy.factory.Factory
WindowManager:
MainWindow:
<MainWindow>:
name: "main"
AddTouch:
on_touch_up:
Factory.MyPopup().open()

How to update Button text in Kivy

I'm making an MP3 Player for a project using Kivy. I am having issues on updating the text of a Button.
The only method that I've used and successfully worked was to update the button text directly, but I want to update a variable that is what the button's text is.
Here's the minimum reproducible example:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
class FirstKivy(App):
def __init__(self, **kwargs):
super(FirstKivy, self).__init__(**kwargs)
self.pausePlay = "Play"
def build(self):
layout = BoxLayout(orientation = "vertical")
btn = Button(text = self.pausePlay)
btn.bind(on_press = self.changePausePlay)
layout.add_widget(btn)
return layout
def changePausePlay(self, button):
if self.pausePlay == "Play":
self.pausePlay = "Pause"
elif self.pausePlay == "Pause":
self.pausePlay = "Play"
FirstKivy().run()
I expect the button's text to change from "Play" to "Pause" on click and then from "Pause" to "Play on click again. No error messages are sent.
Any help is appreciated, I'm new to Kivy as well as OOP in Python.
The easiest way to do it is to use kv to build the gui with a StringProperty to hold the Button text:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import StringProperty
kv = '''
BoxLayout:
orientation: 'vertical'
Button:
text: app.pausePlay
on_press: app.changePausePlay(self)
'''
class FirstKivy(App):
pausePlay = StringProperty('Play')
def __init__(self, **kwargs):
super(FirstKivy, self).__init__(**kwargs)
def build(self):
layout = Builder.load_string(kv)
return layout
def changePausePlay(self, button):
if self.pausePlay == "Play":
self.pausePlay = "Pause"
elif self.pausePlay == "Pause":
self.pausePlay = "Play"
FirstKivy().run()
Some key points. The kv language automatically sets up bindings when it can (creating the same gui in Python does not). The StringProperty allows kv to set up the binding so that any change in pausePlay will be reflected in the Button text.

Python/Kivy passing variables

Struggling to pass a variable to kivy window. I have read similar threads all over the place but none of the fixes seem to work for me. Im sure this is simple to someone who knows their way around tiny, unfortunately I don't.
main.py
import kivy
from kivy.uix.togglebutton import ToggleButton
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.app import App
kivy.require('1.10.0')
from phue import Bridge
import nest
b = Bridge('xxx.xxx.x.xxx')
b.connect()
b.get_api()
lights = b.lights
class Controller(GridLayout):
print("launching")
def __init__(self):
super(Controller, self).__init__()
def KitchenSpot1(self,state):
lights[0].name
lights[0].on = state
def update(dt):
if b.get_light(1, 'on')== True:
#print("down") # When this line is commented out I get an continuous accurate update on the status of the light, showing that its working.
return 'down' # This is the part I want passed to the state criteria in the ivy window
else:
#print("up")# When this line is commented out I get an continuous accurate update on the status of the light, showing that its working.
return 'down' # This is the part I want passed to the state criteria in the ivy window
class ActionApp(App):
def build(self):
Clock.schedule_interval(Controller.update, 1.0 / 60.0)
return Controller()
myApp = ActionApp()
myApp.run()
action.kv
<Controller>:
cols: 4
rows: 3
spacing: 10
ToggleButton:
id: KitchenSpot1Toggle
text: "Kitchen Spot 1"
on_press: root.KitchenSpot1(True)
#on_release: root.KitchenSpot1(False)
#state1 = app.update.h
state: Controller.update # This is the part that is throwing up the error.
The error:
11: #on_release: root.KitchenSpot1(False)
12: #state1 = app.update.h
>> 13: state: Controller.update
14:
15:
...
NameError: name 'Controller' is not defined
Thanks in advance to anyone that can help me.
Make update an instance method and use a StringProperty to update state property in your kv:
main.py:
import kivy
kivy.require('1.10.0')
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.uix.gridlayout import GridLayout
from kivy.uix.togglebutton import ToggleButton
from phue import Bridge
import nest
b = Bridge('xxx.xxx.x.xxx')
b.connect()
b.get_api()
lights = b.lights
class Controller(GridLayout):
state = StringProperty('normal') # <<<<<<<<<<<<
def __init__(self, **kwargs):
super(Controller, self).__init__(**kwargs)
Clock.schedule_interval(self.update, 1.0 / 60.0)
def KitchenSpot1(self,state):
lights[0].name
lights[0].on = state
def update(self, dt):
if b.get_light(1, 'on'):
self.state = 'down' # <<<<<<<<<<<<
else:
self.state = 'normal' # <<<<<<<<<<<<
class ActionApp(App):
def build(self):
return Controller()
if __name__ == "__main__":
myApp = ActionApp()
myApp.run()
action.kv:
<Controller>:
cols: 4
rows: 3
spacing: 10
state: "normal" # <<<<<<<<<<<<
ToggleButton:
id: KitchenSpot1Toggle
text: "Kitchen Spot 1"
on_press: root.KitchenSpot1(True)
#on_release: root.KitchenSpot1(False)
#state1 = app.update.h
state: root.state # <<<<<<<<<<<<
Here is a more generic simplified answer from the kivy documentation, look for the section called "Keyword arguments and init()" because there are some other ways to do it as well.
The following code passes myvar to the build() method of MyApp. It does this by over-riding the init() of the Kivy App class by a new init() that calls App.init() and then continues with whatever extra initialisation you want. You can then store variables in the MyApp class instances and use them in build().
from kivy.app import App
from kivy.uix.label import Label
myvar = 'Hello Kivy'
class MyApp(App):
def __init__(self, myvar, **kwargs):
super(MyApp, self).__init__(**kwargs)
self.myvar = myvar
def build(self):
widget = Label(text=self.myvar)
return widget
if __name__ == '__main__':
MyApp(myvar).run()

Categories