0

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()
stack_horst
  • 302
  • 3
  • 11
  • 1
    http://inclem.net/2019/06/20/kivy/widget_interactions_between_python_and_kv/ – inclement Aug 08 '20 at 10:33
  • @inclement Thanks, I went with your 2nd-last example on that page (i.e. the one before the "global variables" example). So if I were to rephrase that, the reply would be: Introduce a kivy-property in the widget from which you want to induce the change and in the kv rules assign the receiving widget's property to the one of the inducing widget on via kv-ids. right? (Either assign directly or trigger some function upon change of the tracked property.) – stack_horst Aug 09 '20 at 07:17
  • this works in kv, and i think i overlooked it b/c i was thinking about a 'python-way' to do it. What would be the python code for setting it up the same way (but again, without explicitly depending of the nested widget structure)? is there something like app.ids? (b/c the other button is not in self.ids and, again, i do not want to climb the tree back up, b/c that would break if i change the widget tree). – stack_horst Aug 09 '20 at 07:21
  • I found that you can access them by `App.get_running_app().root.ids` -- any good reason not to do that? Should be completely equivalent to using the ids in kv, or not? For future readers: [Also relevant](https://stackoverflow.com/questions/35792621/kivy-get-widgets-ids-and-accessing-widgets-by-unique-property) ;) – stack_horst Aug 09 '20 at 07:48

1 Answers1

1

@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()
stack_horst
  • 302
  • 3
  • 11