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()
Related
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.
I just started to learn Kivy so I am still familiar with its functionalities.
I am trying to put an image as a background to my app main page. This is what I did:
class Prin(BoxLayout):
def __init__(self,**kwargs):
super(Prin,self).__init__(**kwargs)
layout = BoxLayout(orientation='vertical')
with self.canvas:
self.rect = Rectangle(source='test.png', pos=layout.center, size=(self.width, self.height))
self.text = Label(text='Press start')
fb = Button(text='Start!', size_hint =(0.5, 0.1), pos_hint ={'center_x':.5, 'y':.5}, padding=(10, 0), on_press=self.start)
layout.add_widget(self.text)
layout.add_widget(fb)
self.add_widget(layout)
def start(self,event):
self.text.text = self.text.text+ "\nNew line"
class MainApp(App):
def build(self):
return Prin()
if __name__ == "__main__":
app = MainApp()
app.run()
The desired behavior is an image covering the whole screen, that's why I've put pos=self.center, size=(self.width, self.height)
This is the output:
So I have two questions:
1/ Why is the image appearing in the left bottom side ? What widget is actually there ? I am supposed to have only a BoxLayout with 2 widgets in a vertical orientation. I don't understand what is there.
2/ What should why put in size and pos to have the desired output ?
I would recommend putting all graphic elements in a .kv file, so there are fewer imports and it looks better.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
KV = ("""
<Prin>
BoxLayout:
orientation: 'vertical'
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: 'test.png'
Label:
id: label
text: 'TEXT'
Button:
text: 'Start!'
size_hint: None, None
size_hint: 0.5, 0.1
pos_hint: {'center_x': .5, 'center_y': .5}
padding: 10, 0
on_press: root.start()
""")
class Prin(BoxLayout):
Builder.load_string(KV)
def __init__(self, **kwargs):
super(Prin, self).__init__(**kwargs)
def start(self):
self.ids.label.text += "\nNew line"
class MainApp(App):
def build(self):
return Prin()
if __name__ == "__main__":
app = MainApp()
app.run()
If you still want to do this not in the kv file, then the problem is in self.size, by default, this value is [100, 100], only after calling the class and adding it to the main window it changes.
from kivy.core.window import Window
class Prin(BoxLayout):
def __init__(self, **kwargs):
super(Prin, self).__init__(**kwargs)
with self.canvas.before:
Rectangle(source='test.png', pos=self.pos, size=Window.size)
print(self.size) # [100, 100]
...
class MainApp(App):
def build(self):
self.screen = Prin()
return self.screen
def on_start(self):
print(self.screen.size) # [800, 600]
And don't forget about imports when you ask a question, the code should run without any manipulation
In response to your questions:
The image is appearing in that position because pos=layout.center is not a valid position and so instead sets it to a default value ([100, 100] I believe). To fix this, change pos=layout.center to pos=layout.pos
Your size is the default value also! This is getting a little technical but when you initialise your Prin class you are specifying the size of the Rectangle to be the current size of the BoxLayout. However, since it has not been initialised yet, the BoxLayout doesn't yet have a size! Again, Kivy handles this by giving it a default size.
Why are my Buttons and Labels correct?
Kivy automatically binds the children of a BoxLayout to the size and position of the BoxLayout. This binding ensures that when the position and size of the BoxLayout are changed, so too are the widgets within it (https://kivy.org/doc/stable/api-kivy.event.html).
Why doesn't Kivy bind the rectangle?
This has something to do with the canvas. The canvas is a drawing instruction shared by a widget, and not a property of any individual widget. Hence you'll programmatically bind your rectangle to the BoxLayout. (https://kivy.org/doc/stable/api-kivy.graphics.instructions.html)
How do I achieve this binding you speak of?
Two ways. Firstly (preferred), you can define your widgets in the KV language as this will automatically handle any binding you wish. Second, you can create an 'on_size' callback. Something like:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.graphics import Rectangle
class Prin(BoxLayout):
def __init__(self, **kwargs):
super(Prin, self).__init__(**kwargs)
layout = BoxLayout(orientation='vertical')
with self.canvas:
self.rect = Rectangle(source='test.png', pos=layout.pos, size=self.size)
self.text = Label(text='Press start')
fb = Button(text='Start!', size_hint=(0.5, 0.1), pos_hint={'center_x': .5, 'y': .5}, padding=(10, 0),
on_press=self.start)
layout.add_widget(self.text)
layout.add_widget(fb)
self.add_widget(layout)
def start(self, *_):
self.text.text = self.text.text + "\nNew line"
def resize(self, *_):
widgets = self.children[:]
self.canvas.clear()
self.clear_widgets()
with self.canvas:
self.rect = Rectangle(source='test.png', pos=self.pos, size=self.size)
for widget in widgets:
self.add_widget(widget)
on_size = resize
class TestApp(App):
def build(self):
return Prin()
if __name__ == "__main__":
app = TestApp()
app.run()
I just would like to add as a BIG P.S. although the above code solves your problem, it does so in probably the least efficient way imaginable. It is far better to define your widget in the kv file.
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.
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.
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()