1

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.

K DawG
  • 13,287
  • 9
  • 35
  • 66
toto_tico
  • 17,977
  • 9
  • 97
  • 116
  • 1
    why not just remove the widget from the parent, add it to a floatlayout, re-add it back to it's position using parent.add_widget(widget, index) when animation is done. The point of Gridlayout and similar layouts is to control the pos of it's children. if you don't want that then simply remove the widget from the layout. – qua-non Jul 07 '13 at 22:19
  • Thanks. Initially, I was doing something like that. My problem was that the GridLayout reorganizes the widgets. So, I added another level, the buttons level in my example that aren't attach to any `Layout` (just a plain `Widget`). It didn't work because `pos: self.parent.pos` is still binding the `Button` and the `Widget` `pos`. I ended binding and unbinding them with Python, so I now have the control. – toto_tico Jul 08 '13 at 01:42
  • Wait, are you suggesting that by adding the `Button` to a `FloatLayout`, then it will rebind things to the new parent? I will try but right I know I don't think so. @Tshirman explained me before that this binding is done by the builder because I have the [reverse issue with a canvas](http://stackoverflow.com/questions/17058098/how-do-i-update-the-color-of-a-dynamically-added-ellipse-not-using-builder). – toto_tico Jul 08 '13 at 01:49
  • @qua-non, in spite of my disbelief, but that actually works. Well, almost. It is strange but the button doesn't go to the vertical middle until I press either the `Up` or `Down` button. It doesn't update immediately until other action is perform. However, it works perfectly when I put it back in the grid. In other words, it doesn't work immediately when I take out of the box but it does when I put it back. I ll paste the code as an answer. – toto_tico Jul 08 '13 at 02:42

3 Answers3

3

Comments don't seem to be working right now so I'll post this as a answer.

  1. You already have a FloatLayout(your root widget). Use that instead of creating a new FloatLayout.
  2. 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.
  3. 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.

qua-non
  • 4,152
  • 1
  • 21
  • 25
  • Thanks. Before marked as answer, should I assume it is not possible to simply unbind the stuff that is bound in the Kivy language? I am posting another answer with my current **manually bound** solution. – toto_tico Jul 08 '13 at 15:51
  • 1
    Updated the answer to show how you can unbind properties that weren't bound bu you. – qua-non Jul 09 '13 at 19:24
0

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()
toto_tico
  • 17,977
  • 9
  • 97
  • 116
0

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()
toto_tico
  • 17,977
  • 9
  • 97
  • 116