Как отменить привязку свойства, автоматически привязанного к языку Kivy?

Kivy Language автоматически создает внутренние привязки в свойствах. Например, если мы назначим позицию родителя позиции дочернего элемента, то позиция дочернего элемента будет обновляться автоматически:

Widget:
    Label:
        pos: self.parent.pos

В этом случае, если мы переместим родителя Widget, то потомок тоже переместится. Как отвязать свойство pos от дочернего элемента? Я знаю, как отменить привязку (свойств)[http://kivy.org/docs/api-kivy.uix.widget.html#using-properties], которые я привязываю сам, но как мне их отменить, если я не знаю < strong>имя связанного метода.

Вот небольшой пример, чтобы показать, что я имею в виду. Button Вверх перемещает GridLayout вверх, а Down вниз Вниз. Button Центр располагается в центре экрана. Моя проблема в том, что когда я нажимаю Вверх или Вниз, моя центрированная кнопка больше не отображается.

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

Зачем мне это делать в любом случае? Я использую анимацию на GridLayout, которая постоянно обновляет позицию. Нормальное положение кнопок должно быть внутри сетки, но время от времени одна из кнопок пролетает над экраном и возвращается в то же положение. Проблема в том, что я не могу заставить их летать, пока мой gridlayout также движется, потому что свойство привязано, и как только кнопка пытается летать, она возвращается к сетке. Это также означает, что привязка иногда желательна. Я хочу иметь контроль над привязкой и развязкой.


person toto_tico    schedule 06.07.2013    source источник
comment
почему бы просто не удалить виджет из родителя, добавить его в floatlayout, повторно добавить его обратно в его положение, используя parent.add_widget(widget, index) после завершения анимации. Смысл Gridlayout и подобных макетов состоит в том, чтобы контролировать положение его дочерних элементов. если вы этого не хотите, просто удалите виджет из макета.   -  person qua-non    schedule 08.07.2013
comment
Спасибо. Изначально я занимался чем-то подобным. Моя проблема заключалась в том, что GridLayout реорганизует виджеты. Итак, я добавил еще один уровень, уровень кнопок в моем примере, который не привязан ни к какому Layout (просто Widget). Это не сработало, потому что pos: self.parent.pos по-прежнему связывает Button и Widget pos. Я закончил привязывать и отвязывать их с помощью Python, так что теперь у меня есть контроль.   -  person toto_tico    schedule 08.07.2013
comment
Подождите, вы предполагаете, что добавление Button к FloatLayout приведет к повторной привязке вещей к новому родителю? Я попробую, но я знаю, что я так не думаю. @Tshirman объяснил мне раньше, что эта привязка выполняется строителем, потому что у меня есть обратная проблема с холстом.   -  person toto_tico    schedule 08.07.2013
comment
@qua-non, несмотря на мое недоверие, но это действительно работает. Ну, почти. Странно, но кнопка не переходит в середину по вертикали, пока я не нажму кнопку Up или Down. Он не обновляется немедленно, пока не будет выполнено другое действие. Тем не менее, он отлично работает, когда я возвращаю его в сетку. Другими словами, он не работает сразу, когда я достаю его из коробки, но работает, когда я кладу его обратно. Я вставлю код в качестве ответа.   -  person toto_tico    schedule 08.07.2013


Ответы (3)


Комментарии, похоже, сейчас не работают, поэтому я опубликую это как ответ.

  1. У вас уже есть FloatLayout (ваш корневой виджет). Используйте это вместо создания нового FloatLayout.
  2. Before removing the widget from the grid.
    • store it's size,
    • установите для size_hint значение None, None
    • установите pos_hint, чтобы расположить виджет в центре.
  3. При добавлении виджета в сетку сделайте обратное.

Вот ваш код с этими исправлениями:

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

Обновление 1:

Если по какой-либо причине вам по-прежнему необходимо отвязать свойства, связанные с kvlang, вы можете сделать это с помощью самоанализа, чтобы получить список наблюдателей для свойства. поэтому для вашего случая это будет примерно так::

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

Для этого вам нужно будет использовать последний мастер. Я должен предупредить вас, чтобы вы были очень осторожны с этим, хотя вам нужно сбросить привязки, установленные в kvlanguage, но тогда вы потеряете преимущество языка kv... Используйте это, только если вы действительно понимаете, что делаете.

person qua-non    schedule 08.07.2013
comment
Спасибо. Прежде чем пометить как ответ, должен ли я предположить, что невозможно просто отвязать материал, связанный с языком Kivy? Я публикую еще один ответ с моим текущим связанным вручную решением. - person toto_tico; 08.07.2013
comment
Обновлен ответ, чтобы показать, как вы можете отменить привязку свойств, которые не были привязаны к вам. - person qua-non; 09.07.2013

Следуя предложению @qua-non, я временно передаю ребенка другому родителю. Это фактически отвязывает его или, возможно, повторно привязывает его к новому родителю. Это частичное решение по какой-то причине, оно не обновляет позицию автоматически, когда я вынимаю ее из GridLayout (т.е. когда я нажимаю ввод) и помещаю ее в новый родитель. Мне нужно нажать «Вверх» (или «Вниз») после кнопки «Из коробки».

Тем не менее, он возвращается немедленно. Когда вы снова нажимаете кнопку «Из коробки» во второй раз, она возвращается в исходное положение. Эта часть работает идеально. И он продолжает подчиняться родительским инструкциям.

Другими словами, он не работает сразу, когда я вынимаю его из сетки, но работает, когда я кладу его обратно.

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()
person toto_tico    schedule 08.07.2013

Вот что-то очень похожее на то, что я пытался. Разница в том, что я закончил связывать свойства вручную, чтобы я мог их отменить. По сути, если я раскомментирую строку #pos: self.parent.pos кнопки «из коробки», я не смогу их отменить, если не назначу Button другому родителю, как предложил @qua-non.

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()
person toto_tico    schedule 08.07.2013