I am trying to make a children's learning app using KIVY and gTTS where a child will be shown a random image and will have to identify it by saying what it is ("square" for square, "three" for 3 etc).
So far I have the menus working fine.
I am using random.choice() in a dictionary where the value is the image path and the key is "the name"
If I open the relevant screen the image is correctly selected at random and displayed using def on_pre_enter(self, *args): and gTTS kicks in fine as well using def on_enter(self, *args): but only ONCE
I want it to load a new random image once the user replies to the previous one for an X amount of loops but no matter what I try I cannot get it to work (I thought of putting everything on a for x in range() loop as well as using a counter on a while X < Y: but without any success).
here's my .py file
import random
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import StringProperty
class MenuWindow(Screen):
pass
class ShapeGame(Screen):
rand_shape = StringProperty()
def on_pre_enter(self, *args):
random_shape = {"square":'shapes/square.png', "triangle":'shapes/triangle.jpg', "circle":'shapes/circle.jpg'}
random_shape_key, random_shape_value = random.choice(list(random_shape.items()))
print(random_shape_key)
self.rand_shape_key = random_shape_key
self.rand_shape = random_shape_value
def on_enter(self, *args):
print(self.random_shape_key)
class WindowManager(ScreenManager):
pass
class MainApp(App):
def build(self):
return Builder.load_file('Main.kv')
if __name__ == '__main__':
MainApp().run()
and my .kv file
#:kivy 2.0
WindowManager:
MenuWindow:
ShapeGame:
<MenuWindow>:
name: "menu"
BoxLayout:
orientation: "vertical"
size: root.width, root.height
Label:
id:"menu"
text: "Menu Screen"
font_size: 34
BoxLayout:
size_hint: 1.0, 0.2
Button:
text: "Shape Game"
font_size: 22
on_release:
app.root.current = "shapes"
root.manager.transition.direction = "left"
Button:
text: "Exit"
font_size: 22
size_hint: 1.0, 0.2
on_release: app.root.current = exit()
<ShapeGame>:
name: "shapes"
id: ShapeGame
BoxLayout:
orientation: "vertical"
size: root.width, root.height
Image:
id:"shapes"
screen: ShapeGame
source: self.screen.rand_shape
before_source: self.source
BoxLayout:
size_hint: 1.0, 0.2
size: root.width, root.height
Button:
text: "Menu"
on_release:
app.root.current = "menu"
root.manager.transition.direction = "right"
Button:
text: "Exit"
on_release:
app.root.current = exit()
and the entire repo
Not sure I can follow your problem, assuming that you want to shuffle images on entering screen after some time until some value reaches its limit.
For that you can use Clock.schedule_interval with some waiting time, say 2.0 sec.
Thus your ShapeGame will now look like,
class ShapeGame(Screen):
rand_shape = StringProperty()
count = 0
def on_pre_enter(self, *args):
self.count = 0
self.change_event = Clock.schedule_interval(self.chage_photo, 2.0)
def chage_photo(self, *args):
if self.count < 3:
self.count += 1
else:
self.change_event.cancel()
random_shape = {"square":'shapes/square.png', "triangle":'shapes/triangle.jpg', "circle":'shapes/circle.jpg'}
random_shape_key, random_shape_value = random.choice(list(random_shape.items()))
print(random_shape_key)
self.rand_shape_key = random_shape_key
self.rand_shape = random_shape_value
You should change source: self.screen.rand_shape to source: root.rand_shape.
You may also trigger the same action by a button instead of using Clock.schedule.
Related
I've come to a problem in a larger app im writing that seems dumb but I can't figure it out. I want to have the user select an image path on screen1 and then have the path load onto screen2. This presents 2 problems.
if the path is first empty, kivy runs the second window anyway, and cv2 reads an empty path. I would like to keep the path empty and prevent the window from running until the path is loaded from the previous window
my code isn't even loading the data from the previous window. I know i could update the image source directly, but I am seeking to update the path variable
Here is a test code and test image
from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
import cv2
class WindowManager(ScreenManager):
pass
class Window1(Screen, RelativeLayout):
button_size = (100, 100)
button_pos = (400, 400)
class Window2(Screen,RelativeLayout):
path=''
print(cv2.imread(path).shape)
KV = Builder.load_string("""
WindowManager:
Window1:
Window2:
<Window1>:
name: 'one'
RelativeLayout:
Button:
allow_stretch: True
keep_ratio: True
size_hint: None,None
size: root.button_size
pos: root.button_pos
on_press:
root.manager.screens[1].path='/MRI-scan.jpg'
on_release:
app.root.current = "two"
root.manager.transition.direction = "left"
<Window2>:
name: 'two'
RelativeLayout:
Image:
id: my_image
size_hint: 0.5,0.5
pos: (0,0)
source: root.path
""")
class TApp(App):
def build(self):
return KV
if __name__ == '__main__':
TApp().run()
I was able to solve the problem with one shortcoming. Anyone who figures out this shortcoming, I'll check mark your answer. I can switch between images using the path and I did not even need to use StringProperty. However, I cannot start with path=''. I need to start with a file initially. Here's the solution. Anyone who can provide an answer by starting with an empty string, I'll give a check mark to you. I solved the problem with on_pre_enter.
from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
import cv2
class WindowManager(ScreenManager):
pass
class Window1(Screen, RelativeLayout):
button_size = (100, 100)
button_pos = (400, 400)
btn2_size=(100,100)
btn2_pos=(400,300)
def on_touch_up(self, touch):
if self.ids.btn1.collide_point(*touch.pos):
self.manager.get_screen('two').path='img1'
if self.ids.btn2.collide_point(*touch.pos):
self.manager.get_screen('two').path = 'img2'
class Window2(Screen,RelativeLayout):
btn3_size = (100, 100)
btn3_pos = (400, 200)
path='img start' #===> this is where I want to start with a blank string
print(cv2.imread(path).shape)
def on_pre_enter(self, *args):
self.manager.get_screen('two').ids['my_image'].source=self.path
print(print(cv2.imread(self.path).shape)
)
KV = Builder.load_string("""
WindowManager:
Window1:
Window2:
<Window1>:
name: 'one'
RelativeLayout:
Button:
id: btn1
text: 'one'
allow_stretch: True
keep_ratio: True
size_hint: None,None
size: root.button_size
pos: root.button_pos
on_release:
app.root.current = "two"
root.manager.transition.direction = "left"
Button:
id: btn2
text: 'two'
allow_stretch: True
keep_ratio: True
size_hint: None,None
size: root.btn2_size
pos: root.btn2_pos
on_release:
app.root.current = "two"
root.manager.transition.direction = "left"
<Window2>:
name: 'two'
RelativeLayout:
Image:
id: my_image
size_hint: 0.5,0.5
pos: (0,0)
source: root.path
Button:
id: btn3
text: 'previous'
allow_stretch: True
keep_ratio: True
size_hint: None,None
size: root.btn3_size
pos: root.btn3_pos
on_release:
app.root.current = "one"
root.manager.transition.direction = "right"
""")
class TApp(App):
def build(self):
return KV
if __name__ == '__main__':
TApp().run()
I have an Kivy app that has 2 screens. Screen 1 (ProblemWindow) will get the user input and screen 2 (StepsWindow) will show some images based on screen1 input. However, I need to pass one of the values (Spinner id: problem_id) from screen 1 (ProblemWindow) into screen 2 (StepsWindow) and also use the value in the python file for additional logic.
I can pass the value between the screens but I am not able to use it in python. I need to get the updated Label Text of the StepsWindow into my python code every time I change it in my ProblemWindow and press the button "ShowSteps".
Could anyone please guide me how to achieve this?
This is my python code:
# importing dependencies
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
class WindowManager(ScreenManager):
pass
class ProblemWindow(Screen):
def selected_problem(self, value):
self.ids.click_label.text = f'selected problem: {value}'
return value
class StepsWindow(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# self.get_problem_name()
def get_problem_name(self, *args):
self.problem_name = self.ids.problem_name.text
print(self.problem_name)
kv = Builder.load_file('main.kv')
class main(App):
def build(self):
return kv
if __name__ == '__main__':
main().run()
This is my kv file for reference:
WindowManager:
id: window_manager
ProblemWindow:
id: pw
StepsWindow:
id: sw
<ProblemWindow>:
name: "problem_window"
GridLayout:
rows: 4
Label:
text: "TEST"
font_size: 24
Label:
id: click_label
text: "Select a problem"
Spinner:
id: problem_id
text: "Problems List (Click here)"
font_size: 24
values: ["Problem_1", "Problem_2"]
on_text: root.selected_problem(problem_id.text)
size_hint: 0.1, 0.1
width: 300
Button:
text: "Show Steps"
font_size: 28
size_hint: 0.2, 0.2
pos_hint: {"center_x": 0.5, "center_y": 0.5}
on_release:
app.root.current = "steps_window"
root.manager.transition.direction = "left"
<StepsWindow>:
name: "steps_window"
GridLayout:
rows: 3
size: root.width, root.height
Label:
id: problem_name
text: root.manager.ids.pw.ids.problem_id.text
font_size: '42'
size_hint_y: None
height: 50
Button:
text: "Back"
on_release:
app.root.current = "problem_window"
root.manager.transition.direction = 'right'
Something like this?
# importing dependencies
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
class WindowManager(ScreenManager):
pass
class ProblemWindow(Screen):
def selected_problem(self, value):
self.value = value
self.ids.click_label.text = f'selected problem: {value}'
return value
#logic here
def logic_here(self):
if self.value == "Problem_1":
print(f'The solution for "Problem_1" is:')
else:
print(f'The solution for "Problem_2" is:')
class StepsWindow(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# self.get_problem_name()
def get_problem_name(self, *args):
self.problem_name = self.ids.problem_name.text
print(self.problem_name)
kv = Builder.load_file('main.kv')
class main(App):
def build(self):
return kv
if __name__ == '__main__':
main().run()
And for the <ProblemWindow> button in the .kv file add this:
root.logic_here()
I want to catch a value from my first screen into my thirdscreen.
In the first, I write my name in an input field.
I go to the next window.
And I try to show my name in this last window.
So I share the code with you and I hope I will find an issue.
Python code :
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.properties import ObjectProperty
from kivy.uix.screenmanager import ScreenManager, Screen
#define ou different screens
class FirstWindow(Screen):
def envoyer(self):
name = self.ids.nom_input.text
self.ids.my_name.text = name
class SecondWindow(Screen):
pass
class ThirdWindow(Screen):
#PROBLEM HERE
def on_press(self):
self.ids.recup_infos.text = self.root.get_screen('FirstWindow').ids.my_name.text
class WindowManager(ScreenManager):
pass
class MonWidget(Widget):
pass
kv = Builder.load_file('new_window.kv')
class AwesomeApp(App):
def build(self):
Window.clearcolor = (0,0,0,0)
return kv
if __name__ == '__main__':
AwesomeApp().run()
My KV CODE :
WindowManager:
FirstWindow:
SecondWindow:
ThirdWindow:
<FirstWindow>:
name: "romain"
BoxLayout:
orientation: "vertical"
size: root.width, root.height
Label:
id: my_name
text: "Entrez votre nom"
font_size: 32
TextInput:
id: nom_input
multiline: False
size_hint: (1, .5)
Button:
text: "Next screen"
font_size: 32
on_press: root.envoyer()
on_release:
app.root.current = "Mickael"
root.manager.transition.direction = "left"
<SecondWindow>:
name: "Mickael"
BoxLayout:
orientation: "vertical"
size: root.width, root.height
Label:
text: "Entre votre ville"
font_size: 32
TextInput:
id: ville_input
multiline: False
size_hint: (1, .5)
Button:
text: "VĂ©rifier les infos"
font_size: 32
on_release:
app.root.current = "foncier"
root.manager.transition.direction = "left"
Button:
text: "go back first screen"
font_size: 32
on_release:
app.root.current = "romain"
root.manager.transition.direction = "right"
<ThirdWindow>:
name: "foncier"
BoxLayout:
orientation: "vertical"
size: root.width, root.height
Label:
text: "Verifier : "
font_size: 32
Label:
id: recup_infos
text: ""
font_size: 32
color: 'white'
Button:
text: "On press"
font_size: 32
#Problem HERE
on_press: root.on_press()
Button:
text: "Précedent"
font_size: 32
on_release:
app.root.current = "Mickael"
root.manager.transition.direction = "right"
Could you help me ?
Thank you
Romain
In your on_press method:
def on_press(self):
self.ids.recup_infos.text = self.root.get_screen('FirstWindow').ids.my_name.text
self.root.get_screen('FirstWindow').ids.my_name.text isn't the correct way to get access to widgets outside of the class that you are in right now, or in this situation, screen. The correct way is to use the App.get_running_app() method:
self.ids.recup_infos.text = App.get_running_app().root.ids.First.ids.my_name.text
But before doing that, you have to give ids to the screens of your app, so that the First argument of the method demonstrated above actually makes sense:
WindowManager:
FirstWindow:
id: First
# "First" is the id of the FirstWindow class
# which can also explain why there was a "First" arg
# inside "App.get_running_app().root.ids.First.ids.my_name.text"
SecondWindow:
id: Second
ThirdWindow:
id: Third
Still confused to why this works? Let's divide the attributes of App.get_running_app().root.ids.First.ids.my_name.text into 3 parts:
App.get_running_app(): this method returns the location of your running App class, in this case AwesomeApp. This also acts as self if you were to get the variable inside the App object itself
.root.ids.First: if you read the Kivy documentation, or just simply watched Kivy course videos online, carefully, you should know that self.root.ids inside the App object returns a list of ids of the widgets inside your root widget. In this case, App.get_running_app().root.ids is doing the same thing here, and your screens are passed in the ScreenManager root widget, hence make First an available attribute in App.get_running_app().root.ids
.ids.my_name.text: same as above, App.get_running_app().root.ids.First acts the same as self if you were to run it in your FirstWindow class, which gives you the opportunity to get access to variables outside of your working classes/screens
Question:
How do you use an On-Press event to change screen for dynamically created buttons in python, and without using KV language?
Goal:
Be able to navigate to new screen from clicking on dynamically created button,
[without needing to create button in Kivy, and still getting to use Screenmanager in both Python and Kivy (not sure if you have to stick with either Python or Kivy throughout entire program?]
Things I've already tried:
Using button_share.bind(on_press = self.changer), then this:
def changer(self,*args):
ScreenManager()
screenmanager.current = 'MainScreen'
But I get the error ScreenManagerException: No Screen with name "MainScreen".
Suspicion:
I think this is because I'm creating a new instance of ScreenManager, instead of referencing the existing one. To combat this issue, I considered instantiating Screenmanager() in the App class, then referencing that instantiation in the my button def changer(self, *args) method, but that's useless if it's not the same ScreenManager I'm actually using for all my screens. And those are all defined in KV language. I wouldn't be able to switch them all over without a substantial amount of effort.
Using:
button_share.bind(on_press=partial(app.sm.setter('current'), (app.sm, "MainScreen")))`
But the error I get here is ValueError: ScreenManager.current accept only str
Below is a fully runnable example:
Note: In this example, I want to click 'Continue Editing' button, then click on 'Test 1', 'Test 2' or 'Test 3' button and have it take me to another screen.
Python Code:
from kivy.app import App
# kivy.require("1.10.0")
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.clock import Clock
from kivy.uix.widget import Widget
#from kivy.base import runTouchApp
from kivy.properties import StringProperty, ObjectProperty, NumericProperty
from functools import partial
class ScrollableLabelDataEntryInstructions(BoxLayout):
pass
class NewGarageScreen(Screen):
pass
class ContinueEditingScreen(Screen):
pass
class GarageNameBoxLayout(BoxLayout):
box_share2 = ObjectProperty()
sm = ScreenManager()
def __init__(self, **kwargs):
super(GarageNameBoxLayout, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_interval(self.create_button, 5)
def create_button(self, *args):
self.box_share2.clear_widgets()
app = App.get_running_app()
#put GarageNameStartList data into app class, then pull from it in this class
top_button_share = 1.1
color = (.4, .4, .4, 1)
for i in range(len(app.GarageNameStartList)):
top_button_share -= .4
id_ = app.GarageNameStartList[i]
button_share = Button(background_normal='',
background_color = color,
id = id_,
pos_hint = {"x": 0, "top": top_button_share},
size_hint_y = None,
height = 60,
font_size = 30,
text = app.GarageNameStartList[i])
button_share.bind(on_press = self.changer)
#button_share.bind(on_press=partial(app.sm.setter('current'), (app.sm, "MainScreen")))
self.box_share2.add_widget(button_share)
def changer(self,*args):
ScreenManager()
#app = App.get_running_app()
screenmanager.current = 'MainScreen'
class BackHomeWidget(Widget):
pass
class MainScreen(Screen):
pass
class AnotherScreen(Screen):
pass
class ScreenManagement(ScreenManager):
pass
presentation = Builder.load_file("example_on_press.kv")
class MainApp(App):
GarageNameStartList = ["Test1", "Test2", "Test3"]
def Update_GarageNameStartList(self, *args):
self.GarageNameStartList = ["Test1", "Test2", "Test3"]
def build(self):
return presentation
if __name__ == "__main__":
MainApp().run()
KV Code:
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
ScreenManagement:
transition: FadeTransition()
MainScreen:
AnotherScreen:
NewGarageScreen:
ContinueEditingScreen:
<SmallNavButton#Button>:
font_size: 32
size: 125, 50
color: 0,1,0,1
<MedButton#Button>:
font_size: 30
size_hint: 0.25, 0.1
color: 0,1,0,1
<BackHomeWidget>:
SmallNavButton:
on_release: app.root.current = "main"
text: "Home"
pos: root.x, root.top - self.height
<MainScreen>:
name: "main"
FloatLayout:
MedButton:
on_release: app.root.current = "edit"
text: "Edit"
pos_hint: {"x":0.3728, "top": 0.4}
<AnotherScreen>:
name: "edit"
BackHomeWidget:
SmallNavButton:
on_release: app.root.current = "main"
text: "Back"
pos: root.x, root.top - (2.25*(self.height))
FloatLayout:
MedButton:
on_release: app.root.current = "continueediting"
text: "Continue Editing"
pos_hint: {"x":0.25, "top": 0.6}
MedButton:
on_release: app.root.current = "newgarage"
text: "Create New"
pos_hint: {"x":0.3728, "top": 0.4}
<NewGarageScreen>:
name: "newgarage"
BackHomeWidget:
SmallNavButton:
on_release: app.root.current = "edit"
text: "Back"
pos: root.x, root.top - (2.25*(self.height))
FloatLayout:
MedButton:
text: "1. Groundfloor"
pos_hint: {"x":0, "top": 0.6}
<GarageNameBoxLayout>:
box_share2: box_share2
ScrollView:
GridLayout:
id: box_share2
cols: 1
size_hint_y: None
size_hint_x: 0.5
spacing: 5
padding: 130
height: self.minimum_height
canvas:
Color:
rgb: 0, 0, 0
Rectangle:
pos: self.pos
size: self.size
<ContinueEditingScreen>:
name: "continueediting"
GarageNameBoxLayout:
BackHomeWidget:
SmallNavButton:
on_release: app.root.current = "edit"
text: "Back"
pos: root.x, root.top - (2.25*(self.height))
Your code can be improved in the following things:
You do not have to create box_share2 in the .py since you're creating it in the .kv
When you use sm = ScreenManager() you are creating another ScreenManager different from the original, that is not necessary.
It is not necessary to use range and len, make your code less readable, you just have to iterate.
If we look at the structure of the .kv we see that the presentation object is the ScreenManager so you can get it via app.root.
Using the above your code the solution is:
[...]
class GarageNameBoxLayout(BoxLayout):
def __init__(self, **kwargs):
super(GarageNameBoxLayout, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_interval(self.create_button, 5)
def create_button(self, *args):
self.box_share2.clear_widgets()
app = App.get_running_app()
sm = app.root
#put GarageNameStartList data into app class, then pull from it in this class
top_button_share = 1.1
color = (.4, .4, .4, 1)
for text in app.GarageNameStartList:
top_button_share -= .4
id_ = text
button_share = Button(background_normal='',
background_color = color,
id = id_,
pos_hint = {"x": 0, "top": top_button_share},
size_hint_y = None,
height = 60,
font_size = 30,
text = text)
button_share.bind(on_press=lambda *args: setattr(sm, 'current', "main"))
self.box_share2.add_widget(button_share)
[...]
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()