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.
Related
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.
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.
What I want to do is take input from kivy.uix.textinput.TextInput() and show it on the screen.
I am new to gui Programming and I think it is an easy task.
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.textinput import TextInput
class MyWindowApp(App):
def __init__(self):
super(MyWindowApp, self).__init__()
self.lbl = Label(text='Read Me!')
self.inp = TextInput(multiline=False,
size_hint =(1, 0.05),
pos_hint = {"x":0, "y":0.05})
def build(self):
self.inp.bind(on_text_validate=self.on_enter)
#self.bt1.bind(on_press=self.clk)
layout = FloatLayout()
layout.orientation = 'vertical'
layout.add_widget(self.lbl)
layout.add_widget(self.inp)
return layout
def on_enter(self,value):
print(value)
def clk(self, obj):
print ('input')
x = input()
self.lbl.text = x
window = MyWindowApp()
window.run()
when i run the code, I get the regular output output.
when I type say "hello world" in the textbox, this is the output:
<kivy.uix.textinput.TextInput object at 0x03F5AE30>
I do not get what I typed.
please suggest what should I do
Modify the following ...
def on_enter(self, value):
print(value.text)
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()
I try to build a toggle between two or more labels like radio buttons. It work so far with the ToggleButtonBehavior to change the state between the group but when I click the selected item it get deselected and should not do it.
class SelectButton(ToggleButtonBehavior, Label):
active = BooleanProperty(False)
def __init__(self, **kwargs):
super(SelectButton, self).__init__(**kwargs)
self.text='off'
self.color=[1,0,1,1]
self.bind(state=self.toggle)
def toggle(self, instance, value):
if self.state == 'down':
self.text = 'on'
else:
self.text = 'off'
Is there a way to get a behavior like a radio button?
There is a allow_no_selection property:
This specifies whether the widgets in a group allow no selection i.e.
everything to be deselected.
allow_no_selection is a BooleanProperty and defaults to True
After setting it to False and using a group everything starts to work as intended:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.behaviors import ToggleButtonBehavior
from kivy.properties import BooleanProperty
from kivy.lang import Builder
Builder.load_string('''
<MyWidget>:
SelectButton:
state: 'down'
text: 'on'
SelectButton
state: 'normal'
text: 'off'
SelectButton
state: 'normal'
text: 'off'
''')
class SelectButton(ToggleButtonBehavior, Label):
active = BooleanProperty(False)
def __init__(self, **kwargs):
super(SelectButton, self).__init__(**kwargs)
self.allow_no_selection = False
self.group = "mygroup"
self.color=[1,0,1,1]
self.bind(state=self.toggle)
def toggle(self, instance, value):
if self.state == 'down':
self.text = 'on'
else:
self.text = 'off'
class MyWidget(BoxLayout):
pass
class MyApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
MyApp().run()