I have a Panel with several images on it, each of which is bound to the same event handler. How can I determine which image is being clicked from the event handler? I tried using Event.GetEventObject() but it returns the parent panel instead of the image that was clicked.
Here's some sample code:
import math
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, id=-1,title="",pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE,
name="frame"):
wx.Frame.__init__(self,parent,id,title,pos,size,style,name)
self.panel = wx.ScrolledWindow(self,wx.ID_ANY)
self.panel.SetScrollbars(1,1,1,1)
num = 4
cols = 3
rows = int(math.ceil(num / 3.0))
sizer = wx.GridSizer(rows=rows,cols=cols)
filenames = []
for i in range(num):
filenames.append("img"+str(i)+".png")
for fn in filenames:
img = wx.Image(fn,wx.BITMAP_TYPE_ANY)
img2 = wx.BitmapFromImage(img)
img3 = wx.StaticBitmap(self.panel,wx.ID_ANY,img2)
sizer.Add(img3)
img3.Bind(wx.EVT_LEFT_DCLICK,self.OnDClick)
self.panel.SetSizer(sizer)
self.Fit()
def OnDClick(self, event):
print event.GetEventObject()
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyFrame(None)
frame.Show()
app.MainLoop()
In your loop, give each StaticBitmap widget a unique name. One way to do this would be something like this:
wx.StaticBitmap(self, wx.ID_ANY,
wx.BitmapFromImage(img),
name="bitmap%s" % counter)
And then increment the counter at the end. Then in the event handler, do something like this:
widget = event.GetEventObject()
print widget.GetName()
That's always worked for me.
Call GetId() on your event in the handler and compare the id it returns to the ids of your staticBitmaps. If you need an example let me know and Ill update my answer
You can use GetId(), but make sure you keep it unique across your program. I am posting modified code to show how can you do it. Despite of using filenames as list.
def __init__(self, parent, id=-1,title="",pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE,
name="frame"):
wx.Frame.__init__(self,parent,id,title,pos,size,style,name)
self.panel = wx.ScrolledWindow(self,wx.ID_ANY)
self.panel.SetScrollbars(1,1,1,1)
num = 4
cols = 3
rows = int(math.ceil(num / 3.0))
sizer = wx.GridSizer(rows=rows,cols=cols)
#you should use dict and map all id's to image files
filenames = []
for i in range(num):
filenames.append("img"+str(i)+".png")
for imgid,fn in enumerate(filenames):
img = wx.Image(fn,wx.BITMAP_TYPE_ANY)
img2 = wx.BitmapFromImage(img)
#pass the imgid here
img3 = wx.StaticBitmap(self.panel,imgid,img2)
sizer.Add(img3)
img3.Bind(wx.EVT_LEFT_DCLICK,self.OnDClick)
self.panel.SetSizer(sizer)
self.Fit()
def OnDClick(self, event):
print 'you clicked img%s'%(event.GetId() )
You can use dict and map every file name to id, by this way you will keep track of it all through your programe.
Related
Recently i found Hypertreelist, it seems to be what i Need, but there arent any good examples. So i have a few questions:
How do I add wx.Widgets to it? Like placing a Combobox
How do i make the Texts inside a row editable? Vie Doubleclick would be good
Here is my Code so far:
import wx
import wx.lib.agw.hypertreelist as HTL
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "HyperTreeList Demo")
tree_list = HTL.HyperTreeList(self)
tree_list.AddColumn("First column")
root = tree_list.AddRoot("Root")
parent = tree_list.AppendItem(root, "First child")
child = tree_list.AppendItem(parent, "First Grandchild")
tree_list.AppendItem(root, "Second child")
app = wx.App(0)
frame = MyFrame(None)
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()
You could try it with a HitTest and EditLabel. The code could look like this:
pt = event.GetPosition()
item, flags, column = self.tree.HitTest(pt)
if "CP" in item.GetText():
if column > 1:
self.tree.EditLabel(item, column)
event.Skip()
please i want to put several textctrl in a panel and im trying to set the names and positions dynamically, with the following code,controls appears but i cant assing the names.
p=0
for i in range(20):
p += 25
indesc ="ingdesc"
indesc = indesc + str(i)
print indesc
self.HERE I WANT TO PUT indesc value = wx.TextCtrl(self.panel,pos=(280,190+p),size=(350,23),style=wx.TE_READONLY)
thanks
You can do it like this:
import wx
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Indesc')
self.panel = wx.Panel(self)
textctrls=[]
for i in range(20):
textctrls.append("self.indesc"+str(i))
p=0
for i in range(20):
p+=25
textctrls[i] = wx.TextCtrl(self.panel,pos=(10,10+p),size=(350,23),style=wx.TE_READONLY)
textctrls[0].SetValue("Item 0")
textctrls[11].SetValue("Item 11")
self.Show()
if __name__ == '__main__':
app = wx.App()
frame = MainFrame()
app.MainLoop()
But and it's a big BUT you are making life difficult for yourself because you can't access the items by the string name self.indesc11 for example you have to refer to it by the position in the list (as far as I can tell).
It is sensible and clearer for the future, if you are explicit when coding this sort of thing. It's only copy and paste after all.
I have a problem with passing textCtrl data from one class to another in wxpython. I tried using the instance method of passing variables but if I use init_function it is only relevant at the start of the program and doesn't take into account any changes to the text control box after the initial start. Tried the Update() or Refresh() and it didn't work either.
Here is the code simplified.
class DropTarget(wx.DropTarget):
def __init__(self,textCtrl, *args, **kwargs):
super(DropTarget, self).__init__( *args, **kwargs)
self.tc2=kwargs["tc2"]
print self.tc2
class Frame(wx.Frame):
def __init__(self, parent, tc2):
self.tc2 = wx.TextCtrl(self, -1, size=(100, -1),pos = (170,60))#part number
def main():
ex = wx.App()
frame = Frame(None, None)
frame.Show()
b = DropTarget(None, kwarg['tc2'])
ex.MainLoop()
if __name__ == '__main__':
main()
The following way of passing the variable gives me a keyerror. Any help is appreciated.
This isn't the most elegant solution to the problem, but I had a similar issue. If you dump your text to a temporary text file, you can pick it back up whenever you want. So it would be something like this:
tmpFile = open("temp.txt",'w')
tmpFile.write(self.tc2.GetValue())
tmpFile.close()
#when you want the string back in the next class
tmpFile = open("temp.txt",'r')
string = tmpFile.read()
tmpFile.close()
os.system("del temp.txt") #This just removes the file to clean it up, you'll need to import os if you want to do this
import wx
class DropTarget(wx.DropTarget):
def __init__(self,textCtrl, *args, **kwargs):
self.tc2 = kwargs.pop('tc2',None) #remove tc2 as it is not a valid keyword for wx.DropTarget
super(DropTarget, self).__init__( *args, **kwargs)
print self.tc2
class Frame(wx.Frame):
def __init__(self, parent, tc2):
#initialize the frame
super(Frame,self).__init__(None,-1,"some title")
self.tc2 = wx.TextCtrl(self, -1, size=(100, -1),pos = (170,60))#part number
def main():
ex = wx.App(redirect=False)
frame = Frame(None, None)
frame.Show()
#this is how you pass a keyword argument
b = DropTarget(frame,tc2="something")
ex.MainLoop()
if __name__ == '__main__':
main()
there were at least a few errors in your code ... it at least makes the frame now
I have a set of images available. If I click on one of those images is there a way to determine which of the images has been clicked on in wxPython?
You will almost certainly have to calculate it for yourself. The most straight-forward method would be to use a mouse event like wx.EVT_LEFT_DOWN and grab the mouse's coordinates in the event handler. Then use that information to tell you where on your wxPython window you clicked. Each of your image widgets or DCs or whatever you're using can report it's size and position, so if the mouse coordinates are in X image's boundaries, you know it's been clicked on. You might also be able to use the HitTest() method, depending on what you're using to show the images.
EDIT: Here is how you would do it if you were using a wx.StaticBitmap, which actually lets you attach an wx.EVT_LEFT_DOWN to it:
import wx
class PhotoCtrl(wx.Frame):
def __init__(self):
size = (400,800)
wx.Frame.__init__(self, None, title='Photo Control', size=size)
self.panel = wx.Panel(self)
img = wx.EmptyImage(240,240)
self.imageCtrl = wx.StaticBitmap(self.panel, wx.ID_ANY,
wx.BitmapFromImage(img),
name="emptyImage")
imageCtrl2 = wx.StaticBitmap(self.panel, wx.ID_ANY,
wx.BitmapFromImage(img),
name="anotherEmptyImage")
self.imageCtrl.Bind(wx.EVT_LEFT_DOWN, self.onClick)
imageCtrl2.Bind(wx.EVT_LEFT_DOWN, self.onClick)
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(self.imageCtrl, 0, wx.ALL, 5)
mainSizer.Add(imageCtrl2, 0, wx.ALL, 5)
self.panel.SetSizer(mainSizer)
self.Show()
#----------------------------------------------------------------------
def onClick(self, event):
""""""
print event.GetPosition()
imgCtrl = event.GetEventObject()
print imgCtrl.GetName()
if __name__ == '__main__':
app = wx.App(False)
frame = PhotoCtrl()
app.MainLoop()
you dont tell us anything about how you are displaying your images? are you blitting them right on the dc? are you creating panels for them? etc... properly setting up your project is important. basically you give us zero information to help you with.
Keeping all that in mind, something like this would work fine (this is called a self contained code example, you should always provide one with your questions, to make it easier for people to help you)
import wx
a = wx.App(redirect=False)
f= wx.Frame(None,-1,"Some Frame",size = (200,200))
sz = wx.BoxSizer(wx.HORIZONTAL)
def OnClick(evt):
print "Clicked:",evt.GetId()-10023
for i,img in enumerate(["img1","img2","img3"]):
id = 10023+i
p = wx.Panel(f,-1)
sz.Add(p)
sz1 = wx.BoxSizer()
p.Bind(wx.EVT_LEFT_UP,OnClick)
bmp = wx.Image(img).ConvertToBitmap()
b = wx.StaticBitmap(p,-1,bmp)
sz1.Add(b)
p.SetSizer(sz1)
f.SetSizer(sz)
f.Layout()
f.Fit()
f.Show()
a.MainLoop()
Keep in mind I didnt test it... but theoretically it should work...
In the following code (inspired by this snippet), I use a single event handler buttonClick to change the title of the window. Currently, I need to evaluate if the Id of the event corresponds to the Id of the button. If I decide to add 50 buttons instead of 2, this method could become cumbersome. Is there a better way to do this?
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, 'wxBitmapButton',
pos=(300, 150), size=(300, 350))
self.panel1 = wx.Panel(self, -1)
self.button1 = wx.Button(self.panel1, id=-1,
pos=(10, 20), size = (20,20))
self.button1.Bind(wx.EVT_BUTTON, self.buttonClick)
self.button2 = wx.Button(self.panel1, id=-1,
pos=(40, 20), size = (20,20))
self.button2.Bind(wx.EVT_BUTTON, self.buttonClick)
self.Show(True)
def buttonClick(self,event):
if event.Id == self.button1.Id:
self.SetTitle("Button 1 clicked")
elif event.Id == self.button2.Id:
self.SetTitle("Button 2 clicked")
application = wx.PySimpleApp()
window = MyFrame()
application.MainLoop()
You could give the button a name, and then look at the name in the event handler.
When you make the button
b = wx.Button(self, 10, "Default Button", (20, 20))
b.myname = "default button"
self.Bind(wx.EVT_BUTTON, self.OnClick, b)
When the button is clicked:
def OnClick(self, event):
name = event.GetEventObject().myname
Take advantage of what you can do in a language like Python. You can pass extra arguments to your event callback function, like so.
import functools
def __init__(self):
# ...
for i in range(10):
name = 'Button %d' % i
button = wx.Button(parent, -1, name)
func = functools.partial(self.on_button, name=name)
button.Bind(wx.EVT_BUTTON, func)
# ...
def on_button(self, event, name):
print '%s clicked' % name
Of course, the arguments can be anything you want.
I recommend that you use different event handlers to handle events from each button. If there is a lot of commonality, you can combine that into a function which returns a function with the specific behavior you want, for instance:
def goingTo(self, where):
def goingToHandler(event):
self.SetTitle("I'm going to " + where)
return goingToHandler
def __init__(self):
buttonA.Bind(wx.EVT_BUTTON, self.goingTo("work"))
# clicking will say "I'm going to work"
buttonB.Bind(wx.EVT_BUTTON, self.goingTo("home"))
# clicking will say "I'm going to home"
Keep a dict with keys that are the .Id of the buttons and values that are the button names or whatever, so instead of a long if/elif chain you do a single dict lookup in buttonClick.
Code snippets: in __init__, add creation and update of the dict:
self.panel1 = wx.Panel(self, -1)
self.thebuttons = dict()
self.button1 = wx.Button(self.panel1, id=-1,
pos=(10, 20), size = (20,20))
self.thebuttons[self.button1.Id] = 'Button 1'
self.button1.Bind(wx.EVT_BUTTON, self.buttonClick)
and so on for 50 buttons (or whatever) [they might be better created in a loop, btw;-)].
So buttonClick becomes:
def buttonClick(self,event):
button_name = self.thebuttons.get(event.Id, '?No button?')
self.setTitle(button_name + ' clicked')
You could create a dictionary of buttons, and do the look based on the id ... something like this:
class MyFrame(wx.Frame):
def _add_button (self, *args):
btn = wx.Button (*args)
btn.Bind (wx.EVT_BUTTON, self.buttonClick)
self.buttons[btn.id] = btn
def __init__ (self):
self.button = dict ()
self._add_button (self.panel1, id=-1,
pos=(10, 20), size = (20,20))
self._add_button = (self.panel1, id=-1,
pos=(40, 20), size = (20,20))
self.Show (True)
def buttonClick(self,event):
self.SetTitle (self.buttons[event.Id].label)
I ran into a similar problem: I was generating buttons based on user-supplied data, and I needed the buttons to affect another class, so I needed to pass along information about the buttonclick. What I did was explicitly assign button IDs to each button I generated, then stored information about them in a dictionary to lookup later.
I would have thought there would be a prettier way to do this, constructing a custom event passing along more information, but all I've seen is the dictionary-lookup method. Also, I keep around a list of the buttons so I can erase all of them when needed.
Here's a slightly scrubbed code sample of something similar:
self.buttonDefs = {}
self.buttons = []
id_increment = 800
if (row, col) in self.items:
for ev in self.items[(row, col)]:
id_increment += 1
#### Populate a dict with the event information
self.buttonDefs[id_increment ] = (row, col, ev['user'])
####
tempBtn = wx.Button(self.sidebar, id_increment , "Choose",
(0,50+len(self.buttons)*40), (50,20) )
self.sidebar.Bind(wx.EVT_BUTTON, self.OnShiftClick, tempBtn)
self.buttons.append(tempBtn)
def OnShiftClick(self, evt):
### Lookup the information from the dict
row, col, user = self.buttonDefs[evt.GetId()]
self.WriteToCell(row, col, user)
self.DrawShiftPicker(row, col)
I needed to do the same thing to keep track of button-presses . I used a lambda function to bind to the event . That way I could pass in the entire button object to the event handler function to manipulate accordingly.
class PlatGridderTop(wx.Frame):
numbuttons = 0
buttonlist = []
def create_another_button(self, event): # wxGlade: PlateGridderTop.<event_handler>
buttoncreator_id = wx.ID_ANY
butonname = "button" + str(buttoncreator_id)
PlateGridderTop.numbuttons = PlateGridderTop.numbuttons + 1
thisbutton_number = PlateGridderTop.numbuttons
self.buttonname = wx.Button(self,buttoncreator_id ,"ChildButton %s" % thisbutton_number )
self.Bind(wx.EVT_BUTTON,lambda event, buttonpressed=self.buttonname: self.print_button_press(event,buttonpressed),self.buttonname)
self.buttonlist.append(self.buttonname)
self.__do_layout()
print "Clicked plate button %s" % butonname
event.Skip()
def print_button_press(self,event,clickerbutton):
"""Just a dummy method that responds to a button press"""
print "Clicked a created button named %s with wxpython ID %s" % (clickerbutton.GetLabel(),event.GetId())
Disclaimer : This is my first post to stackoverflow