Kivy: dismiss() doesn't work on my ModalView widget - python

I am making a ModalView widget and trying to close it by pressing a button on it. The callback of the button press goes to method that suppose to close it.
This is main.kv:
<MainFrame>:
id: main_frame
ScreenMaster:
id: screen_master
StartScreen:
id: start_screen
SettingsPopup:
id: settings_popup
GameScreen:
id: game_screen
GameOverPopup:
id: gameover_popup
And gameoverpopup.kv:
<GameOverPopup>:
auto_dismiss: False
pos_hint: {'center_x': .5, 'center_y': .5}
size_hint: .7, .4
RelativeLayout:
Button:
id: close_button
pos_hint: {'x': .1, 'y': .05}
size_hint: .8, .2
text: 'PLAY AGAIN'
on_press: root.done()
Label:
pos_hint: {'x': .2, 'y': .8}
size_hint: .6, .15
font_size: 32
text: 'YOU WON'
And main.py:
# kivy includes
Builder.load_file('startscreen.kv')
Builder.load_file('gamescreen.kv')
Builder.load_file('settingspopup.kv')
Builder.load_file('gameoverpopup.kv')
class StartScreen(Screen):
pass
class GameScreen(Screen):
pass
class ScreenMaster(ScreenManager):
pass
class SettingsPopup(ModalView):
pass
class GameOverPopup(ModalView):
def done(self):
self.dismiss()
class MainFrame(AnchorLayout):
pass
class MainApp(App):
def on_pause(self):
return True
def build(self):
return MainFrame()
if __name__ == '__main__':
MainApp().run()
Pressing Play again button calls done() method, but the ModalView widget doesn't dismiss. How can I solve this problem and make it disappear?

Have found a solution myself:
<MainFrame>:
id: main_frame
ScreenMaster:
id: screen_master
StartScreen:
id: start_screen
SettingsPopup:
id: settings_popup
on_parent: if self.parent == start_screen: start_screen.remove_widget(self)
GameScreen:
id: game_screen
GameOverPopup:
id: gameover_popup
on_parent: if self.parent == game_screen: game_screen.remove_widget(self)
So we instantiate those popups and close them right away. Now we can open them back up anytime we want.

ModalView or anything that inherits from it (Popup) aren't meant to be added as a child to anywhere. By default the ModalView is attached to a Window directly as it's mentioned in the docs when you call its open() method.
When you add it as a child somewhere, you'll automatically make it visible, which is bad because you misuse the purpose of that widget.
Instead of that you should just make it as a class/rule somewhere and call open() method when you need it to show. When you need to dismiss it manually, then you'll need to attach some method to on_open to store your instance of the ModalView class somewhere and call dismiss() through that instance or put a button somewhere on ModalView.
By your answer you only monkeypatch your mistake of misusing the widget. :)
There are two ways how to open a ModalView(or Popup):
python:
SettingsPopup().open()
kv:
#:import Factory kivy.factory.Factory
Factory.SettingsPopup().open()

Related

custom widget triggers another custom widget's on_touch_down event

Whenever I press any area in MyWidget then it trigger the first MyButton 's on_touch_down event inside MyLayout class.
I am new to kivy and don't understand why this is happening.
<MyButton#Button>:
background_normal: ''
background_color: [1,0,1,1]
font_size: "44dp"
color:[1,1,0,1]
border:(1,1,1,1)
text_size:self.size
<MyWidget#Widget>:
id:customwidget
canvas.before:
Color:
rgba:0.2,0.44,0.66,1
Rectangle:
pos:self.pos
size:self.size
<MyLayout>:
MyWidget:
id:mywidget
MyButton:
id:button1
text: "User Name"
spacing: "10dp"
on_touch_down: button1.text= self.text + "?"
opacity: .8
MyButton:
text: "ikinci"
on_touch_down: root.export_to_png("filename.png")
MyButton:
text: "ucuncu"
And this is python:
import kivy
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.widget import Widget
class MyLayout(BoxLayout):
pass
class KivyApp(App):
def build(self):
return MyLayout()
if __name__ == "__main__":
KivyApp().run()
Python code
Create a class MyButton() and implement on_touch_down() method to check for collision of the touch with our widget.
Snippet - Python code
class MyButton(Button):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
print("\tMyButton.on_touch_down:")
if self.text == 'User Name':
self.text= self.text + "?"
elif self.text == 'ikinci':
App.get_running_app().root.export_to_png("filename.png")
return True
return super(MyButton, self).on_touch_down(touch)
kv file
Rename dynamic class <MyButton#Button>: to classs rule, <MyButton>:
Remove all on_touch_down: from MyButton:
Snippet - kv file
<MyButton>:
background_normal: ''
background_color: [1,0,1,1]
font_size: "44dp"
color:[1,1,0,1]
border:(1,1,1,1)
text_size:self.size
...
MyButton:
id:button1
text: "User Name"
spacing: "10dp"
opacity: .8
MyButton:
text: "ikinci"
MyButton:
text: "ucuncu"
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
Output
The on_touch events are not specific to any widget, but are propagated to all your widgets. See the documentation. If you only want your Button to respond, you probably want to use on_pressor on_release events instead (See button behavior).

Python/Kivy - Replace value of a label in another screen calling a function

I have an app with 2 screens (ScreenManager).
I wrote a function in the second screen (SettingsScreen) and I want this function to update a label in the first screen.
Below the two classes:
class MenuScreen(Screen):
count = NumericProperty(30)
class SettingsScreen(Screen):
def set_20(self):
self.count = 20
The classes are linked to a button and a label in the Kivy stylesheet with two different screens
<MenuScreen>:
FloatLayout:
orientation: 'horizontal'
Label:
id: l_label
text: str(root.count)
<SettingsScreen>:
BoxLayout:
orientation: 'vertical'
padding: 50
FloatLayout:
Button:
text: "20"
on_release: root.set_20()
To be clearer, the user has to click on the Button in the SettingsScreen to set the value of the NumericProperty in the first screen to 20.
At the moment, if I click on the button nothing happens.
What you see above is an extract - The full code of the app is stored below if you want to see more.
https://github.com/marcogdepinto/LifeCounter
Thanks in advance for your help.
I think the problem is that when your button gets clicked and calls the set_20 method, that method is trying to set the property count of the SettingsScreen to 20. In other words
def set_20(self):
self.count = 20
would try to set the the count property inside the SettingsScreen (hence the word self) and it cannot find it. There is another count property inside MenuScreen which it knows nothing about. To fix this I think you should give an id to MenuScreen
<MenuScreen>:
id: menu
and in the on_release method of the Button do
on_release: menu.count = 20
EXAMPLE
Python file
class MenuScreen(Screen):
counter = NumericProperty(0)
class SettingsScreen(Screen):
pass
class MyWidget(ScreenManager):
pass
class MyRandomApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
MyRandomApp().run()
kv file
<MyWidget>:
id:manager
MenuScreen:
id: menu
name: 'menuscreen'
BoxLayout:
orientation: 'vertical'
Label:
text: str(menu.counter)
Button:
text: 'Go to S2'
on_press: manager.current = 'settingsscreen'
SettingsScreen:
id: settings
name: 'settingsscreen'
BoxLayout:
orientation: 'vertical'
Button:
text: 'Click to edit label of Prev screen'
on_press: menu.counter = 20
Button:
text: 'Go to S1'
on_press: manager.current = 'menuscreen'

Calling Function in a Different Class Through Kivy Button

I am trying to call a function on button press in kivy, that is located in a different class screen than the button is located in. I tried running the function in the app class as well and ran into issues there. Here is the class where the function I am trying to call lies:
# Main screen with button layout
class LandingScreen(Screen):
def __init__(self, **kwargs):
super(LandingScreen, self).__init__(**kwargs)
self.buttons = [] # add references to all buttons here
Clock.schedule_once(self._finish_init)
def ChangePic(self):
self.buttons[1].background_normal = 'folder.png'
And here is the button that I am trying to call it with:
<InputScreen#Screen>:
name: 'input_sc'
FloatLayout:
size: 800, 480
id: anchor_1
Label:
text: "What would you like to bind to this button?"
size_hint: (1,.15)
text_size: self.size
pos_hint: {'x': 0.11, 'top': 1}
font_size: 28
font_name: 'Montserrat-Bold.ttf'
Button:
root: 'landing_sc'
id: filebutton
size: 150, 150
size_hint: None, None
background_normal: 'folder.png'
background_down: 'opacity.png'
pos_hint: {'x': 0.11, 'top': .7}
on_release:
root.manager.transition = FadeTransition()
root.manager.transition.duration = 1.5
app.MakeFolder()
root.IfFolder()
root.ChangeToSlide()
What do I have to prefix ChangePic() with in order to call it from this location?
Alternatively- is there a way to easily work with the buttons inside of the LandingScreen class from inside of the InputScreen class?
Thanks!
You can create a variable in your App Class:
some_variable = LandingScreen()
and then in your button call ChangePic() like this:
on_release: app.some_variable.ChangePic()
Also, this can help you: StackOverflow, Kivy's Google Group, Introduction to properties

Kivy - Change screens after pushing button in ModalView

I am having difficulty figuring out how to correctly change screens using the on_press attribute of a button inside of a ModalView widget.
On pressing the button in the ModalView, I want the screen to change to the game_screen_name defined in the Game1HomeScreen class and other GameHomeScreen classes (as is done with the NewGameButton and SavedGameButton below). This app has multiple games, so I would rather not make a call directly to Game1HomeScreen1().game_screen_name and want to instead keep it generic, so game_screen_name takes on the value of the class from which NewGamePopup is called.
What is a good way to do this?
The main.py code:
class Game1HomeScreen(Screen):
game_screen_name = 'game1_gameboard_screen_name'
class NewGamePopup(ModalView):
pass
class GamesApp(App):
sm = ScreenManager()
def show_new_game_popup(self):
p = NewGamePopup()
p.open()
def prev_screen(self):
self.sm.current = self.game_screen_name #this line does not work of course, because there is no game_screen_name variable in the NewGamePopup class.
The .kv code:
<NewGamePopup>:
size_hint: .5, .3
NewGameBoxLayout:
padding: [10,10,10,10]
orientation: 'vertical'
Label:
font_name: 'fonts/playce.ttf'
font_size: '14sp'
markup: True
text: '[color=#000000]Are you sure? Current game will be erased![/color]'
Button:
font_name: 'fonts/playce.ttf'
font_size: '14sp'
text: 'Confirm'
background_normal: 'img/red_button5.png'
background_down: 'img/red_button5.png'
size_hint_y: None
on_press: root.dismiss(); app.prev_screen()
<Game1HomeScreen>:
GeneralBoxLayout:
BannerGridLayout1:
BodyBoxLayout:
rows: 2
Image:
source: 'img/logo.png'
size_hint: (1.0,.9)
GridLayout:
cols: 2
spacing: '5dp'
padding: '5dp'
size_hint: (1.0,.1)
NewGameButton:
id: game1
on_press:
if saved_game1.disabled == False: app.show_new_game_popup()
else: root.manager.current = root.game_screen_name; saved_game1.disabled = False
SavedGameButton:
id: saved_game1
on_press: root.manager.current = root.game_screen_name;
FooterGridLayout:
ReturnButton:
text: 'Return to main menu'
on_press: root.manager.current = 'home'
Save the game screen name in a string property when the game is selected
from kivy.properties import StringProperty
....
class GamesApp(App):
game_screen_name = StringProperty('')
Then you can use the sm.current call later as needed. Too many things were left out of the code snippet in the question to create a working version; even the build method was missing.

How to unbind a property automatically binded in Kivy language?

The Kivy Language automatically creates internal binds in properties. For example, if we assign the position of the parent to the position of the child, then the position of the child is going to be updated automatically:
Widget:
Label:
pos: self.parent.pos
In this case, if we move the parent Widget, then the child is also going to move. How do I unbind the property pos from the child? I know how to unbind (properties)[http://kivy.org/docs/api-kivy.uix.widget.html#using-properties] that I bind myself but how do I unbind them if I don't know the name of the method it is bound.
Here is a small example to show what I mean. The Button Up moves the GridLayout to the top and Down to the Bottom. The Button Center center itself in the middle of the screen. My problem is that when I click Up or Down my Centered button is not anymore.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<Example>:
GridLayout:
id: _box
cols: 3
size_hint: .7, .3
pos_hint: {'center_x': .5}
x: 0
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _box.y = 0
text: "Down"
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: self.center_y = root.height/2
text: "Out of the Grid"
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _box.top = root.height
text: "Up"
""")
class Example(FloatLayout):
pass
class ExampleApp(App):
def build(self):
return Example()
if __name__ == "__main__":
ExampleApp().run()
Why do I want to do that in any case? I am using an animations on the GridLayout that constantly updates the position. The normal position of the buttons should be inside the gridlayout but once in a while one of the buttons flies over the screen and come back to the same position. The problem is that I cannot make them fly while my gridlayout is also moving because the property is bound and as soon as the button try to fly it goes back to the grid. That also means that the binding is sometimes desirable. What I want is have control of the bind and unbind.
Comments don't seem to be working right now so I'll post this as a answer.
You already have a FloatLayout(your root widget). Use that instead of
creating a new FloatLayout.
Before removing the widget from the grid.
store it's size,
set size_hint to None, None
set pos_hint to position the widget in the center.
When adding the widget to grid do the reverse.
Here's your code with these fixes::
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<Example>:
center_button: _center_button
center_widget: _center_widget
grid:_grid
GridLayout:
id: _grid
cols: 3
size_hint: .7, .3
pos_hint: {'center_x': .5}
x: 0
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _grid.y = 0
text: "Down"
Widget:
id: _center_widget
Button:
id: _center_button
pos: self.parent.pos
size: self.parent.size
on_press: root.centerize(*args)
text: "Out of the Grid"
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _grid.top = root.height
text: "Up"
""")
class Example(FloatLayout):
def centerize(self, instance):
if self.center_button.parent == self.center_widget:
_size = self.center_button.size
self.center_widget.remove_widget(self.center_button)
self.center_button.size_hint = (None, None)
self.add_widget(self.center_button)
self.center_button.pos_hint = {'center_x': .5, 'center_y':.5}
else:
self.remove_widget(self.center_button)
self.center_button.size_hint = (1, 1)
self.center_widget.add_widget(self.center_button)
self.center_button.size = self.center_widget.size
self.center_button.pos = self.center_widget.pos
class ExampleApp(App):
def build(self):
return Example()
if __name__ == "__main__":
ExampleApp().run()
Update 1:
If for whatever reason you still need to unbind the properties bound by kvlang you can do so using introspection to get a list of observers for the property. so for your case it would be something like this::
observers = self.center_widget.get_property_observers('pos')
print('list of observers before unbinding: {}'.format(observers))
for observer in observers:
self.center_widget.unbind(pos=observer)
print('list of observers after unbinding: {}'.format(self.center_widget.get_property_observers('pos')))
You would need to use the latest master for this. I should fore-warn you to be extremely careful with this though you'd need to reset the bindings set in kvlanguage, but then you loose the advantage of kv language... Only use this If you really understand what you are doing.
Following #qua-non suggestion, I am temporarily moving the child to another parent. It actually unbinds it, or maybe, rebinds it to the new parent. This is a partial solution because of whatever reason, it doesn't update the position automatically when I took it out of the GridLayout (i.e. when I press enter) and put it into the new parent. I need to press 'Up' (or 'Down') after the 'Out of the Box' button.
However, it does go back immediately. When you click again on the 'Out of the box' button the 2nd time, it goes back to its original position. This part works perfectly. And it continue obeying to its parent instructions.
In other words, it doesn't work immediately when I take out of the grid but it does when I put it back.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<Example>:
center_button: _center_button
center_widget: _center_widget
float: _float
grid:_grid
GridLayout:
id: _grid
cols: 3
size_hint: .7, .3
pos_hint: {'center_x': .5}
x: 0
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _grid.y = 0
text: "Down"
Widget:
id: _center_widget
Button:
id: _center_button
pos: self.parent.pos
size: self.parent.size
on_press: root.centerize(*args)
text: "Out of the Grid"
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _grid.top = root.height
text: "Up"
FloatLayout:
id: _float
size_hint: None,None
""")
class Example(FloatLayout):
def centerize(self, instance):
if self.center_button.parent == self.center_widget:
self.center_widget.remove_widget(self.center_button)
self.float.add_widget(self.center_button)
self.float.size = self.center_button.size
self.float.x = self.center_button.x
self.float.center_y = self.center_y
else:
self.float.remove_widget(self.center_button)
self.center_widget.add_widget(self.center_button)
self.center_button.size = self.center_widget.size
self.center_button.pos = self.center_widget.pos
class ExampleApp(App):
def build(self):
return Example()
if __name__ == "__main__":
ExampleApp().run()
Here is something very similar to what I was trying. The difference is that I ended binding the properties manually so I can unbind them. Basically if I uncomment the line #pos: self.parent.pos of the 'out of the box' button, then I cannot unbind them unless I assign the Button to another parent as #qua-non suggested.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<Example>:
center_button: _center_button
GridLayout:
cols: 3
size_hint: .7, .3
pos_hint: {'center_x': .5}
Button:
on_press: self.parent.y = 0
text: "Down"
Widget:
Button:
id: _center_button
size: self.parent.size
#pos: self.parent.pos
on_press: root.centerize(*args)
text: "Out of the Grid"
Button:
on_press: self.parent.top = root.height
text: "Up"
""")
class Example(FloatLayout):
def __init__(self, **kwargs):
super(Example, self).__init__(**kwargs)
self.center_button.parent.bind(pos=self.binder)
self.centered = False
def binder(self, instance, value):
self.center_button.pos = instance.pos
def centerize(self, instance):
if self.centered:
self.center_button.parent.bind(pos=self.binder)
self.center_button.y = self.center_button.parent.y
self.centered = False
else:
self.center_button.parent.unbind(pos=self.binder)
self.center_button.center_y = self.height/2
self.centered = True
class ExampleApp(App):
def build(self):
return Example()
if __name__ == "__main__":
ExampleApp().run()

Categories