hi,

I want to load an unknown number of buttons (20 max) into a panel.
When clicking a button, some other code should be executed.
Below code shows my attempt at solving the problem. Unfortunately execution results in an assertion error. Any help in pointing me in the right direction is much appreciated:

import wx

"""trying to bind an unknown number of images to click event"""

class MyApp(wx.App):

    def OnInit(self):
        frame = MyFrame(None, -1)
        frame.Show()
        self.SetTopWindow(frame)
        return True

class MyFrame(wx.Frame):

    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id, 'Bind unknown number of images', size=(1000, 500))
        panel = wx.Panel(self)

        mainSizer = wx.GridBagSizer(hgap=5, vgap=5)
        testSizer = wx.GridSizer(cols = 4, rows =0 )

        images = "g1 g2 g3 g4 g5 ".split()
        n=0

        for image in images:
            n=n+1
            path = 'O:\Python Programming Bits\bind test\%s.gif' % image
            image= wx.Image(path, wx.BITMAP_TYPE_ANY).Scale(80, 80).ConvertToBitmap()
            button = wx.BitmapButton(panel,  -1, bitmap=image, name=image)
            button.Bind(wx.BitmapButton, self.OnButton)
            testSizer.Add(button, flag=wx.EXPAND)

        mainSizer.Add(testSizer, pos=(2, 2), span=(2, 5 ), flag=wx.EXPAND)
        panel.SetSizer(mainSizer)
        mainSizer.Fit(self)
        mainSizer.SetSizeHints(self)

    def OnButton(self, event):
        print "you clicked on ... " + button_by_id.GetName()

    def OnQuit(self, event):
        self.Close()

if __name__ == '__main__':
    app = MyApp(False)
    app.MainLoop()    


app.MainLoop()    

I don't use Wx, i tend to lean towards GTK, but this is what I've figured out. Setting the ID manually, and saving it to a dict with the filename as the value will let you retrieve it later. There were several problems with your implementation, such as redefining the image variable. If image is going to be used for the image file name, then your image object needs to be called something else (like imageobj in my example.). The Bind() function expects wx.EVT_BUTTON, you were passing the wx.Bitmap class. At the bottom, your app.MainLoop() gets called twice if __name__ == "__main__". Just take a look at what I've done and see if it can help you. So far, when I click a button it prints out "you clicked on #2 /my/image/path/my_image.png".

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""trying to bind an unknown number of images to click event"""

import wx
# Used for listing directories, 
# can be removed if you don't need it.
import os.path


class MyApp(wx.App):

    def OnInit(self):
        self.frame = MyFrame(None, -1)
        self.frame.Show()
        self.SetTopWindow(self.frame)
        return True


class MyFrame(wx.Frame):

    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id, 'Bind unknown number of images', size=(1000, 500))
        panel = wx.Panel(self)

        mainSizer = wx.GridBagSizer(hgap=5, vgap=5)
        testSizer = wx.GridSizer(cols = 4, rows =0 )
        # REPLACE THIS WITH A LIST OF YOUR IMAGES (full path and filename)
        # or use your previous method.
        imagedir = "/home/cj/test_images"
        images = os.listdir(imagedir)
        images = [os.path.join(imagedir, i) for i in images if os.path.isfile(os.path.join(imagedir,i))]

        # THIS WILL BE USED TO STORE AND RECALL BUTTON IDs
        self.button_names = {}
        # Debug Print, can be removed.
        print "Using images:" + '\n'.join(images)
        # Used to track ID being used.
        n=0
        # Cycle thru image paths
        for image in images:
            try:
                n=n+1
                imageobj= wx.Image(image, wx.BITMAP_TYPE_ANY).Scale(80, 80).ConvertToBitmap()
                # Set ID manually so it can be used later
                # Set button name to image path/filename
                button = wx.BitmapButton(panel, bitmap=imageobj, id=n, name=image)
                # Save image name to dict using button id as key
                self.button_names[n] = image

                button.Bind(wx.EVT_BUTTON, self.OnButton)
                testSizer.Add(button, flag=wx.EXPAND)
            except Exception as ex:
                # Skip over images that cause errors (you could handle them properly)
                print "Error loading image:\n" + str(ex)
                pass
        # Debug print, can be removed.    
        print "Added " + str(n) + " buttons."
        mainSizer.Add(testSizer, pos=(2, 2), span=(2, 5 ), flag=wx.EXPAND)
        panel.SetSizer(mainSizer)
        mainSizer.Fit(self)
        mainSizer.SetSizeHints(self)

    def OnButton(self, event):
        buttonid = event.GetId()
        # Button ID that we set earlier
        print "you clicked on #" + str(buttonid) + "..."
        # Print button name (image name)
        print "    " + self.button_names[buttonid]

    def OnQuit(self, event):
        self.Close()

if __name__ == '__main__':
    app = MyApp(False)
    app.MainLoop()
# Script ends here, if __name__ != __main__, do nothing.

Edited 3 Years Ago by chriswelborn: typos

I just saw an even better way to do it, by adding a property to the button when it's created, and then using the proper event.GetEventObject() to retrieve it.

Change the creation to this:

# Set custom image_name property
button = wx.BitmapButton(panel, bitmap=imageobj, id=-1 name=image)
button.image_name = image

Change OnButton() to this:

def OnButton(self, event):
    buttonobj = event.GetEventObject()
    print "You selected: " + buttonobj.image_name

This way, no dict is needed and you're not messing with IDs.
Sorry for the double-post, I haven't really messed with Wx. I would definitely use this method instead of the first one. It's much cleaner than the first.

Edited 3 Years Ago by chriswelborn: added a note

Chris,
thanks for help, you put a ton of effort into it.
It will take me a little while to work through all of your suggestions and understand what you propose.
Will be back with questions/comments.
thanks again!

This article has been dead for over six months. Start a new discussion instead.