custom widget triggers another custom widget's on_touch_down event - python

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).

Related

Kivy: Function Call to Update Label Text in Another Class Not Working

I have a class called StatSummary that has labels and a button at the bottom called "Reset". When I press this button, a popup appears with a button that also says "Reset". When I press the popup "Reset" button, I want to update the labels' text of the class StatSummary. I created a method inside StatSummary called resetStatSummary which updates the labels' text using IDs.
However, when I call Factory.StatSummary().resetStatSummary() from ResetPopup, the labels on the screen do not update. This is especially confusing because I added a print statement in my Python file to see if the function runs and it does, but the labels' text does not update.
Python File:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.config import Config
from kivy.uix.popup import Popup
class StatSummary(Screen):
def resetStatSummary(self): # resets StatSummary text when "Reset" button is pressed
self.ids.summaryShotFractionLabel.text = "0/0"
self.ids.summaryShotPercentLabel.text = "0.0%"
self.ids.summaryLongStreakLabel.text = "Longest Streak: 0"
print("resetStatSummary ran")
class ResetPopup(Popup):
pass
class WindowManager(ScreenManager):
pass
kv = Builder.load_file('basketball.kv')
class MainApp(App):
def build(self):
return kv
if __name__ == "__main__":
MainApp().run()
.kv File:
#:import Factory kivy.factory.Factory
WindowManager:
StatSummary
<StatSummary>
name: "statSummary"
GridLayout:
BoxLayout:
orientation: "vertical"
Label:
id: summaryShotFractionLabel
text: "0/0"
Label:
id: summaryShotPercentLabel
text: "0.0%"
BoxLayout:
orientation: "vertical"
Label:
id: summaryLongStreakLabel
text: "Longest Streak: 0"
BoxLayout:
orientation: "horizontal"
Button:
text: "Reset"
on_release:
Factory.ResetPopup().open()
<ResetPopup>:
title: "Wait..."
BoxLayout:
cols: 1
orientation: "vertical"
Label:
text: "Are you sure you want to reset?"
Button:
text: "Go back"
on_release:
root.dismiss()
Button:
text: "Reset"
on_release:
root.dismiss()
Factory.StatSummary().resetStatSummary()
app.root.current = "interact"
app.root.transition.direction = "right"
The label doesn't change because you call wrong object's resetStatSummary method.
Within reset's on_release button method you have:
Factory.StatSummary().resetStatSummary()
which means: create fresh new StatSummary object (StatSummary() will create and return new object - class StatSummary instance), then call it's method resetStatSummary(). So you call this method on brand new object, not this created by kv file. To access expected StatSummary class instance just replace line:
Factory.StatSummary().resetStatSummary()
with
app.root.get_screen('statSummary').resetStatSummary()

Kivy popup call structure and bindings not making sence

Below is a working snippet example of a program that presents the user with menu popups to enter info.
The issues is getting the dismiss bindings working correctly. The program flow is currently:
declare content with a return callback
Load content into a popup object
call the popup
__init__ is called and sets up something like _keyboard bindings
User enters data and presses accept
Return call back is called, the popup is no longer needed so we call popup.dismiss()
popup closes and is it
The issue is if I do _keyboard binding in the __init__ then when the popup closes I MUST call the unbind method or else the keyboard input is still calling the old popups functions!
Another thing I dislike is the return callback needing to call self._popup.dismiss(). I think it is much cleaner if the popup is completely self contained and completely reuseable. This is a numpad entry popup, it should bind the keyboard and unbind it by itself. The callback recieves an instance snapshot of the popup so the return data is easy to access. The popup itself should be the one to close itself as it knows for sure that the returnCB() was its final goal.
I have no idea how to implement this though. Binding on_dismiss inside of the __init__ does nothing at all as TouchGoToInput_dismiss is never called. I also cant figure out how to get TouchGoToInput to close itself.
Another issue is if ESC is pressed the popup closes and once again the keyboard binding is messed up.
Can anyone lend me a hand understanding the call case structure?
from kivy.app import App
from kivy.lang import Builder
from kivy.factory import Factory
from kivy.uix.gridlayout import GridLayout
from kivy.properties import ObjectProperty
from kivy.properties import StringProperty
from kivy.core.window import Window
from kivy.uix.popup import Popup
Builder.load_string('''
<TouchGoToInput>:
textInput:textInput
cols: 1
size: root.size
pos: root.pos
GridLayout:
cols: 1
size_hint_y:.25
TextInput:
size_hint_x:1.0
font_size: self.height - 15
padding_y: [self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), 0]
id:textInput
disabled: True
GridLayout:
cols: 3
Button:
text: "1"
on_release: root.addText("1")
Button:
text: "2"
on_release: root.addText("2")
Button:
text: "3"
on_release: root.addText("3")
Button:
text: "4"
on_release: root.addText("4")
Button:
text: "5"
on_release: root.addText("5")
Button:
text: "6"
on_release: root.addText("6")
Button:
text: "7"
on_release: root.addText("7")
Button:
text: "8"
on_release: root.addText("8")
Button:
text: "9"
on_release: root.addText("9")
Button:
text: "."
on_release: root.addText(".")
Button:
text: "0"
on_release: root.addText("0")
Button:
text: "Done"
on_release: root.accept()
''')
class TouchGoToInput(GridLayout):
returnCB = ObjectProperty(None)
def __init__(self, **kwargs):
super(TouchGoToInput, self).__init__(**kwargs)
self.bind(on_dismiss=self.dismiss)
print('TouchGoToInput.__init__')
def dismiss(self):
print('TouchGoToInput_dismiss')
def addText(self, text):
self.textInput.text = self.textInput.text + text
def accept(self):
print('TouchGoToInput.accept')
self.returnCB(self)
def __del__(self):
print('TouchGoToInput.__del__')
self.returnCB(self)
class TestApp(App):
def build(self):
self.popupContent = TouchGoToInput(returnCB=self.gotoLinePopup)
self._popup = Popup(title="GoTo...", content=self.popupContent,
size_hint=(0.9, 0.9))
#self._popup.bind(on_dismiss=self.main_dismiss)
return Factory.Button(text="press me", on_press=self._popup.open)
def gotoLinePopup(self, instance):
print('returnCB.text: ', instance.textInput.text)
self._popup.dismiss()
def main_dismiss(self, instance):
print('main_dismiss')
TestApp().run()
In the example, it demonstrates implementation of numpad using Popup widget with keyboard binding. It accepts input from Buttons, Keyboard, and NumPad.
Popup » dismiss
By default, any click outside the popup will dismiss/close it. If you
don’t want that, you can set auto_dismiss to False:
Popup » auto_dismiss
auto_dismiss
This property determines if the view is automatically dismissed when
the user clicks outside it.
auto_dismiss is a BooleanProperty and defaults to True.
Example
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.core.window import Window
Builder.load_string('''
#:kivy 1.11.0
<NumPad>:
title: "GoTo..."
size_hint: (0.9, 0.9)
auto_dismiss: False
<TouchGoToInput>:
textInput: textInput
cols: 1
size: root.size
pos: root.pos
GridLayout:
cols: 1
size_hint_y: .25
TextInput:
size_hint_x:1.0
font_size: self.height - 15
padding_y: [self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), 0]
id: textInput
disabled: True
GridLayout:
cols: 3
Button:
text: "1"
on_release: root.addText(self.text)
Button:
text: "2"
on_release: root.addText(self.text)
Button:
text: "3"
on_release: root.addText(self.text)
Button:
text: "4"
on_release: root.addText(self.text)
Button:
text: "5"
on_release: root.addText(self.text)
Button:
text: "6"
on_release: root.addText(self.text)
Button:
text: "7"
on_release: root.addText(self.text)
Button:
text: "8"
on_release: root.addText(self.text)
Button:
text: "9"
on_release: root.addText(self.text)
Button:
text: "."
on_release: root.addText(self.text)
Button:
text: "0"
on_release: root.addText(self.text)
Button:
text: "Done"
on_release:
app._popup.dismiss()
''')
class TouchGoToInput(GridLayout):
def addText(self, text):
self.textInput.text = self.textInput.text + text
class NumPad(Popup):
def __init__(self, **kwargs):
super(NumPad, self).__init__(**kwargs)
self.popupContent = TouchGoToInput()
self.content = self.popupContent
def on_open(self):
# erase previous textInput
self.popupContent.textInput.text = ''
# keyboard binding
self._keyboard = Window.request_keyboard(
self._keyboard_closed, self, 'text')
if self._keyboard.widget:
# If it exists, this widget is a VKeyboard object which you can use
# to change the keyboard layout.
pass
self._keyboard.bind(on_key_down=self._on_keyboard_down)
def _keyboard_closed(self):
# keyboard have been closed!
if self._keyboard is not None:
self._keyboard.unbind(on_key_down=self._on_keyboard_down)
self._keyboard = None
def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
# check for 0...9, or '.' pressed from keyboard
if (keycode[0] in list(range(48, 58))) or (keycode[0] == 46):
# keyboard: 0 / 48 to 9 / 57, or decimal / 46
self.popupContent.addText(text)
# check for 0...9, or '.' pressed from numpad
elif (keycode[0] in list(range(256, 267))):
# numpad0 / 256 to numpad9 / 265, or numpaddecimal / 266
if keycode[0] == 266:
self.popupContent.addText('.')
else:
self.popupContent.addText(keycode[1][-1:])
# Keycode is composed of an integer + a string
# If we hit escape, release the keyboard
if keycode[1] == 'escape':
keyboard.release()
# Return True to accept the key. Otherwise, it will be used by
# the system.
return True
def on_dismiss(self):
print('\tNumPad.on_dismiss: self.popupContent.textInput.text=', self.popupContent.textInput.text)
self._keyboard_closed()
class TestApp(App):
def build(self):
self._popup = NumPad()
return Button(text="press me", on_press=self._popup.open)
if __name__ == "__main__":
TestApp().run()
Output

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

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()

adding widget dynamically into kivy screen object

As far as I am aware, kv language is useful when making a static display, e.g. not when making game which need much widget's positioning during runtime. Here I try to make a simple game, but still need much positioning, so kv language is out of context for the widget, but not for the screen. I use screen to differentiate the main menu and game screen. But when I try to use 'add_widget' to insert my image, it always positioned at the middle of the window. Later I found out that the screen size is only 100x100.
Below are the only way that I can thought of, but still with no luck:
class HomeScreen(Screen):
pass
class GameScreen(Screen):
pass
class ScreenManagement(ScreenManager):
pass
presentation=Builder.load_file('ProjectSD.kv')
class ProjectSDApp(App):
def build(self):
A=presentation
A.screens[0].size=(Window.size)
A.screens[0].add_widget(Label(text='hello',font_Size=80,pos=(0,0)))
return A
if __name__=='__main__':
print(Window.size)
ProjectSDApp().run()
and my ProjectSD.kv file:
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
ScreenManagement:
transition: FadeTransition()
HomeScreen:
GameScreen:
<Button>:
font_name:'attackofthecucumbers.ttf'
<HomeScreen>:
name:'home'
canvas:
Rectangle:
pos: self.pos
size: self.size
source: 'nature.jpg'
Label:
text: 'Monopoly GX'
font_name:'KBDunkTank.ttf'
font_size:100
size_hint:0.7,0.2
pos:root.width*0.15,root.height*0.70
Button:
on_release: app.root.current = "game"
text: 'Play Game'
font_size:root.width/20
size_hint:0.3,0.15
pos:root.width*0.35,root.height*0.45
Button:
on_release: app.stop()
text: 'exit'
font_size:root.width/20
size_hint:0.3,0.15
pos:root.width*0.35,root.height*0.20
<GameScreen>:
name:'game'
Button:
on_release: app.root.current = "home"
background_color: (1,0.15,0.2,0.8)
text: 'X'
font_size:root.width/40
size_hint:0.05,0.05
pos:root.width*0.95,root.height*0.95
Since there is no 'pos' method in screen object, I put my widget to position (0,0) manually.
The only way I found is just below:
https://kivyspacegame.wordpress.com/2014/08/10/tutorial-flappy-ship-part-2-build-simple-menus-and-animate-your-games-using-clock/
So my question is, if I use screen object from kivy's build in, how to achieve the same result? So I can still adding and remove widget as I want it later?
I'm not entirely clear on what you're asking; I don't see anywhere in your code where you are trying to add an Image to a Layout. You add a label to the middle and that works fine.
I think because you are creating screens without any layouts and not setting a default window size, that the screens just take up their minimum default size. You need to add a layout and fill it with stuff that has a defined size, or set a window size at the beginning e.g:
# window import
from kivy.core.window import Window
from kivy.utils import get_color_from_hex
Window.size = (1920/2,1080/2)
Window.clearcolor = get_color_from_hex('#000000') # black
Here is some code that creates your two Screens, and adds a draggable and a static Image at a position to a FloatLayout.
from kivy.app import App
from kivy.uix.screenmanager import (ScreenManager, Screen)
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.behaviors import DragBehavior
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
class HomeScreen(Screen):
pass
class GameScreen(Screen):
pass
class ScreenManagement(ScreenManager):
pass
class MovingImage(DragBehavior,Image):
pass
class DragLabel(DragBehavior, Label):
pass
class StaticImage(Image):
pass
class MyApp(App):
def build(self):
A = ScreenManagement()
A.current = 'game'
A.screens[1].ids.gamefloat.add_widget(MovingImage(size_hint = [0.3,0.3]))
A.screens[1].ids.gamefloat.add_widget(StaticImage(pos = (150,300)))
return A
if __name__=='__main__':
MyApp().run()
and the .kv file called myapp.kv
<ScreenManagement>:
HomeScreen:
GameScreen:
<Button>:
<HomeScreen>:
name:'home'
canvas:
Rectangle:
pos: self.pos
size: self.size
source: 'image.jpg'
Label:
text: 'Monopoly GX'
font_size:100
size_hint:0.7,0.2
pos:root.width*0.15,root.height*0.70
Button:
on_release: app.root.current = "game"
text: 'Play Game'
font_size:root.width/20
size_hint:0.3,0.15
pos:root.width*0.35,root.height*0.45
Button:
on_release: app.stop()
text: 'exit'
font_size:root.width/20
size_hint:0.3,0.15
pos:root.width*0.35,root.height*0.20
<GameScreen>:
name:'game'
FloatLayout:
id: gamefloat
Button:
on_release: app.root.current = "home"
background_color: (1,0.15,0.2,0.8)
text: 'X'
font_size:root.width/40
size_hint:0.05,0.05
pos:root.width*0.95,root.height*0.95
<MovingImage>:
drag_rectangle: self.x, self.y, self.width, self.height
drag_timeout: 1000000
drag_distance: 0
source: 'image.jpg'
<StaticImage>:
source: 'image.jpg'

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