I'm working on a form using wxPython where I want want listctrl's list of values to change based on the selection of another listctrl. To do this, I'm using methods linked to the controlling object's EVT_LIST_ITEM_SELECTED and EVT_LIST_ITEM_DESELECTED events to call Publisher.sendMessage. The control to be changed has a method that is a subscriber to that publisher. This works: when the first listctrl is clicked, the second is refreshed.
The problem is that the data must be refreshed from the database and a message is sent for every selection and deselection. This means that even if I simply click on one item, the database gets queried twice (once for the deselection, then again for the selection). If I shift-click to multi-select 5 items, then 5 calls get made. Is there any way to have the listctrl respond to the set, rather than the individual selections?
The best solution seems to be to use wx.CallAfter with a flag to execute the follow-up procedure exactly once:
import wx
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
self.list_ctrl_1 = wx.ListCtrl(self, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER)
sizer_1 = wx.BoxSizer(wx.HORIZONTAL)
sizer_1.Add(self.list_ctrl_1, 1, wx.EXPAND, 0)
self.list_ctrl_1.InsertColumn(0,"1")
self.list_ctrl_1.InsertStringItem(0,"HELLO1")
self.list_ctrl_1.InsertStringItem(0,"HELLO2")
self.list_ctrl_1.InsertStringItem(0,"HELLO3")
self.list_ctrl_1.InsertStringItem(0,"HELLO4")
self.list_ctrl_1.InsertStringItem(0,"HELLO5")
self.list_ctrl_1.InsertStringItem(0,"HELLO6")
self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, self.list_ctrl_1)
self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected, self.list_ctrl_1)
self.dirty = False
def Cleanup(self, StringToPrint):
print 'No Longer Dirty!'
self.dirty = False
def OnItemSelected(self,event):
print str(self.__class__) + " - OnItemSelected"
if not self.dirty:
self.dirty = True
wx.CallAfter(self.Cleanup)
event.Skip()
def OnItemDeselected(self,event):
print str(self.__class__) + " - OnItemDeselected"
if not self.dirty:
self.dirty = True
wx.CallAfter(self.Cleanup)
event.Skip()
if __name__ == "__main__":
app = wx.PySimpleApp(0)
wx.InitAllImageHandlers()
frame_1 = MyFrame(None, -1, "")
app.SetTopWindow(frame_1)
frame_1.Show()
app.MainLoop()
You can try EVT_LIST_ITEM_RIGHT_CLICK. That should work. Otherwise you'd want to use a flag and check said flag every time the selection event fires to see if it needs to query the database or not. There's also the UltimateListCtrl, a pure python widget, that you can probably hack to do this too.
You can push in a custom event handler
import wx
class MyEventHandler(wx.PyEvtHandler):
def __init__(self,target):
self.target = target
wx.PyEvtHandler.__init__(self)
def ProcessEvent(self,event):
# there must be a better way of getting the event type,
# but I couldn't find it
if event.GetEventType() == wx.EVT_LEFT_DOWN.evtType[0]:
print "Got Mouse Down event"
(item,where) = self.target.HitTest(event.GetPosition())
if item != -1:
print self.target.GetItem(item,0).GetText()
print where
else:
print "Not on list item though"
return True
else:
return False
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
self.list_ctrl_1 = wx.ListCtrl(self, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER)
self.myevthandler = MyEventHandler(self.list_ctrl_1)
sizer_1 = wx.BoxSizer(wx.HORIZONTAL)
sizer_1.Add(self.list_ctrl_1, 1, wx.EXPAND, 0)
self.list_ctrl_1.InsertColumn(0,"1")
self.list_ctrl_1.InsertStringItem(0,"HELLO1")
self.list_ctrl_1.PushEventHandler(self.myevthandler)
if __name__ == "__main__":
app = wx.PySimpleApp(0)
wx.InitAllImageHandlers()
frame_1 = MyFrame(None, -1, "")
app.SetTopWindow(frame_1)
frame_1.Show()
app.MainLoop()
Related
I would want to have a ComboBox with autocomplete as I have a list of more than 1000 items and would like to be able to select one element without having to go through the whole list, by only having part of the item string in the ComboBox.
I have been looking around and the question has been answered multiple times and I even checked the following link from a previous question here:
Auto-Completion In wxPython wxComboBox
and this other link:
https://github.com/RajaS/ACTextCtrl
However, when I try to run the example codes I always get the error: "module 'wx' has no attribute 'SimpleHtmlListBox'/'HtmlListBox' ".
What might be the reason for the error? And are there maybe other ways to achieve an autocomplete ComboBox?
I have converted the code in the first link you gave to make it work in python 3, wxPython Phoenix. Some changes has been added to make the combo box work better. The first comboBox has been tested it in Mac OSX. But when I tested it Linux gtk+, it doesn't work as expected so I created a work around in second line with a TextCtrl as a filter for comboBox.
import wx
class PromptingComboBox(wx.ComboBox) :
def __init__(self, parent, choices=[], style=0, **par):
wx.ComboBox.__init__(self, parent, wx.ID_ANY, style=style|wx.CB_DROPDOWN, choices=choices, **par)
self.choices = choices
self.Bind(wx.EVT_TEXT, self.OnText)
self.Bind(wx.EVT_KEY_DOWN, self.OnPress)
self.ignoreEvtText = False
self.deleteKey = False
def OnPress(self, event):
if event.GetKeyCode() == 8:
self.deleteKey = True
event.Skip()
def OnText(self, event):
currentText = event.GetString()
if self.ignoreEvtText:
self.ignoreEvtText = False
return
if self.deleteKey:
self.deleteKey = False
if self.preFound:
currentText = currentText[:-1]
self.preFound = False
for choice in self.choices :
if choice.startswith(currentText):
self.ignoreEvtText = True
self.SetValue(choice)
self.SetInsertionPoint(len(currentText))
self.SetTextSelection(len(currentText), len(choice))
self.preFound = True
break
class TrialPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, wx.ID_ANY)
choices = ['grandmother', 'grandfather', 'cousin', 'aunt', 'uncle', 'grandson', 'granddaughter']
for relative in ['mother', 'father', 'sister', 'brother', 'daughter', 'son']:
choices.extend(self.derivedRelatives(relative))
self.choices = choices = sorted(choices)
mainSizer = wx.FlexGridSizer(2, 2, 5, 10)
self.SetSizer(mainSizer)
mainSizer.Add(wx.StaticText(
self, -1, "Worked in Mac - python 3 - wx phoenix"))
cb1 = PromptingComboBox(self, choices=choices)
mainSizer.Add(cb1)
mainSizer.Add(wx.StaticText(self, -1, "Work arround in Linux-gtk"))
sizer2 = wx.BoxSizer(wx.HORIZONTAL)
mainSizer.Add(sizer2)
filterCtrl = wx.TextCtrl(self, -1, size=(150, -1))
filterCtrl.Bind(wx.EVT_TEXT, self.OnFilter)
sizer2.Add(filterCtrl)
self.cb2 = wx.ComboBox(self, -1, size=(150, -1), choices=choices)
sizer2.Add(self.cb2)
def derivedRelatives(self, relative):
return [relative, 'step' + relative, relative + '-in-law']
def OnFilter(self, event):
currentText = event.GetString().upper()
tmpChoices = [c for c in self.choices if c.startswith(currentText)]
if tmpChoices != []:
self.cb2.SetItems(tmpChoices)
self.cb2.SetValue(tmpChoices[0])
else:
self.cb2.SetValue('')
self.cb2.SetItems([])
if __name__ == '__main__':
app = wx.App(False)
frame = wx.Frame (None, -1, 'Demo PromptingComboBox Control and Work around',
size=(700, 400))
TrialPanel(frame)
frame.Show()
app.MainLoop()
I have created a web browser in python using WxPython. I've been able to get everything to function, go forward/back, reload, ect. My only problem is I'd like to be able to set the title at the top of the program to the title of the web page. I understand how to set the title with self.SetTitle(title), I also know from some of the research I've done that you can get the tile of the page using self.browser.GetCurrentTitle(). Only problem with using that is that it's a one time thing that doesn't refresh it's self, when a user clicks a new link. I'm assuming there is some sort of function or something I can catch when a new page is loaded and tell python to do something like this:
def OnLoad(self, event):
self.webtitle = self.browser.GetCurrentTitle()
self.browser.SetTitle(self.webtitle)
Or something along those lines I'm just not sure where or how I can connect or "catch" that function. I have read through the documentation of wx.html2.WebView, but I'm not able to make sense of how to do this, I have also looked through this site as well as a few mailing lists but I can't seem to find anything that would explain how to do this.
Here is the main code that will run my browser (obviously I've shortened it).
class PyBrowser(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
self.SetTitle('PyBrowser')
sizer = wx.BoxSizer(wx.VERTICAL)
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
#Element Variables
self.browser = wx.html2.WebView.New(self)
self.address = wx.TextCtrl(self, value="http://",size=(500, 26))
self.go = wx.Button(self, label="Go", id=wx.ID_OK)
self.back = wx.BitmapButton(self, -1, wx.Bitmap('img/Back Button.png', wx.BITMAP_TYPE_PNG), size=(26,26) ,style=wx.NO_BORDER)#wx.Button(self, label="Back")
self.forward = wx.BitmapButton(self, -1, wx.Bitmap('img/Forward Button.png', wx.BITMAP_TYPE_PNG), size=(26,26) ,style=wx.NO_BORDER)#wx.Button(self, label="Forward")
self.reload = wx.BitmapButton(self, -1, wx.Bitmap('img/Reload Button.png', wx.BITMAP_TYPE_PNG), size=(26,26) ,style=wx.NO_BORDER)#wx.Button(self, label = "Refresh")
self.result = None
#Nav area
addressarea = wx.BoxSizer()
addressarea.Add(self.address, proportion = 1, border = 0)
...
#Adding elements
sizer.Add(addressarea, proportion = 0, flag = wx.EXPAND, border = 0)
sizer.Add(self.browser, 1, wx.EXPAND, 10)
#Button binding
self.Bind(wx.EVT_BUTTON, self.OnGo, id=wx.ID_OK)
...
#Menu bar stuff
#File Menu
self.fileMenu = wx.Menu()
self.New = self.fileMenu.Append(wx.ID_ANY, 'New Window')
...
#Help Menu
self.helpMenu = wx.Menu()
self.Help = self.helpMenu.Append(wx.ID_ANY, 'Help')
...
self.Bind(wx.EVT_MENU,self.OnHelp,self.Help)
...
#Adding the actual Menu Bar
self.menuBar = wx.MenuBar()
self.menuBar.Append(self.fileMenu, 'File')
self.menuBar.Append(self.helpMenu, 'Help')
self.SetMenuBar(self.menuBar)
self.SetSizer(sizer)
self.SetSize((1000, 700))
def OnGo(self, event):
self.result = self.address.GetValue()
self.browser.LoadURL(self.result)
if __name__ == '__main__':
app = wx.App()
dialog = PyBrowser(None, -1)
dialog.browser.LoadURL("http://www.google.com")
dialog.Show()
app.MainLoop()
To summarize what I'm asking is how to I set the title of a wx.Frame to the current title of a web page inside of wx.html2.WebView and change the name every time a new link is clicked?
The webview events that you require are listed here
I would catch the EVT_WEBVIEW_LOADED event and update the title in the event handler. I believe you could do something like this:
def onLoaded(self, event):
title = self.browser.GetCurrentTitle()
self.SetTitle(title)
What may be even better is to catch EVT_WEBVIEW_TITLE_CHANGED as that actually detects when the page title changes.
I figured out that from what Mike said, using an event to catch it would work. So instead of EVT_WEBVIEW_TITLE_CHANGED I used EVT_WEBVIEW_LOADED, I then binded the event to my function to handle changing the title. It looked like this:
#Binding the function the event to the function.
self.Bind(wx.html2.EVT_WEBVIEW_LOADED, self.OnPageLoad, self.browser)
#Function binding to the page loading
def OnPageLoad(self, event):
self.WebTitle = self.browser.GetCurrentTitle()
self.SetTitle(self.WebTitle)
I'm working on a media player and am able to load in a single .wav and play it. As seen in the code below.
foo = wx.FileDialog(self, message="Open a .wav file...", defaultDir=os.getcwd(), defaultFile="", style=wx.FD_MULTIPLE)
foo.ShowModal()
queue = foo.GetPaths()
self.playing_thread = threading.Thread(target=self.playFile, args=(queue[0], 'msg'))
self.playing_thread.start()
But the problem is, when I try to make the above code into a loop for multiple .wav files. Such that while playing_thread.isActive == True, create and .start() the thread. Then if .isActive == False, pop queue[0] and load the next .wav file. Problem is, my UI will lock up and I'll have to terminate the program. Any ideas would be appreciated.
Since is using wx.python, use a Delayedresult, look at wx demos for a complete example.
Full minimal example:
import wx
import wx.lib.delayedresult as inbg
import time
class Player(wx.Frame):
def __init__(self):
self.titulo = "Music Player"
wx.Frame.__init__(self, None, -1, self.titulo,)
self.jobID = 0
self.Vb = wx.BoxSizer(wx.VERTICAL)
self.panel = wx.Panel(self,-1)
self.playlist = ['one','two']
self.abortEvent = inbg.AbortEvent()
self.msg = wx.StaticText(self.panel, -1, "...",pos=(30,-1))
self.msg.SetFont(wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD))
self.action = wx.Button(self.panel, -1,"Play Playlist")
self.Bind(wx.EVT_BUTTON, self.StartPlaying,self.action)
self.Vb.Add(self.msg, 0, wx.EXPAND|wx.ALL, 3)
self.Vb.Add(self.action, 0, wx.EXPAND|wx.ALL, 3)
self.panel.SetSizer(self.Vb)
self.Show()
def StartPlaying(self,evt):
self.BgProcess(self.Playme)
def Playme(self,jobID, abortEvent):
print "in bg"
list = self.getPlayList()
print list
for music in list:
self.msg.SetLabel('Playing: %s' % music)
stop = 100
while stop > 0:
print stop
stop -=1
self.msg.SetLabel('Playing: %s [%s ]' % (music,stop))
def _resultConsumer(self, inbg):
jobID = inbg.getJobID()
try:
result = inbg.get()
return result
except Exception, exc:
return False
def getPlayList(self):
return self.playlist
def setPlayList(self,music):
self.playlist.appdend(music)
def BgProcess(self,executar):
self.abortEvent.clear()
self.jobID += 1
inbg.startWorker(self._resultConsumer, executar, wargs=(self.jobID,self.abortEvent), jobID=self.jobID)
app = wx.App(False)
demo = Player()
app.MainLoop()
Could someone show me how I could return a value from a wxPython Frame? When the use clicks close, I popup a message dialog asking him a question. I would like to return the return code of this message dialog to my calling function.
Thanks
Because the wxFrame has events that process via the app.MainLoop() functionality, the only way to get at the return value of a wx.Frame() is via catching an event.
The standard practice of handling events is typically from within the class which derives from wx.Window itself (e.g., Frame, Panel, etc.). Since you want code exterior to the wx.Frame to receive information that was gathered upon processing the OnClose() event, then the best way to do that is to register an event handler for your frame.
The documentation for wx.Window::PushEventHandler is probably the best resource and even the wxpython wiki has a great article on how to do this. Within the article, they register a custom handler which is an instance of "MouseDownTracker." Rather than instantiating within the PushEventHandler call, you'd want to instantiate it prior to the call so that you can retain a handle to the EventHandler derived class. That way, you can check on your derived EventHandler class-variables after the Frame has been destroyed, or even allow that derived class to do special things for you.
Here is an adaptation of that code from the wx python wiki (admittedly a little convoluted due to the requirement of handling the results of a custom event with a "calling" function):
import sys
import wx
import wx.lib.newevent
(MyCustomEvent, EVT_CUSTOM) = wx.lib.newevent.NewEvent()
class CustomEventTracker(wx.EvtHandler):
def __init__(self, log, processingCodeFunctionHandle):
wx.EvtHandler.__init__(self)
self.processingCodeFunctionHandle = processingCodeFunctionHandle
self.log = log
EVT_CUSTOM(self, self.MyCustomEventHandler)
def MyCustomEventHandler(self, evt):
self.log.write(evt.resultOfDialog + '\n')
self.processingCodeFunctionHandle(evt.resultOfDialog)
evt.Skip()
class MyPanel2(wx.Panel):
def __init__(self, parent, log):
wx.Panel.__init__(self, parent)
self.log = log
def OnResults(self, resultData):
self.log.write("Result data gathered: %s" % resultData)
class MyFrame(wx.Frame):
def __init__(self, parent, ID=-1, title="", pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE):
wx.Frame.__init__(self, parent, ID, title, pos, size, style)
self.panel = panel = wx.Panel(self, -1, style=wx.TAB_TRAVERSAL | wx.CLIP_CHILDREN | wx.FULL_REPAINT_ON_RESIZE)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add((25, 25))
row = wx.BoxSizer(wx.HORIZONTAL)
row.Add((25,1))
m_close = wx.Button(self.panel, wx.ID_CLOSE, "Close")
m_close.Bind(wx.EVT_BUTTON, self.OnClose)
row.Add(m_close, 0, wx.ALL, 10)
sizer.Add(row)
self.panel.SetSizer(sizer)
def OnClose(self, evt):
dlg = wx.MessageDialog(self, "Do you really want to close this frame?", "Confirm Exit", wx.OK | wx.CANCEL | wx.ICON_QUESTION)
result = dlg.ShowModal()
dlg.Destroy()
if result == wx.ID_CANCEL:
event = MyCustomEvent(resultOfDialog="User Clicked CANCEL")
self.GetEventHandler().ProcessEvent(event)
else: # result == wx.ID_OK
event = MyCustomEvent(resultOfDialog="User Clicked OK")
self.GetEventHandler().ProcessEvent(event)
self.Destroy()
app = wx.App(False)
f2 = wx.Frame(None, title="Frame 1 (for feedback)", size=(400, 350))
p2 = MyPanel2(f2, sys.stdout)
f2.Show()
eventTrackerHandle = CustomEventTracker(sys.stdout, p2.OnResults)
f1 = MyFrame(None, title="PushEventHandler Tester (deals with on close event)", size=(400, 350))
f1.PushEventHandler(eventTrackerHandle)
f1.Show()
app.MainLoop()
You can get the result of clicking the OK, CANCEL buttons from the Dialog ShowModal method.
Given dialog is an instance of one of the wxPython Dialog classes:
result = dialog.ShowModal()
if result == wx.ID_OK:
print "OK"
else:
print "Cancel"
dialog.Destroy()
A few years late for the initial question, but when looking for the answer to this question myself, I stumbled upon a built-in method of getting a return value from a modal without messing with any custom event funniness. Figured I'd post here in case anyone else needs it.
It's simply this guy right here:
wxDialog::EndModal void EndModal(int retCode)
Ends a modal dialog, passing a value to be returned from the
*wxDialog::ShowModal invocation.*
Using the above, you can return whatever you want from the Dialog.
An example usage would be subclassing a wx.Dialog, and then placing the EndModal function in the button handlers.
class ProjectSettingsDialog(wx.Dialog):
def __init__(self):
wx.Dialog.__init__(self, None, -1, "Project Settings", size=(600,400))
sizer = wx.BoxSizer(wx.VERTICAL) #main sized
sizer.AddStretchSpacer(1)
msg = wx.StaticText(self, -1, label="This is a sample message")
sizer.Add(msg, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 15)
horizontal_sizer = wx.BoxSizer(wx.HORIZONTAL)
okButton = wx.Button(self, -1, 'OK')
self.Bind(wx.EVT_BUTTON, self.OnOK, okButton)
cancelBtn = wx.Button(self, -1, "Cancel")
self.Bind(wx.EVT_BUTTON, self.OnCancel, cancelBtn)
horizontal_sizer.Add(okButton, 0, wx.ALIGN_LEFT)
horizontal_sizer.AddStretchSpacer(1)
horizontal_sizer.Add(cancelBtn, 0, wx.ALIGN_RIGHT)
sizer.Add(horizontal_sizer, 0)
sizer.AddStretchSpacer(1)
self.SetSizer(sizer)
def OnOK(self, event):
self.EndModal(wx.ID_OK) #returns numeric code to caller
self.Destroy()
def OnCancel(self, event):
self.EndModal(wx.ID_CANCEL) #returns numeric code to caller
self.Destroy()
(Note: I just banged this code out quickly; didn't test the sizers)
As you can see, all you need to do is call the EndModal from a button event to return a value to whatever spawned the dialog.
I wanted to do the same thing, to have a graphical "picker" that I could run from within a console app. Here's how I did it.
# Fruit.py
import wx
class Picker (wx.App):
def __init__ (self, title, parent=None, size=(400,300)):
wx.App.__init__(self, False)
self.frame = wx.Frame(parent, title=title, size=size)
self.apple_button = wx.Button(self.frame, -1, "Apple", (0,0))
self.apple_button.Bind(wx.EVT_BUTTON, self.apple_button_click)
self.orange_button = wx.Button(self.frame, -1, "Orange", (0,100))
self.orange_button.Bind(wx.EVT_BUTTON, self.orange_button_click)
self.fruit = None
self.frame.Show(True)
def apple_button_click (self, event):
self.fruit = 'apple'
self.frame.Destroy()
def orange_button_click (self, event):
self.fruit = 'orange'
self.frame.Destroy()
def pick (self):
self.MainLoop()
return self.fruit
Then from a console app, I would run this code.
# Usage.py
import Fruit
picker = Fruit.Picker('Pick a Fruit')
fruit = picker.pick()
print 'User picked %s' % fruit
user1594322's answer works but it requires you to put all of your controls in your wx.App, instead of wx.Frame. This will make recycling the code harder.
My solution involves define a "PassBack" variable when defining your init function. (similar to "parent" variable, but it is normally used already when initiating a wx.Frame)
From my code:
class MyApp(wx.App):
def __init__ (self, parent=None, size=(500,700)):
wx.App.__init__(self, False)
self.frame = MyFrame(parent, -1, passBack=self) #Pass this app in
self.outputFromFrame = "" #The output from my frame
def getOutput(self):
self.frame.Show()
self.MainLoop()
return self.outputFromFrame
and for the frame class:
class MyFrame(wx.Frame):
def __init__(self, parent, ID, passBack, title="My Frame"):
wx.Frame.__init__(self, parent, ID, title, size=(500, 700))
self.passBack = passBack #this will be used to pass back variables/objects
and somewhere during the execution of MyFrame
self.passBack.outputFromFrame = "Hello"
so all in all, to get a string from an application
app = MyApp()
val = app.getOutput()
#Proceed to do something with val
Check this answer on comp.lang.python: Linkie
I don't think a wxFrame can return a value since it is not modal. If you don't need to use a wxFrame, then a modal dialog could work for you. If you really need a frame, I'd consider using a custom event.
It would go something like this:
(1) User clicks to close the wxFrame
(2) You override OnClose (or something like that) to pop up a dialog to ask the user a question
(3) Create and post the custom event
(4) Close the wxFrame
(5) Some other code processes your custom event
I think I just had the same problem as you. Instead of making that popup a frame, I made it a dialog instead. I made a custom dialog by inheriting a wx.dialog instead of a wx.frame. Then you can utilize the code that joaquin posted above. You check the return value of the dialog to see what was entered. This can be done by storing the value of the textctrl when the user clicks ok into a local variable. Then before it's destroyed, you get that value somehow.
The custom dialog section of this site helped me out greatly.
http://zetcode.com/wxpython/dialogs/
I'm having a bit of trouble with a panel that has two wxPython TextCtrls in it. I want either an EVT_CHAR or EVT_KEY_UP handler bound to both controls, and I want to be able to tell which TextCtrl generated the event. I would think that event.Id would tell me this, but in the following sample code it's always 0. Any thoughts? I've only tested this on OS X.
This code simply checks that both TextCtrls have some text in them before enabling the Done button
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, ID, title):
wx.Frame.__init__(self, parent, ID, title,
wx.DefaultPosition, wx.Size(200, 150))
self.panel = BaseNameEntryPanel(self)
class BaseNameEntryPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
self.entry = wx.TextCtrl(self, wx.NewId())
self.entry2 = wx.TextCtrl(self, wx.NewId())
self.donebtn = wx.Button(self, wx.NewId(), "Done")
self.donebtn.Disable()
vsizer = wx.BoxSizer(wx.VERTICAL)
vsizer.Add(self.entry, 1, wx.EXPAND|wx.GROW)
vsizer.Add(self.entry2, 1, wx.EXPAND|wx.GROW)
vsizer.Add(self.donebtn, 1, wx.EXPAND|wx.GROW)
self.SetSizer(vsizer)
self.Fit()
self.entry.Bind(wx.EVT_KEY_UP, self.Handle)
self.entry2.Bind(wx.EVT_KEY_UP, self.Handle)
def Handle(self, event):
keycode = event.GetKeyCode()
print keycode, event.Id # <- event.Id is always 0!
def checker(entry):
return bool(entry.GetValue().strip())
self.donebtn.Enable(checker(self.entry) and checker(self.entry2))
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, -1, "Hello from wxPython")
frame.Show(True)
self.SetTopWindow(frame)
return True
app = MyApp(0)
app.MainLoop()
You could try event.GetId() or event.GetEventObject() and see if either of these work.
Another approach to this is to use lambda or functools.partial to effectively pass a parameter to the handler. So, for example, sub in the lines below into your program:
self.entry.Bind(wx.EVT_KEY_UP, functools.partial(self.Handle, ob=self.entry))
self.entry2.Bind(wx.EVT_KEY_UP, functools.partial(self.Handle, ob=self.entry2))
def Handle(self, event, ob=None):
print ob
And then ob will be either entry or entry2 depending on which panel is clicked. But, of course, this shouldn't be necessary, and GetId and GetEventObject() should both work -- though I don't (yet) have a Mac to try these on.