stack_horst
stack_horst

Reputation: 312

How to change properties of another Kivy Widget's child without using `self.parent.parent. ...`?

My question is the title. It is probably the same as this one, but I am not happy with the answer there or with my working minimal example below. That is because if I were to move the child I am making the call from one "nested layer" up or down (e.g. in my example: put the "special button" inside another Layout of the parent BoxLayout) the callback function would not work anymore.

How to do this properly (and the intended kivy way)?

Minimal Example:

from kivy.app import App
from kivy.lang.builder import Builder
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.properties import ObjectProperty

Builder.load_string('''
<RootWidget>:
  button1: b1
  button2: b2
  BoxLayout:
    orientation: 'horizontal'
    size: root.size
    Button:
      id: b1
      text: 'I am the normal one'
    SpecialButton:
      id: b2
      text: 'I am the special one'
<SpecialButton>:
  on_release: root.clickityclick()
''')

class RootWidget(Widget):
  button1 = ObjectProperty(None)
  button2 = ObjectProperty(None)

class SpecialButton(Button):
  def clickityclick(self):
    self.parent.parent.button1.disabled = True  ### <----- How to do this properly?

class MinimalExampleApp(App):
  def build(self):
    return RootWidget()

if __name__ == '__main__':
  MinimalExampleApp().run()

Upvotes: 0

Views: 1283

Answers (1)

stack_horst
stack_horst

Reputation: 312

@inclement's comment contains the answer to my question (specifically: 2nd-last example from his article ("Option 1"))

Option 1: Introduce a kivy-property in the widget from which you want to induce the change. In the kv rules set a property of the receiving widget to the one you introduced in the inducing widget (via the kv-id of the iducing widget). This way you could either have the receiving widget track a property directly or trigger a function when the property changes.

Option 2: Use App.get_running_app().root.ids ("in Python") to talk to the receiving widget directly.

Example for Option 1:


from kivy.app import App
from kivy.lang.builder import Builder
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.properties import ObjectProperty, BooleanProperty

Builder.load_string('''
<RootWidget>:
  button1: b1
  button2: b2
  BoxLayout:
    orientation: 'horizontal'
    size: root.size
    Button:
      id: b1
      text: 'I am the normal one'
      disabled: b2.switch         # <----- track the property of the other ('special') button
    SpecialButton:
      id: b2
      text: 'I am the special one'
<SpecialButton>:
  on_release: root.clickityclick()
''')

class RootWidget(Widget):
  button1 = ObjectProperty(None)
  button2 = ObjectProperty(None)

class SpecialButton(Button):
  switch = BooleanProperty(False)    # <----- introduce a property which is then tracked by the other ('normal') button
  def clickityclick(self):
    self.switch = not self.switch    # <----- only change your own property

class MinimalExampleApp(App):
  def build(self):
    return RootWidget()

if __name__ == '__main__':
  MinimalExampleApp().run()

Example for Option 2:

from kivy.app import App
from kivy.lang.builder import Builder
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.properties import ObjectProperty

Builder.load_string('''
<RootWidget>:
  button1: b1
  button2: b2
  BoxLayout:
    orientation: 'horizontal'
    size: root.size
    Button:
      id: b1
      text: 'I am the normal one'
    SpecialButton:
      id: b2
      text: 'I am the special one'
<SpecialButton>:
  on_release: root.clickityclick()
''')

class RootWidget(Widget):
  button1 = ObjectProperty(None)
  button2 = ObjectProperty(None)

class SpecialButton(Button):
  def clickityclick(self):
    App.get_running_app().root.ids.b1.disabled = \
    not App.get_running_app().root.ids.b1.disabled  # <---- directly access the other ('normal') button

class MinimalExampleApp(App):
  def build(self):
    return RootWidget()

if __name__ == '__main__':
  MinimalExampleApp().run()

Upvotes: 1

Related Questions