Search code examples
pythonpython-3.xkivykivy-languagekivymd

How to create function buttons from variable items in a file Python Kivy APP


I am actually trying to create an application but I encountered a big issue

Specifically, it should check a folder filled with pictures for it's items and for each item it should create an ImageButton(already defined) in a Scollview GridLayout list. It works so far..

But when one of the displayed pictures is pressed, I want it to change the path of the picture on the main screen

This is my Python code

#This is outside the MDApp class
class ImageButton(ButtonBehavior, Image):

#Inside the MDApp class
def item_choser(self, item) 
 .
 .
 .
  elif item == "car":
    
    #It first clears the grid layout        
      self.root.ids.pictures_grid.clear_widgets()
    

    #Then, the path folder of the items(pictures) is being defined              
      pictures = glob.glob(f"./images/items/cars/*.jpg")
     

    #For loop created to assign            
      for i in pictures:
            
         #This "z" is the number after path in format <path\02_test_golf7_tez.jpg>      
         #Different for every picture (01, 02, 03 etc)
         z = i[-21:-19]
          
         #Creating button with bind
         z = ImageButton(source=i, allow_stretch=True, keep_ratio=True)
         z.bind(on_release=lambda x:self.chosen(i))  <<--- Here is my actual problem
         print(z)

         #Adding it into the grid
         self.root.ids.pictures_grid.add_widget(z)




def chosen(self, selectedCar):
    
    print(selectedCar)

    self.root.ids.main_image_display.source = selectedCar

This is what the path folder contains:

...cars\01_test_golf8_tez.jpg

...cars\02_test_golf7_tez.jpg

...cars\03_test_passat_te.jpg

...cars\04_test_crafter_t.jpg

All photos are rightly placed. For every " z " printed after the bind, it shows a different object in memory so until here everything is fine


But here is where my problem starts.

Let's say you want to pick the passat so you press the picture of it but somehow, whatever picture you press, the chosen function will always print "...cars\04_test_crafter_t.jpg"

How could this code be made in order to bind 1 button to it's function?

This was the closest answer found but either I don't understand how to use and integrate it in my code or it simply doesn't help [lambda x, i=i : i * x for i in range(len(pictures))] because it didn't work


Solution

  • This may not be the full answer, but I can't squeeze much code in a comment.

    Make gala a list then append each picture to it:

    gala = []
    for i in pictures:
       z = i[-21:-19]
       tmp = ImageButton(source=i, allow_stretch=True, keep_ratio=True)
       tmp.bind(on_release=lambda x, i=i:self.chosen(i))  # closure hack
       self.root.ids.pictures_grid.add_widget(tmp)
       gala.append(tmp)   # can reference later >  gala[0]
    

    Concerning the 'hack'

    The target function (lamda) does not run immediately. When defining the function, python uses late binding. With this, python stores the method address (lambda) and the parameter address (i). It does not store the value of the parameter. When the function is later called, the current (latest) value of the parameter variable is used.

    Take this example. When chosen is called, i will equal 2 because that is the value of i when the loop is completed but before the function is actually called.

    for i in range(3):  0-2
        tmp.bind(on_release=lambda i:self.chosen(i)) # address of i is stored, not value
    

    The hack resolves this issue by forcing early binding. Using i=i tells python there is a default value for that parameter (like def myfunc(i=5)) and early binding should be used. With early binding, the (default) parameter values are stored when the method is created, resolving the loop issue.

    for i in range(3):  0-2
        tmp.bind(on_release=lambda x, i=i:self.chosen(i)) # value of i is stored
    

    The x parameter may not be needed. You can experiment to confirm.

    @inclement provided a useful link in his comment: https://docs.python-guide.org/writing/gotchas/#late-binding-closures