Play/stop music with classes in kivy - python

I'm trying to execute this:
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.core.audio import SoundLoader,Sound
from kivy.lang import Builder
Builder.load_string('''
<MenuPage>:
BoxLayout:
orientation:'vertical'
Button:
text:'play'
on_press:root.plays()
Button:
text:'stop'
on_press:root.stops()
''')
class Music(Sound):
def __init__(self):
self.sound = SoundLoader.load('/home/hosein/Music/Man.mp3')
class MenuPage(Screen):
def __init__(self):
self.M = Music()
def plays(self):
self.M.play()
def stops(self):
self.M.stop()
music = Music()
sm = ScreenManager()
menu = MenuPage(name='menu')
sm.add_widget(menu)
class TestApp(App):
def build(self):
return sm
TestApp().run()
https://gist.github.com/daryasary/f69e1d0444ae70ff5296
There should be just two buttons to play or stop a song.
But it doesn't work. What is the solution?
Also, is there any way to make the play and stop buttons into a single button, where the first touch plays the song and the second stops it?

If you want to use one button, you could try using Kivy's ToggleButton and play music when the toggle button's state is 'down' and not play music when the state is 'normal'.
<MenuPage>:
BoxLayout:
orientation:'vertical'
ToggleButton:
id: music_button
text:'play'
on_press:root.play_or_stop()
''')
class MenuPage(Screen):
def __init__(self):
self.M = Music()
def play_or_stop(self):
if self.music_button.state == 'down':
self.music_button.text = "Stop"
self.M.play()
else:
self.music_button.text = "Play"
self.M.stop()
Or, you could use a regular button that sets a variable to either True or False each time it's pressed. You can then have this value determine whether the music plays or stops.

The problem is that you're using Sound wrong. You should not be subclassing Sound or trying to create a new instance directly.
SoundLoader.load returns a Sound instance created using one of the available audio providers - this should be used instead. Try something like this:
class MenuPage(Screen):
def __init__(self):
self.M = SoundLoader.load('/home/hosein/Music/Man.mp3')
def plays(self):
self.M.play()
def stops(self):
self.M.stop()
def toggle(self):
self.M.state = 'play' if self.M.state == 'stop' else 'play'
return self.M.state

it should be something like this:
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.core.audio import SoundLoader,Sound
from kivy.lang import Builder
Builder.load_string('''
<MenuPage>:
BoxLayout:
orientation:'vertical'
Button:
text:'song'
on_press:root.plays()
''')
class MenuPage(Screen):
M = SoundLoader.load('/home/hosein/Music/Man.mp3')
def plays(self):
if MenuPage.M.state == 'stop':
MenuPage.M.play()
else:
MenuPage.M.stop()
sm = ScreenManager()
menu = MenuPage(name='menu')
sm.add_widget(menu)
class TestApp(App):
def build(self):
return sm
TestApp().run()

Related

How to clear the text field after performing??? KivyMD

After performing an operation, for example, receiving text and closing the window, when opening the window in the text field text.
It is necessary that after closing the mdDialog window, the text is erased. I manage to save it, but in the string method. It must be erased by pressing the button and closing.
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.button import MDFlatButton
from kivymd.uix.dialog import MDDialog
from kivymd.uix.boxlayout import BoxLayout
KV = '''
<Content>
tes:tes
orientation: 'vertical'
spacing: '12dp'
size_hint_y: None
height: '120dp'
MDTextField:
id:tes
on_text:app.inputs(self)
MDFloatLayout:
MDFloatingActionButton:
icon:'plus'
pos_hint:{'center_y': .1, 'center_x':.5}
on_release:app.new()
'''
class Content(BoxLayout):
pass
class General(MDApp):
dialog=None
def build(self):
return Builder.load_string(KV)
def inputs(self, event):
self.txt = event.text
print(self.txt)
def no(self, event):
event.text=''
self.dialog.dismiss()
def yes(self, event):
event.text=''
self.dialog.dismiss()
def new(self):
if not self.dialog:
self.dialog = MDDialog(
type='custom',
content_cls=Content(),
buttons = [
MDFlatButton(text='Отмена', text_color=self.theme_cls.primary_color, on_release=self.no),
MDFlatButton(text='Добавить',text_color=self.theme_cls.primary_color, on_press=self.yes),
]
)
self.dialog.open()
General().run()
Good day.
I recommend you create text_field_text = StringProperty("My Default String") in your App class and let the widget.text = app.text_field_text.
From there, you could clear your app string property with the callback function.

Kivy - How to access instance of class created by .kv file?

My class OrderManagementScreen is firstly instantiated by Builder.load_file and then by sm.add_widget(OrderManagementScreen(name='order_management')).
I would like to be able to access the instance created by Builder.load_file - how can I do this?
This post is the closest I could find, but when I do MDApp.get_running_app().root it returns None.
app.py: (mixture of attempts in my update_database function)
import atexit
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from order_management.constants import HELP_TEXT
from order_management.presentation.order_management.order_management_view import OrderManagementScreen
from order_management.presentation.components.dialog import Dialog
from order_management.data.query import Query
class App(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.screen = Builder.load_file(
"order_management/presentation/main.kv")
self._query = Query()
something = MDApp.get_running_app()
self._dialog = Dialog()
def build(self):
self.theme_cls.primary_palette = "Green"
sm = ScreenManager()
# Instantiates OrderManagementScreen.py
sm.add_widget(OrderManagementScreen(name='order_management'))
return self.screen
def handle_menu_click(self):
self._dialog.render_dialog("Usage Guide:", HELP_TEXT, None, None)
def update_database(self):
# order_management_screen = MDApp.get_running_app().screen.children[0].manager.get_screen('order_management')
# order_management_screen = self.screen.get_screen('order_management')
self.order_management_screen.update_database()
if __name__ == "__main__":
App().run()
main_app = App()
atexit.register(main_app.update_database) # Calls update_database on exit.
main.kv:
#:kivy 1.11.0
#:include order_management/presentation/order_management/order_management_ui.kv
# Having to use v1.11.0 because of bug relating to MDDataTable
<Toolbar#AnchorLayout>:
AnchorLayout:
anchor_x: 'center'
anchor_y: 'top'
MDToolbar:
title: 'Order Management App'
specific_text_color: app.theme_cls.accent_color
right_action_items: [['help-circle', lambda x: app.handle_menu_click()]]
elevation: 9
ScreenManager:
OrderManagementScreen:
Toolbar:
order_management_ui.kv:
#:kivy 1.11.0
# Having to use v1.11.0 because of bug relating to MDDataTable
<OrderManagementScreen>:
name: 'order_management'
id: order_management
GridLayout:
cols: 1
pos_hint: {'top': 0.85}
ScrollView:
id: table_container
I guess it goes to show how sometimes you just need to step away from the computer and come back with fresh eyes...
My solution was to simply store the instance of app when run so that when I called my update_database function is was looking at the correct instance - and therefore could access the instance of a screen class the kivy had created with:
self.root.children[0].manager.get_screen('order_management')
I use children[0] here because this is the first screen I added to ScreenManager, and 'order_management' is the name of the screen obviously.
I hope that this by chance does help someone else.
New app.py:
import atexit
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from order_management.constants import HELP_TEXT
from order_management.presentation.order_management.order_management_view import OrderManagementScreen
from order_management.presentation.components.dialog import Dialog
from order_management.data.query import Query
class App(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.screen = Builder.load_file(
"order_management/presentation/main.kv")
self._query = Query()
self._dialog = Dialog()
def build(self):
self.theme_cls.primary_palette = "Green"
sm = ScreenManager()
sm.add_widget(OrderManagementScreen(name='order_management'))
return self.screen
def handle_menu_click(self):
self._dialog.render_dialog("Usage Guide:", HELP_TEXT, None, None)
def update_database(self):
order_management_screen = self.root.children[0].manager.get_screen('order_management')
order_management_screen.update_database()
if __name__ == "__main__":
app = App()
app.run()
atexit.register(app.update_database) # Calls update_database on exit.

How can I change screen using bottom sheet in kivymd?

I have made a simple app in kivymd. But I can not change screen on click on button inside kivymd. Everything works great. But when I click on button then it popup toast also but screen is not changing. What will be changes or better implementation for this?
app.py
from kivymd.app import MDApp
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.lang import Builder
from main_screen_str import helper_string
from kivy.core.window import Window
from kivymd.toast import toast
from kivymd.uix.bottomsheet import MDGridBottomSheet
Window.size = (300, 500)
class MainScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class AboutScreen(Screen):
pass
class MainApp(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.sm = ScreenManager()
self.sm.add_widget(MainScreen(name="main_screen"))
self.sm.add_widget(SettingsScreen(name="settings_screen"))
self.sm.add_widget(AboutScreen(name="about_screen"))
self.main_str = Builder.load_string(helper_string)
def build(self):
screen = Screen()
screen.add_widget(self.main_str)
return screen
def callback_for_menu_items(self, *args):
if args[0] == 'Home':
toast(args[0])
self.sm.current = "main_screen"
if args[0] == 'Settings':
toast(args[0])
self.sm.current = "settings_screen"
if args[0] == 'About':
toast(args[0])
self.sm.current = "about_screen"
def show_example_grid_bottom_sheet(self):
self.bottom_sheet_menu = MDGridBottomSheet()
data = {
"Home": "home",
"Settings": "settings",
"About": "information-outline",
}
for item in data.items():
self.bottom_sheet_menu.add_item(
item[0],
lambda x, y=item[0]: self.callback_for_menu_items(y),
icon_src=item[1],
)
self.bottom_sheet_menu.open()
if __name__ == '__main__':
MainApp().run()
This builder string to create screen.
Are there any better solution for this?
builder string
helper_string = """
ScreenManager:
MainScreen:
SettingsScreen:
AboutScreen:
<MainScreen>:
name: 'main_screen'
MDIconButton:
icon: "menu"
theme_text_color: "Custom"
text_color: 1,0,0,1
on_press: app.show_example_grid_bottom_sheet()
<SettingsScreen>:
name: 'settings_screen'
<AboutScreen>:
name: 'about_screen'
"""
In your __init__() method of the App, you are building self.sm with the lines:
self.sm = ScreenManager()
self.sm.add_widget(MainScreen(name="main_screen"))
self.sm.add_widget(SettingsScreen(name="settings_screen"))
self.sm.add_widget(AboutScreen(name="about_screen"))
But self.sm is not used as part of your GUI. So your changes to self.sm has no effect on your GUI. The line following that:
self.main_str = Builder.load_string(helper_string)
basically does exactly the same thing as the previous lines.
Then in your build() method, you are creating a new Screen and adding the self.main_str as a child of that Screen.
While you can have a ScreenManager as a child of a Screen, in your posted example that does not seem to serve any purpose.
Here is a modified version of part of the MainApp that I think will do what you want:
class MainApp(MDApp):
# def __init__(self, **kwargs):
# super().__init__(**kwargs)
# self.sm = ScreenManager()
# self.sm.add_widget(MainScreen(name="main_screen"))
# self.sm.add_widget(SettingsScreen(name="settings_screen"))
# self.sm.add_widget(AboutScreen(name="about_screen"))
#
# self.main_str = Builder.load_string(helper_string)
def build(self):
self.sm = Builder.load_string(helper_string)
return self.sm
# screen = Screen()
# screen.add_widget(self.main_str)
# return screen
The above code greatly simplifies the build() method, eliminates the __init__() method, and now self.sm is actually part of the GUI.
Note that when you load a kv string that has a root node with Builder.load_string(), that root node is created and returned. The lines in your kv string:
ScreenManager:
MainScreen:
SettingsScreen:
AboutScreen:
result in a ScreenManager instance being created along with the three children listed for it, so the code in your __init__() method was duplicating that.

Lost focus by Enter Key detection (Python-Kivy)

I want to enhance usability of my Python-Kivy program. In the following program example, I want to allow users to operate it even if they don't have mouse. (by keyboard input.)
(1) In the first dialog (MyLayout2), user input in Textbox easily as the textbox has focus
(2) Hit Enter keyboard key to go to next dialog (MyLayout1)
(3) Hit Enter keyboard key to go to (1) (MyLayout2 again)
However in the 2nd (1), after (3), the focus of Textbox is lost. Any idea how to cope with this problem?
Main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.textinput import TextInput
from kivy.core.window import Window
sm = ScreenManager()
class MyLayout1(BoxLayout):
pass
class MyLayout2(BoxLayout):
pass
class MyScreen(Screen):
dialog_number = 0
def __init__(self, **kwargs):
super(MyScreen, self).__init__(**kwargs)
self.gridlayout = None
self.gridlayout = MyLayout2()
self.add_widget(self.gridlayout)
Window.bind(on_key_down=self._keydown)
def _keydown(self,*args):
if (args[2] == 40):
if self.dialog_number == 0:
self.button2_clicked()
elif self.dialog_number == 1:
self.button1_clicked()
def _create_layout(self):
if self.gridlayout is not None:
self.remove_widget(self.gridlayout)
self.add_widget(self.gridlayout)
def button1_clicked(self, *args):
if self.gridlayout is not None:
self.remove_widget(self.gridlayout)
self.gridlayout = MyLayout2()
self.add_widget(self.gridlayout)
self.dialog_number = 0
def button2_clicked(self, *args):
if self.gridlayout is not None:
self.remove_widget(self.gridlayout)
self.gridlayout = MyLayout1()
self.add_widget(self.gridlayout)
self.dialog_number = 1
def find_instance(self, layout):
c = None
for c in list(self.children):
if isinstance(c, layout):
break
return c
class myApp(App):
def build(self):
self.anschoi = MyScreen(name = 'anschoi')
sm.add_widget(self.anschoi)
sm.current = 'anschoi'
return sm
if __name__ == '__main__':
myApp().run()
my.kv
<MyScreen>:
BoxLayout:
orientation: 'vertical'
padding: 10,40,10,40
spacing: 40
<MyLayout1>:
Button1:
id: btn1
text: 'OK or ENTER key'
on_release: root.parent.button1_clicked()
<MyLayout2>:
txtinput: txtinput
orientation: 'vertical'
TextInput:
id: txtinput
text: ''
multiline: False
focus: True
button2:
id:Button2
text: 'OK or ENTER key'
on_release: root.parent.button2_clicked()
<Button0#Button>:
<Button1#Button>:
<button2#Button>:
This is exactly the same problem as your previous question. Just change:
Window.bind(on_key_down=self._keydown)
to:
Window.bind(on_key_up=self._keydown)
Again, to avoid the on_key_up event (that almost always follows a on_key_down event) stealing the focus from your TextInput.

(Kivy Python) Resetting ScreenManager Screen Direction After Instance

I am trying to figure out how to only change the screen direction animation in kivy for one certain button press(cancel button which should wipe right instead of left like a forward-navigating button.) Here is what I am doing currently to make this happen:
# Cancel Button
self.cancel = Button(text="Cancel", height=30, width=90,size_hint=(None, None),pos=(300, 350))
self.cancel.bind(on_release=self.BackFunction)
self.ids.float_web.add_widget(self.cancel)
def BackFunction(self, *args):
self.manager.transition.direction = 'right'
self.manager.current = ('input_sc')
I am wondering how to reset the direction to left after this animation happens. If I follow the screen change with
self.manager.transition.direction = 'left'
then it simply makes the direction left inside of BackFunction
Thanks
I think it's best to define the direction property each time you change thecurrent attribute. However, you can do what you want to do binding a function to on_complete event and unbind within the function itself:
Example:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
kv_text='''\
#:import SlideTransition kivy.uix.screenmanager.SlideTransition
<MyScreenManager>:
transition: SlideTransition()
FirstScreen:
LastScreen:
<FirstScreen#Screen>:
name: 'first_sc'
BoxLayout:
Button:
text: 'Next'
on_release: root.next_screen()
<LastScreen#Screen>:
name: 'last_sc'
BoxLayout:
Button:
text: 'Previous'
on_release: root.previous_screen()
'''
class MyScreenManager(ScreenManager):
pass
class FirstScreen(Screen):
def __init__(self, **kwargs):
super(FirstScreen, self).__init__(**kwargs)
def next_screen(self, *args):
self.manager.current = 'last_sc'
class LastScreen(Screen):
def __init__(self, **kwargs):
super(LastScreen, self).__init__(**kwargs)
def previous_screen(self, *args):
self.manager.transition.direction = 'right'
self.manager.current = 'first_sc'
self.manager.transition.bind(on_complete=self.restart)
def restart(self, *args):
self.manager.transition.direction = 'left'
self.manager.transition.unbind(on_complete=self.restart)
class MySubApp(App):
def build(self):
return MyScreenManager()
def main():
Builder.load_string(kv_text)
app = MySubApp()
app.run()
if __name__ == '__main__':
main()

Categories

Resources