Dynamically created kivy buttons run on_press and on_release immediately

10,786

btn2.bind(on_release=self.btn2_pressed())

You have the syntax to bind wrong (and this is also the problem in your other question).

Bind takes a function, but you aren't passing a function, you're calling the function. bind never sees that you happened to write btn2_pressed in its argument box, because python calls the function and passes only the result into bind.

So the solution is, you really want to write something like

btn2.bind(on_release=self.btn2_pressed)

Note the removed brackets - these are the syntax to call the function, but we specifically don't want to do that. Instead we pass the function itself.

bind also passes in some default arguments to the function, whereas your function is defined to only accept one. Since you don't care about the extra arguments here, you can just define your function with

def btn2_pressed(self, *args):

The *args catches the spare arguments. You can look up this syntax if you are not familiar with it.

Share:
10,786
bosky
Author by

bosky

Updated on June 05, 2022

Comments

  • bosky
    bosky almost 2 years

    Why do my kivy buttons act pressed the moment they are created in python?

    So, let me start by saying I know that there seems to be a answer to this question here:

    on_press in Kivy keeps running at start up instead

    ...however, there is no working example. I have tried to copy the example there to understand the answer, but lack the experience to do fill in what's missing from the example.

    So, this will probably be an easy answer for someone who looks at the other answer, can apply it here, and explain in plainer english for the noob.

    Here is a small working example of the problem:

    import kivy
    kivy.require('1.7.2') # replace with your current kivy version !
    
    from kivy.app import App
    from kivy.uix.screenmanager import ScreenManager, Screen
    from kivy.properties import ObjectProperty
    from kivy.uix.button import Button
    from kivy.uix.gridlayout import GridLayout
    
    i = ['some', 'words']
    
    class HomeScreen(Screen):
        grid_l = ObjectProperty(None)
        top_lbl = ObjectProperty(None)
    
        def search_btn_pressed(self):
            grid = self.grid_l
            grid.bind(minimum_height=grid.setter('height'),
                         minimum_width=grid.setter('width'))
    
            for result in i:
    
                    btn1 = Button(size_hint=(1, None))
                    btn1.text = '%r' % result
                    btn1.bind(on_release=self.btn1_pressed(result))
    
                    btn2 = Button(size_hint=(1, None))
                    btn2.text = 'Remove result buttons'
                    btn2.bind(on_release=self.btn2_pressed)
    
                    grid.add_widget(btn1)
                    grid.add_widget(btn2)
    
        def btn1_pressed(self, result, *args):
            new_text = result
            self.top_lbl.text = new_text
    
        def btn2_pressed(self, *args):
            self.grid_l.clear_widgets()
            #pass
    
    class buttons_pressedApp(App):
    
        def build(self):
    
            return HomeScreen()
    
    if __name__ == '__main__':
        buttons_pressedApp().run()
    

    And the kv file:

    #:kivy 1.7.2
    
    <HomeScreen>:
        scroll_view: scrollviewID
        top_lbl: lblID
        grid_l: gridlayoutID
        AnchorLayout:
            size_hint: 1, .1   
            pos_hint: {'x': 0, 'y': .9}
            anchor_x: 'center'
            anchor_y: 'center'
            Label:
                id: lblID
                text: 'Button Tester'
        Button:
            size_hint: 1, .1   
            pos_hint: {'x': 0, 'y': .8}
            text: 'Add theoretical search results'
            on_release: root.search_btn_pressed()
        ScrollView:
            id: scrollviewID
            orientation: 'vertical'
            pos_hint: {'x': 0, 'y': 0}
            size_hint: 1, .8
            bar_width: '8dp'
            GridLayout:
                id: gridlayoutID
                cols: 1
                size_hint: 1, None
                row_default_height: 40
                row_force_default: False
    

    As you can see when you run it, the first button created in kivy works super. However, you will also notice that the top label changed immediately, indicating that the first dynamically created button already executed the on_release function.

    You can't tell that the second dynamically created button, "Remove result buttons" was already executed because it removed all buttons when there were still none. However, it is evident the "Remove result buttons" button is being immediately executed when you press the "Add theoretical search results" button a second time. It should add two more buttons, but seems like nothing happens. This is because the "Remove result buttons" button is removing the previous two buttons, and then they are immediately being replaced.

    Then, of course, neither of the buttons seem to do anything.

    Should be easy for someone to fix the example given the similar question!

    Thanks in advance.

    EDIT:

    I've altered btn2 to reflect inclement's answer. Works perfectly. However, as he pointed out, when I do the same to btn1, some default arguments are being passed into the method and it gives an error. So, I left btn1 as before with the parenthesis and 'result' inside them as the argument. Of course, this is run immediately and return nothing to be bound (as inclement explained). I want to be able to pass in 'result' as already defined in the example, but naturally not run it immediately. My apologies.. I should have written it this way the first time.

    EDIT 2, the answer:

    To reflect inclements last comment, I just wanted to posted the whole working example again with the answer included.

    import kivy
    kivy.require('1.7.2') # replace with your current kivy version !
    
    from kivy.app import App
    from kivy.uix.screenmanager import ScreenManager, Screen
    from kivy.properties import ObjectProperty
    from kivy.uix.button import Button
    from kivy.uix.gridlayout import GridLayout
    from functools import partial
    
    i = ['some', 'words']
    
    class HomeScreen(Screen):
        grid_l = ObjectProperty(None)
        top_lbl = ObjectProperty(None)
    
        def search_btn_pressed(self):
            grid = self.grid_l
            grid.bind(minimum_height=grid.setter('height'),
                         minimum_width=grid.setter('width'))
    
            for result in i:
    
                    btn1 = Button(size_hint=(1, None))
                    btn1.text = '%r' % result
                    btn1.bind(on_release=partial(self.btn1_pressed, result))
    
                    btn2 = Button(size_hint=(1, None))
                    btn2.text = 'Remove result buttons'
                    btn2.bind(on_release=self.btn2_pressed)
    
                    grid.add_widget(btn1)
                    grid.add_widget(btn2)
    
        def btn1_pressed(self, result, *args):
            new_text = result
            self.top_lbl.text = new_text
    
        def btn2_pressed(self, *args):
            self.grid_l.clear_widgets()
            #pass
    
    class buttons_pressedApp(App):
    
        def build(self):
    
            return HomeScreen()
    
    if __name__ == '__main__':
        buttons_pressedApp().run()
    

    It works!

  • bosky
    bosky over 10 years
    Your answer was perfect for my example (and clear), and I want to give you the answer. However, I just edited my question to reflect that I do want to be able to pass arguments. Now btn1 is supposed to pass each 'result' in the list to btn1_pressed.
  • inclement
    inclement over 10 years
    Then you need to make a function that includes those arguments. A commonm trick is to use partial from the functools module, in which case you can do bind(on_release=partial(self.btn1_pressed, result)). partial is a function that takes a function and some arguments, and returns a new function that will be passed those arguments first.
  • bosky
    bosky over 10 years
    That's the ticket! Thanks!