3

I am writing a program that has a panel of buttons on the right hand side, that successfully binds a method to each, dependant on user input/actions. My issues is that I cannot unbind() the method explicitely, as the method bound is dynamic.

Consider;

    i = 1
    while i <= 8:
        string = "action" + str(i) 
        #Buttons named 'action1, action1, ..., action8'
        ref = self.ids[string] 
        ref.text = ""
        observers = ref.get_property_observers('on_release')
        print observers
        ref.unbind()
        ref.bind(on_release=partial(self.BlankMethod, arg1))
        i += 1

The lines;

        observers = ref.get_property_observers('on_release')
        print observers

Show that I have an increasing list of weakmethods bound, every time the method is called, but the unbind function does not unbind the method.

Although my code example does not show it, the bound method changes regularly, and the self.BlankMethod was meant to override the original binding. This is not the case, and the Button's bound methods increase.

[<kivy.weakmethod.WeakMethod object at 0x7f8cc826a810>] [<kivy.weakmethod.WeakMethod object at 0x7f8cc826a850>, <kivy.weakmethod.WeakMethod object at 0x7f8cc826acd0>]

I have tried;

        observers = ref.get_property_observers('on_release')
        if observers != []:
            for observer in observers:
                ref.unbind(observer)
        ref.bind(on_release=partial(self.Blank))

But I am returned the error;

TypeError: unbind() takes exactly 0 positional arguments (1 given)

I had a look at using funbind() function but subsequently given;

AttributeError: 'Button' object has no attribute 'funbind'

Trying to use fbind() prior to funbind() also gives the same error, but with respect to the button not having an fbind() attribute.

How can I list all bound methods of an object, and subsequently unbind them?

zeeMonkeez
  • 5,057
  • 3
  • 33
  • 56
Chazara
  • 159
  • 2
  • 3
  • 16
  • I was going to suggest using `fbind`/`unbind`. Can you show your usage of `fbind`? Note that the syntax is slightly different. – zeeMonkeez Feb 11 '16 at 00:09
  • I stumbled across that too, but apparently `'Button' has no attribute 'fbind'` ... ? - yes, 1 moment – Chazara Feb 11 '16 at 00:11
  • `ref.fbind('on_release', self.SignPopupMethod, str(signText))` was how I was using it – Chazara Feb 11 '16 at 00:14
  • 1
    Also, `unbind` takes the same kind of argument as `bind`, so it would have to be `unbind(on_x=callback)` to unbind something bound as `bind(on_x=callback)`. The API documentation could be bit more explicit here... – zeeMonkeez Feb 11 '16 at 00:14
  • `ref.unbind(on_release=observer)` doesn't give the `TypeError: unbind() takes exactly 0 positional arguments (1 given)` error any more, but still does not unbind the method! Thank you for making me aware of this :) – Chazara Feb 11 '16 at 00:19
  • Reading the Event Dispatcher page: https://kivy.org/docs/api-kivy.event.html#kivy.event.EventDispatcher.get_property_observers Perhaps you may be able to tell me how to use the `name` property here? `observer.name` is not valid and neither is `ref.get_property_observers('on_release').name` – Chazara Feb 11 '16 at 00:24
  • Can you try storing your partial in an ObjectProperty, like `ref._mycb=partial(...)`, then `ref.bind(on_release=ref._mycb)`, and BEFORE all of this `if ref._mycb is not None: ref.unbind(on_release=ref._mycb)`? – zeeMonkeez Feb 11 '16 at 00:24
  • It was a nice idea, but same results - the code executes without issue, but fails to unbind... – Chazara Feb 11 '16 at 00:36

1 Answers1

3

Here is a toy example demonstrating setting, saving, and later clearing callbacks. When a_button or b_button is pressed, set_callbacks is triggered, which binds callbacks to all of MyButtons. MyButton has a list property _cb storing user defined callbacks. c_button will trigger clear_callbacks, which goes through each of the MyButton's _cb lists and unbinds each stored callback.

from kivy.app import App
from kivy.lang import Builder
from functools import partial

kv_str = """
<MyButton@Button>:
    _cb: []
    my_id: 1
    text: "hello {}".format(self.my_id)
BoxLayout:
    orientation: 'vertical'
    BoxLayout:
        Button:
            id: a_button
            text: "a button"
        Button:
            id: b_button
            text: "b button"
        Button:
            id: c_button
            text: "clear callbacks"
    GridLayout:
        cols: 4
        id: grid
        MyButton:
            my_id: 1
        MyButton:
            my_id: 2
        MyButton:
            my_id: 3
        MyButton:
            my_id: 4
"""

def cb(sender, arg='nothing'):
    print "we got: {} pressed with {}".format(sender.text, arg)

class MyApp(App):
    def build(self):
        root = Builder.load_string(kv_str)

        def set_callbacks(sender):
            for b in root.ids.grid.children:
                new_cb = partial(cb, arg=sender.text)
                b._cb.append(new_cb)
                b.fbind('on_press', new_cb)
        def clear_callbacks(sender):
            for b in root.ids.grid.children:
                for cb in b._cb:
                    b.funbind('on_press', cb)
                b._cb = []
        root.ids.a_button.bind(on_press=set_callbacks)
        root.ids.b_button.bind(on_press=set_callbacks)
        root.ids.c_button.bind(on_press=clear_callbacks)
        return root

if __name__ == '__main__':
    a = MyApp()
    a.run()
zeeMonkeez
  • 5,057
  • 3
  • 33
  • 56
  • Thank you for your time and effort zee! I will implement my changes tomorrow evening and report back :) – Chazara Feb 11 '16 at 02:47
  • The use of a list property on the button to store the partial, and later removing items from the list, was genius! It has worked flawlessly. Just to note, I was still having issues with `fbind` and `unbind` on a button `AttributeError: 'Button' object has no attribute 'fbind'` although the methodology of a list worked with `bind` and `unbind` too! Thank you very much once again. – Chazara Feb 11 '16 at 16:54
  • There's one caveat: with partials, they can be bound multiple times (as this example shows), even using `bind`. More logic is needed to prevent that ... – zeeMonkeez Feb 11 '16 at 17:02
  • I do the `if list -> unbind` for all buttons first, and there will never be more than one action bound at a time. Thanks for the intel though, it's good to know :) – Chazara Feb 11 '16 at 17:07