I'm working on making a bind event on a button refer to a function, however i get the error explained in the title.
I already have another bind event working and coded, and as far as i can tell there is zero difference between the two syntactically.
def OnBtnSuperTesting(self, event):
class MainWindow(wx.Frame):
def updateList(self, e):
#########Defining some listbox possibilites
T89 = [large array was here]
T87AZ = [large array was here]
T89ZA = T89AZ[::-1]
T87ZA = T87AZ[::-1]
#############
if self.radioAtoZ.GetValue() == True:
if self.radioT89.GetValue() == True:
choices = T89AZ
else:
choices = T87AZ
elif self.radioZtoA.GetValue() == True:
if self.radioT89.GetValue() == True:
choices = T89ZA
else:
choices = T87ZA
else:
if self.radioT89.GetValue() == True:
choices = T89
else:
choices = T87
self.listbox.Set(choices)
def Generate(self, e):
#probably need a try except here
selection = self.listbox.GetString(self.listbox.GetSelection())
if self.radioT89 == True:
if selection == 'String name here':
self.pathname = 'pathname here (its coded)'
#Assume the indentation here is right, StackOverflow isn't fommating this nicely
self.lstCommands.AppendRows(1, 1)
item = self.lstCommands.GetNumberRows()-1
self.lstCommands.SetCellValue(item, 0, "Add Module")
self.lstCommands.SetCellValue(item, 1, self.pathname)
self.modifiedFlg = True
def __init__(self, parent, title):
self.dirname=''
wx.Frame.__init__(self, parent, title=title, size=(320,440))
self.SetBackgroundColour(wx.WHITE)
self.CenterOnScreen()
self.CreateStatusBar()
self.radioT89 = wx.RadioButton(self, -1, 'T89 only', pos = (2,0), style = wx.RB_GROUP)
self.radioT87 = wx.RadioButton(self, -1, 'T87 only', pos = (154, 0))
self.radioKeySort = wx.RadioButton(self, -1, 'Sort by Key', pos = (2,40), style = wx.RB_GROUP)
self.radioAtoZ = wx.RadioButton(self, -1, 'Sort Name A-Z', pos = (2,60))
self.radioZtoA = wx.RadioButton(self, -1, 'Sort Name Z-A', pos = (2,80))
self.checkCode = wx.CheckBox(self, -1, 'Generate Code', pos = (154,40))
self.checkBuild = wx.CheckBox(self, -1, 'Generate Build Report', pos = (154, 60))
self.ln = wx.StaticLine(self, -1, pos = (0,15), size = (300,3), style = wx.LI_HORIZONTAL)
self.ln2 = wx.StaticLine(self, -1, pos = (150,15), size = (3,100), style = wx.LI_VERTICAL)
self.radioT87.Bind(wx.EVT_RADIOBUTTON, self.updateList)
self.radioT89.Bind(wx.EVT_RADIOBUTTON, self.updateList)
self.radioKeySort.Bind(wx.EVT_RADIOBUTTON, self.updateList)
self.radioAtoZ.Bind(wx.EVT_RADIOBUTTON, self.updateList)
self.radioZtoA.Bind(wx.EVT_RADIOBUTTON, self.updateList)
self.go.Bind(wx.EVT_BUTTON, self.Generate)
self.go = wx.Button(self,-1, label = 'Go!', pos = (110, 325))
# Setting up the menu.
filemenu= wx.Menu()
menuAbout= filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")
menuExit = filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program")
# Creating the menubar.
menuBar = wx.MenuBar()
menuBar.Append(filemenu,"&File")
self.SetMenuBar(menuBar)
# Events.
self.Bind(wx.EVT_MENU, self.OnExit, menuExit)
self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
self.SetAutoLayout(1)
self.Show()
def OnExit(self,e):
self.Close(True) # Close the frame.
app = wx.App(False)
frame = MainWindow(None, "Supervisory Testing")
app.MainLoop()
So, you can see that the def updateList is indented the same as def Generate. It also has the same parameters and the same bind event syntax, with the exception that one is a radio button and the other is a button. What am i doing wrong?
You are binding to self.go in the line before you have asigned wx.Button to self.go, move the bind to after the button is created.
Related
I'm trying to make a series of buttons navigable with the arrow keys and enter key.
I have the arrow keys implemented easily enough, but I can't seem to find the answer online how to bind the focused button to the enter key. Specifically I want to be able to use the up/down keys to set the focus to a button, and then hit the enter key to activate the button.
I can highlight the button i want with the arrows, but the enter key does nothing. Any ideas?
import wx
class Example(wx.Frame):
def __init__(self, parent, title):
frame = wx.Frame.__init__(self, parent, title=title, )
self.btn = []
self.btn.append(wx.Button(self, label='Button 1', id=0))
self.btn.append(wx.Button(self, label='Button 2', id=1))
self.Bind(wx.EVT_BUTTON, self.button1_press, id=0)
self.Bind(wx.EVT_TEXT_ENTER, self.button1_press)
self.Bind(wx.EVT_BUTTON, self.button2_press, id=1)
self.Bind(wx.EVT_TEXT_ENTER, self.button2_press)
self.Bind(wx.EVT_CHAR_HOOK, self.on_key)
self.sizer = wx.GridBagSizer(0, 0)
self.sizer.Add(self.btn[0], pos=(0, 0), flag=wx.ALIGN_CENTER)
self.sizer.Add(self.btn[1], pos=(1, 0), flag=wx.ALIGN_CENTER)
self.SetSizer(self.sizer)
self.Fit()
def button1_press(self, event):
print 'button 1'
def button2_press(self, event):
print 'button 2'
def on_key(self, event):
i = self.get_focus()
if event.GetKeyCode() == wx.WXK_DOWN:
i = min(i+1, 1)
self.btn[i].SetFocus()
elif event.GetKeyCode() == wx.WXK_UP:
i = max(i-1, 0)
self.btn[i].SetFocus()
elif event.GetKeyCode() == wx.WXK_RETURN: # <-doesn't work
print 'ENTER!'
else:
event.Skip()
def get_focus(self):
focused = wx.Window.FindFocus()
if focused == self.btn[0]:
return 0
elif focused == self.btn[1]:
return 1
class AppMenu(wx.App):
def OnInit(self):
'Create the main window and insert the custom frame'
frame = Example(None, 'Example')
frame.Show(True)
return True
app = AppMenu(0)
app.MainLoop()
Your code is close and to be honest, on my machine (ubuntu 18.04) enter does work but the arrows keys don't.
The key to this appears to be setting Default() for the focus within the window.
If you put it all in a Panel you get the added benefit of Tab traversval, so tabbing moves between your buttons as well, without any effort on your part.
Here is a version of your code working on my box using python2.7 wx (3.0.2.0) and python 3.6 wx (4.0.3)
import wx
class Example(wx.Frame):
def __init__(self, parent, title):
frame = wx.Frame.__init__(self, parent, title=title, )
self.panel = wx.Panel(self, -1, size=(200,100))
self.btn1 = wx.Button(self.panel, label='Button 1', id=1)
self.btn2 = wx.Button(self.panel, label='Button 2', id=2)
self.btn1.SetDefault()
self.btn1.SetFocus()
self.sizer = wx.GridBagSizer(0, 0)
self.sizer.Add(self.btn1, pos=(0, 0), flag=wx.ALIGN_CENTER)
self.sizer.Add(self.btn2, pos=(1, 0), flag=wx.ALIGN_CENTER)
self.Bind(wx.EVT_BUTTON, self.button_press)
self.Bind(wx.EVT_CHAR_HOOK, self.on_key)
self.panel.SetSizer(self.sizer)
self.Fit()
def button_press(self, event):
Id = event.GetId()
print ('Click Button',str(Id))
def on_key(self, event):
key = event.GetKeyCode()
if key == wx.WXK_DOWN or key == wx.WXK_UP:
i = self.get_focus()
if i == 1:
self.btn1.SetDefault()
self.btn1.SetFocus()
else:
self.btn2.SetDefault()
self.btn2.SetFocus()
print ('Focus on',str(i))
elif key == wx.WXK_RETURN:
print ('ENTER on Button',str(event.GetId()))
else:
event.Skip()
def get_focus(self):
focused = wx.Window.FindFocus()
if focused == self.btn1:
return 2
elif focused == self.btn2:
return 1
class AppMenu(wx.App):
def OnInit(self):
'Create the main window and insert the custom frame'
frame = Example(None, 'Example')
frame.Show(True)
return True
app = AppMenu()
app.MainLoop()
This code reads a picture and put it as background in a window.
There are two issues I cannot explain:
once you import the picture, clicking the red "X" in the top-right corner doesn't close the window.
if you try to drag the image, the program crashes.
Why is it so?
Thank you
import wx
import wx.lib.buttons as buttons
class Main(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, id=-1, title=title, size=(300, 300))
self.initUI()
self.panel = wx.Panel(self, wx.ID_ANY, wx.DefaultPosition, size=(10000, 10000))
self.backGroundImage=''
self.Layout()
def initUI(self):
menubar = wx.MenuBar()
fileMenu = wx.Menu()
fileMenu.AppendSeparator()
imp = wx.Menu()
importBackgroundButton = imp.Append(wx.ID_ANY, 'Import background')
self.Bind(wx.EVT_MENU, self.OnImportBackground, importBackgroundButton)
fileMenu.AppendMenu(wx.ID_ANY, 'I&mport', imp)
menubar.Append(fileMenu, '&File')
self.SetMenuBar(menubar)
self.SetTitle('test')
self.Centre()
self.Show(True)
#load background
def OnImportBackground(self, e):
app = wx.App(None)
style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
dialog = wx.FileDialog(None, 'Open', wildcard='*.png', style=style)
if dialog.ShowModal() == wx.ID_OK:
path = dialog.GetPath()
else:
path = None
dialog.Destroy()
self.backgroundImage = ButtonImage(self, self.panel, path, (0, 0))
W = self.backgroundImage.bmp.GetSize()[0]
H = self.backgroundImage.bmp.GetSize()[1]
self.SetSize((W+16, H+58))
self.Refresh()
#crash
class ButtonImage():
def __init__(self, parent, panel, nameImage, pos):
self.panel = panel
self.bmp = wx.Bitmap(nameImage, wx.BITMAP_TYPE_ANY)
self.maxPiecePositionX = self.panel.GetSize()[0] - self.bmp.GetSize()[0]
self.maxPiecePositionY = self.panel.GetSize()[1] - self.bmp.GetSize()[1]
self.bmapBtn = wx.BitmapButton(self.panel, id=wx.ID_ANY, bitmap=self.bmp, style=wx.NO_BORDER, pos=pos)
self.bmapBtn.Bind(wx.EVT_LEFT_DOWN, self.OnClickDown, self.bmapBtn)
self.bmapBtn.Bind(wx.EVT_LEFT_UP, self.OnClickUp, self.bmapBtn)
self.bmapBtn.Bind(wx.EVT_MOTION, self.MoveButton, self.bmapBtn)
self.hold = 0
self.holdPosition = (0, 0)
def EnterButton(self, event):
pass
def LeaveButton(self, event):
self.hold = 0
def OnClickDown(self, event):
obj = event.GetEventObject()
self.hold = 1
self.holdPosition = (event.GetX(), event.GetY())
def OnClickUp(self, event):
self.hold = 0
def MoveButton(self, event):
deltaX, deltaY = 0, 0
if self.hold:
deltaX = event.GetPosition()[0] - self.holdPosition[0]
deltaY = event.GetPosition()[1] - self.holdPosition[1]
newPositionX = self.bmapBtn.GetPosition()[0] + deltaX
newPositionY = self.bmapBtn.GetPosition()[1] + deltaY
if (0 < newPositionX < self.maxPiecePositionX) and (0 < newPositionY < self.maxPiecePositionY):
self.bmapBtn.SetPosition((newPositionX, newPositionY))
else:
self.holdPosition = self.holdPosition[0] + deltaX, self.holdPosition[1] + deltaY
self.bmapBtn.Raise()
self.bmapBtn.Refresh()
app = wx.App()
frame = Main(None, "Test")
frame.Show()
app.MainLoop()
This example has so many issues that it would take a long time to explain it. E. g., you create a new ButtonImage, which is essentially a wx.BitmapButton, every time you call OnImportBackground without destroying the old one, stacking up a collection of bitmap buttons without properly layouting them.
But what is driving the nail into the coffin that you instantiate a new wx.App every time OnImportBackground is called. If you remove this line (which is completely pointless), the frame can at least be closed.
But to see for "the right way (TM)" to do it, look at this stackoverflow post.
I have a listbox with a set of strings. The set of strings I want to display depends on which radio button is selected. I would like it such that while the user is interacting with the Form, if they ever change the radio button it will update the list box.
Here is my code (I'm leaving the array for t87 and t89 out because they are very long (assume they exist):
def OnBtnSuperTesting(self, event):
class MainWindow(wx.Frame):
def __init__(self, parent, title):
self.dirname=''
wx.Frame.__init__(self, parent, title=title, size=(320,440))
self.SetBackgroundColour(wx.WHITE)
self.CenterOnScreen()
self.CreateStatusBar()
self.radioT89 = wx.RadioButton(self, -1, 'T89 only', pos = (2,0), style = wx.RB_GROUP)
self.radioT87 = wx.RadioButton(self, -1, 'T87 only', pos = (154, 0))
self.radioKeySort = wx.RadioButton(self, -1, 'Sort by Key', pos = (2,40), style = wx.RB_GROUP)
self.radioAtoZ = wx.RadioButton(self, -1, 'Sort Name A-Z', pos = (2,60))
self.radioZtoA = wx.RadioButton(self, -1, 'Sort Name Z-A', pos = (2,80))
self.checkCode = wx.CheckBox(self, -1, 'Generate Code', pos = (154,40))
self.checkBuild = wx.CheckBox(self, -1, 'Generate Build Report', pos = (154, 60))
self.ln = wx.StaticLine(self, -1, pos = (0,15), size = (300,3), style = wx.LI_HORIZONTAL)
self.ln2 = wx.StaticLine(self, -1, pos = (150,15), size = (3,100), style = wx.LI_VERTICAL)
self.radioT87.Bind(wx.EVT_RADIOBUTTON, self.updateList)
#self.Bind(wx.EVT_RADIOBUTTON, self.radioT89, self.updateList())
self.listbox = wx.ListBox(self, -1, pos = (0,120), size = (300,200), choices = T89, style = (wx.LB_SINGLE|wx.LB_HSCROLL))
self.go = wx.Button(self,-1, label = 'Go!', pos = (110, 325))
# Setting up the menu.
filemenu= wx.Menu()
menuAbout= filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")
menuExit = filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program")
# Creating the menubar.
menuBar = wx.MenuBar()
menuBar.Append(filemenu,"&File")
self.SetMenuBar(menuBar)
# Events.
self.Bind(wx.EVT_MENU, self.OnExit, menuExit)
self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
self.SetAutoLayout(1)
self.Show()
def OnExit(self,e):
self.Close(True) # Close the frame.
def updateList(self):
if self.radioT87.GetValue() == True:
choices = T87
self.listbox.Set(choices)
else:
choices = T89
self.listbox.Set(choices)
app = wx.App(False)
frame = MainWindow(None, "Supervisory Testing")
app.MainLoop()
When you create each radiobutton you can create a bind event. What this does (as you have implemented later on in your code) is execute a command function when the bind event occurs. In your case it would look like this:
self.Bind(wx.EVT_RADIOBUTTON,self.RadioButton,self.DoSomething)
Explanation:
wx.EVT_RADIOBUTTON
This is the event that is triggered when the user changes the Radiobutton's status. It may or may not have attributes.
self.RadioButton
This is the radiobutton which you would like to bind. In your case "self.radioAtoZ" or similar.
self.DoSomething
THis is the callback function. You can make it whatever you want such as:
def DoSomething(self):
if self.radioAtoZ.getStatus():
rearrangeNumbersFromAtoZ
print 'Re-arranged numbers from A to Z'
else:
etc.
EDIT:
self.RadioButton.Bind(EVT_RADIOBUTTON, self.DoSomething)
Your structure for self.DoSomething should be like this:
Class MainWindow:
def __init_(self, parent):
def DoSomething(self):
#dostuff
Also in response to your other comment:
when a function is called within a Bind event, it passes the event to the function by default. In addition, all functions have the "self" arg, thus 2 given args. You need to change the following:
def DoSomething(self, event):
#dostuff
I decided to rewrite the OP's code to demonstrate how to bind 2 RadioButton's to 2 different event handlers and update the ListBox:
import wx
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Radios!")
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
self.radioAtoZ = wx.RadioButton(panel, label='Sort Name A-Z',
style = wx.RB_GROUP)
self.radioAtoZ.Bind(wx.EVT_RADIOBUTTON, self.sortAZ)
sizer.Add(self.radioAtoZ, 0, wx.ALL|wx.EXPAND, 5)
self.radioZtoA = wx.RadioButton(panel, label='Sort Name Z-A')
self.radioZtoA.Bind(wx.EVT_RADIOBUTTON, self.sortZA)
sizer.Add(self.radioZtoA, 0, wx.ALL|wx.EXPAND, 5)
choices = ["aardvark", "zebra", "bat", "giraffe"]
self.listbox = wx.ListBox(panel, choices=choices)
sizer.Add(self.listbox, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.Show()
#----------------------------------------------------------------------
def sortAZ(self, event):
""""""
choices = self.listbox.GetStrings()
choices.sort()
self.listbox.SetItems(choices)
#----------------------------------------------------------------------
def sortZA(self, event):
""""""
choices = self.listbox.GetStrings()
choices.sort()
choices.reverse()
self.listbox.SetItems(choices)
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
You will want to take a look at the following wiki article on the differences of binding it this way versus the other:
http://wiki.wxpython.org/self.Bind%20vs.%20self.button.Bind
Most of the time when you create a group of widgets that do different things, you bind them to different event handlers. If you want to bind all the RadioButtons to the same handler, then you'll probably need to name each widget with a unique name so that when they come to the handler, you can tell which button was pressed. Then you can use a series of if statements to decide what to do to the list box. Here's a tutorial that talks about that sort of thing:
http://www.blog.pythonlibrary.org/2011/09/20/wxpython-binding-multiple-widgets-to-the-same-handler/
I am trying to put together a gui in Python for a school project, but I am getting an error and don't really understand why.
self.prompt.addAnswer(i, self.ansControls[i].GetValue()) File
"C:\Python27\lib\site-packages\wx-3.0-msw\wx_core.py", line 16712, in
getattr
raise PyDeadObjectError(self.attrStr % self._name) wx._core.PyDeadObjectError: The C++ part of the TextCtrl object has
been deleted, attribute access no longer allowed.
I understand what the error means, The TextCtrl object no longer exists so I can't access it. I do not understand why the TextCtrl object does not exist anymore. Here is how the flow goes:
The frame appears with labels, TextCtrls, and buttons. The user enters in data and hits next. Everything goes smoothly. A different instance of the same PromptFrame class is then created, and the same thing happens. This time however, when the user hits next, I get the aforementioned error. Here is the code:
The service in the background running the show:
class AppService(object):
prompts = [Prompt_1, Prompt_2, Prompt_3, Prompt_4, Prompt_5, Prompt_6, Prompt_7,
Prompt_8, Prompt_9, Prompt_10, Prompt_11, Prompt_12, Prompt_13, Prompt_14,
Prompt_15, Prompt_16, Prompt_17, Prompt_18, Prompt_19]
skippedPromptIndices = []
def __init__(self):
print "Service Started"
PromptFrame(self, self.prompts[0], 0, len(self.prompts))
def doBack(self, curIndex, curPrompt):
if curIndex >= 0:
self.prompts[curIndex] = curPrompt
PromptFrame(self, self.prompts[curIndex - 1], curIndex - 1, len(self.prompts))
else:
posCurIndex = (curIndex * -1) - 1
self.prompts[posCurIndex] = curPrompt
backIndex = self.skippedPromptIndices.index(curIndex) - 1
nextPromptIndex = 0
if backIndex < 0:
nextPromptIndex = len(self.prompts) - 1
else:
nextPromptIndex = self.skippedPromptIndices[backIndex]
PromptFrame(self, self.prompts[(nextPromptIndex * -1) - 1], nextPromptIndex, len(self.prompts))
def doSkip(self, curIndex, curPrompt):
skipIndex = (curIndex + 1) * -1
if self.skippedPromptIndices.count(skipIndex) > 0:
self.skippedPromptIndices.remove(skipIndex)
self.skippedPromptIndices.append(skipIndex)
self.doNext(curIndex, curPrompt)
def doNext(self, curIndex, curPrompt):
if curIndex >= 0:
self.prompts[curIndex] = curPrompt
else:
self.prompts[(curIndex * -1) - 1] = curPrompt
if (curIndex >= 0 and curIndex < (len(self.prompts) - 1)):
PromptFrame(self, self.prompts[curIndex + 1], curIndex + 1, len(self.prompts))
elif len(self.skippedPromptIndices) > 0:
skipIndex = self.skippedPromptIndices.pop(0)
nextIndex = (skipIndex * -1) - 1
PromptFrame(self, self.prompts[nextIndex], skipIndex, len(self.prompts))
else:
dlg = wx.MessageDialog(self, "Done!", "Message", wx.OK)
dlg.ShowModal() # Shows it
dlg.Destroy() # finally destroy it when finished.
and here is the PromptFrame Class:
class PromptFrame(wx.Frame):
appService = None
prompt = None
promptIndex = 0
numPrompts = 0
ansControls = []
def __init__(self, appService, prompt=testPrompt, promptIndex=0, numPrompts=0):
print "New Prompt Frame!"
self.appService = appService
self.prompt = prompt
self.promptIndex = promptIndex
self.numPrompts = numPrompts
wx.Frame.__init__(self, None, wx.ID_ANY, title=prompt.title)
self.panel = wx.Panel(self, wx.ID_ANY)
self.panel.SetBackgroundColour('#2FC0E0') #0013E3, #66DFFA, #2FC0E0
self.Maximize()
self.CreateStatusBar() # A Statusbar in the bottom of the window
# Setting up the menu.
filemenu= wx.Menu()
menuAbout= filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")
menuExit = filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program")
# Creating the menubar.
menuBar = wx.MenuBar()
menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar
self.SetMenuBar(menuBar) # Adding the MenuBar to the Frame content.
# Creating fonts for the controls
titleFont = wx.Font(24, wx.DECORATIVE, wx.ITALIC, wx.BOLD)
qFont = wx.Font(12, wx.FONTFAMILY_DECORATIVE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
#Creating layout
vertSizer = wx.BoxSizer(wx.VERTICAL)
lblTitle = wx.StaticText(self.panel, wx.ID_ANY, prompt.title)
lblTitle.SetFont(titleFont)
vertSizer.Add(lblTitle, 0, wx.ALIGN_CENTER | wx.TOP, border=30)
vertSizer.Add(wx.StaticLine(self.panel, wx.ID_ANY), 0, wx.EXPAND)
vertSizer.AddStretchSpacer(2)
for i in range(len(prompt.questions)):
if prompt.qTypes[i] == 0:
lbl = wx.StaticText(self.panel, wx.ID_ANY, prompt.questions[i], size=(200, -1))
lbl.SetFont(qFont)
lbl.Wrap(200)
ans = wx.TextCtrl(self.panel, wx.ID_ANY, size=(200,-1))
if not prompt.answers[i] == None:
ans.SetValue(prompt.answers[i])
self.ansControls.append(ans)
horizSizer = wx.BoxSizer(wx.HORIZONTAL)
horizSizer.Add((30, -1), 0)
horizSizer.Add(lbl, 0)
horizSizer.Add((20, -1), 0)
horizSizer.Add(self.ansControls[len(self.ansControls) - 1], 0)
vertSizer.Add(horizSizer, 0)
vertSizer.AddStretchSpacer(1)
print self.ansControls
vertSizer.Add(wx.StaticLine(self.panel, wx.ID_ANY), 0, wx.EXPAND)
self.btnBack = wx.Button(self.panel, wx.ID_ANY, "Back")
self.btnSkip = wx.Button(self.panel, wx.ID_ANY, "Skip")
self.btnNext = wx.Button(self.panel, wx.ID_ANY, "Next")
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer.Add(self.btnBack, 0, wx.RIGHT, border=30)
btnSizer.Add(self.btnSkip, 0)
btnSizer.Add(self.btnNext, 0, wx.LEFT, border=30)
btnSizer.AddStretchSpacer(1)
vertSizer.AddStretchSpacer(2)
vertSizer.Add(btnSizer, 0, wx.ALIGN_CENTER)
vertSizer.AddStretchSpacer(2)
lblPage = wx.StaticText(self.panel, wx.ID_ANY, "Page: " + str(self.promptIndex) + "/" + str(self.numPrompts))
vertSizer.Add(lblPage, 0, wx.ALIGN_BOTTOM | wx.ALIGN_RIGHT | wx.ALL, border=20)
self.panel.SetSizer(vertSizer)
# Events.
self.Bind(wx.EVT_MENU, self.OnExit, menuExit)
self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
self.Bind(wx.EVT_BUTTON, self.OnBack, self.btnBack)
self.Bind(wx.EVT_BUTTON, self.OnSkip, self.btnSkip)
self.Bind(wx.EVT_BUTTON, self.OnNext, self.btnNext)
self.Show()
def OnAbout(self,e):
# Create a message dialog box
aboutString = "This program was designed by students of Worcester Polytechnic Institute to aid water supply " \
"officials apply the World Health Organization's Water Safety Plans."
dlg = wx.MessageDialog(self, aboutString, "About Water Safety Plan Helper", wx.OK)
dlg.ShowModal() # Shows it
dlg.Destroy() # finally destroy it when finished.
def OnExit(self,e):
self.Close(True) # Close the frame.
def OnBack(self, e):
if (self.promptIndex > 0):
self.SaveAns()
self.appService.doBack(self.promptIndex, self.prompt)
self.Close(True)
else:
errorString = "There are no previous pages to go back to"
dlg = wx.MessageDialog(self, errorString, "Error", wx.OK)
dlg.ShowModal() # Shows it
dlg.Destroy() # finally destroy it when finished.
def OnSkip(self, e):
self.SaveAns()
self.appService.doSkip(self.promptIndex, self.prompt)
self.Close(True)
def OnNext(self, e):
self.SaveAns()
self.appService.doNext(self.promptIndex, self.prompt)
self.Close(True)
def SaveAns(self):
print self.ansControls
for i in range(len(self.ansControls)):
if self.prompt.qTypes[i] == 0:
self.prompt.addAnswer(i, self.ansControls[i].GetValue())
Thanks for your help guys!
EDIT: Here is my init.py file:
from MainFrame import MainFrame
import wx
app = wx.App(False)
frame = MainFrame(None, "My App")
app.MainLoop()
ansControls is currently defined as a class variable. Which means that any control defined in any window gets added to it.
You create a control in the first instance, it is added to the class, but the window belongs to the instance. So when you destroy the class, the instance gets destroyed, but the python object pointing to it still exists.
Then you open your second window, add more controls, and then hit the loop where you loop over them. The first ones in your loop will not have a valid C++ object below it anymore, and will fail.
Not sure why they were defined as class variables, but you either need to keep a pointer to the root window as well, delete class controls as the parent windows get deleted, or (simpler) just make ansControls an instance variable instead of a class variable...
does anyone of you have an example how to make the following possible:
I have a listctrl that displays > 600 items. Now I need to search in these items for a text the user inputs and update the list to only show the items containing this string.
So let us say the list contains "Hello", "Hi" and "Morning". The list displays all three items. Now the user types "h" into the textctrl, and the listctrl is narrowed down to "Hello" and "Hi". If the user instead types "o", and the list becomes "Hello" and "Morning".
Is this possible? Or is there any other convenient way to find an item in a listctrl? The build in "find as you type" is only of real use if you do exactly know what you search for - and in my case this will not really be the case...
Thanks, Woodpicker
The wxPython demo has a pretty good "type-ahead" filter built into it. Looking at the source code to Main.py they do it the "manual way", loop and rebuild the list. They are using a treeview but the ideas are sound:
def OnSearch(self, event=None):
value = self.filter.GetValue()
if not value:
self.RecreateTree()
return
wx.BeginBusyCursor()
for category, items in _treeList:
self.searchItems[category] = []
for childItem in items:
if SearchDemo(childItem, value):
self.searchItems[category].append(childItem)
wx.EndBusyCursor()
self.RecreateTree()
I like the ObjectListView wrapper better than the straight wx.ListCtrl. It includes the ability to filter items in the control as a feature of the widget. You can read about it here: http://objectlistview.sourceforge.net/python/features.html#filtering and here's the main page for the control: http://objectlistview.sourceforge.net/python/
Here is an example of filtering an UltimateListCtrl.
I know this is 2 years later but I've found other examples on stackoverflow really, really helpful.
I'm new to python/wxpython but hopefully it will be useful.
starting from http://www.blog.pythonlibrary.org/2011/11/02/wxpython-an-intro-to-the-ultimatelistctrl/
import wx
from wx.lib.agw import ultimatelistctrl as ULC
class ULC_Panel(wx.Panel):
""""""
def __init__(self, parent, col_headers=None, list_data=None, options=None, dlg=None,
selected_list=None):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.options = options
self.show_only_selected = False
self.filter_string = ""
hsizer = wx.BoxSizer(wx.HORIZONTAL)
okayButton = wx.Button(self, wx.ID_OK, "OK")
okayButton.SetToolTip(wx.ToolTip("Click to close this dialog and use the selections"))
self.Bind(wx.EVT_BUTTON, self.OnOkayCanButton, okayButton)
hsizer.Add(okayButton, 0, wx.ALL, 5)
canButton = wx.Button(self, wx.ID_CANCEL, "Cancel")
canButton.SetToolTip(wx.ToolTip("Click to close this dialog and cancel selections"))
self.Bind(wx.EVT_BUTTON, self.OnOkayCanButton, canButton)
hsizer.Add(canButton, 0, wx.ALL, 5)
cb_show_only = wx.CheckBox(self, -1, "Show only selected items?")
cb_show_only.SetValue(self.show_only_selected)
cb_show_only.SetToolTip(wx.ToolTip("Click to show only selected rows"))
self.Bind(wx.EVT_CHECKBOX, self.EvtShowOnly, cb_show_only)
hsizer.Add(cb_show_only, 0, wx.ALL, 5)
self.stext = wx.StaticText(self, -1, "Filter: ", style=wx.ALIGN_LEFT)
self.filtr = wx.TextCtrl(self, -1, "", style=wx.ALIGN_LEFT)
self.Bind(wx.EVT_TEXT, self.OnFiltr, self.filtr)
fsizer = wx.BoxSizer(wx.HORIZONTAL)
fsizer.Add(self.stext, 0, wx.ALL)
fsizer.Add(self.filtr, 1, wx.EXPAND)
font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
boldfont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
boldfont.SetWeight(wx.BOLD)
boldfont.SetPointSize(12)
self.ultimateList = ULC.UltimateListCtrl(self, agwStyle = wx.LC_REPORT
| wx.LC_VRULES | ULC.ULC_HAS_VARIABLE_ROW_HEIGHT
| wx.LC_HRULES)
self.checkbox = [None] * len(list_data)
if selected_list != None:
self.selected = selected_list
else:
self.selected = [False] * len(list_data)
self.rows_max = len(list_data)
self.rows_current = -1
self.cols_max = len(col_headers)
self.cols_extra = 1
if options & ULC.ULC_MASK_CHECK:
self.cols_extra += 1
for i in xrange(self.cols_max+self.cols_extra):
info = ULC.UltimateListItem()
info._mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT
info._image = []
info._format = 0
info._kind = 1
width = 150
if i >= self.cols_extra:
info._text = col_headers[i-self.cols_extra]
elif i == 0:
info._text = "Row"
width = 35
elif i == 1 and options & ULC.ULC_MASK_CHECK:
info._text = "Select"
width = 50
self.ultimateList.InsertColumnInfo(i, info)
self.ultimateList.SetColumnWidth(i, width)
self.list_data = list_data
pos = self.populate_table("")
if pos != None:
self.sz = self.ultimateList.GetItemRect(pos)
self.width = self.sz[2] + self.sz[3]
self.height = self.sz[1]
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(hsizer, 0, wx.EXPAND)
sizer.Add(fsizer, 0, wx.EXPAND)
sizer.Add(self.ultimateList, 1, flag=wx.EXPAND)
self.SetSizer(sizer)
sizer.Fit(self)
def EvtShowOnly(self, event):
cb = event.GetEventObject()
val = cb.GetValue()
self.show_only_selected = val
pos = self.populate_table(self.filter_string)
print "show_only_selected val= ", val
def EvtCheckBox(self, event):
cb = event.GetEventObject()
id = event.GetId()
val = cb.GetValue()
self.selected[id] = val
print "id, val= ", id, val
def OnOkayCanButton(self, event):
id = event.GetId()
dlg.EndModal(id)
def myGetNeedWH(self):
return (self.width, self.height)
def myGetSelectedState(self):
return self.selected
def populate_table(self, str_in):
busy = wx.BusyCursor()
if str_in:
str_low = str_in.lower()
if self.options & ULC.ULC_MASK_CHECK:
# if we have widgets in the row then we have to delete 1 row
# at a time (or else it leaves some of the widgets)
i = self.rows_current
#print "i, self.rows_max= ", i, self.rows_max
while i >= 0:
#print "i= ", i
self.ultimateList.DeleteItem(i)
i -= 1
else:
self.ultimateList.DeleteAllItems()
row = -1
for i in xrange(len(self.list_data)):
tlwr = self.list_data[i][0].lower()
if not str_in or tlwr.find(str_low) >= 0:
if self.show_only_selected and self.selected[i] == False:
continue
row += 1
for j in xrange(self.cols_max+self.cols_extra):
if j == 0:
pos = self.ultimateList.InsertStringItem(row, str(row))
elif j == 1 and self.options & ULC.ULC_MASK_CHECK:
self.checkbox[i] = wx.CheckBox(self.ultimateList, id= i)
self.checkbox[i].SetValue(self.selected[i])
self.checkbox[i].SetToolTip(wx.ToolTip("Click to select this row"))
self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, self.checkbox[i])
self.ultimateList.SetItemWindow(pos, col=1, wnd=self.checkbox[i], expand=False)
else:
self.ultimateList.SetStringItem(row, j, self.list_data[i][j-self.cols_extra])
self.rows_current = row
return row
def OnFiltr(self, event):
str1 = event.GetString()
id = event.GetId()
#print "got txt_tval str[%s]= %s" % (id, str1)
self.filter_string = str1
pos = self.populate_table(str1)
event.Skip()
return
########################################################################
class FilterListDiag(wx.Dialog):
def __init__(self, parent, id, title, headers=None, data_table=None, options=None, selected_list=None):
wx.Dialog.__init__(self, parent, id, title="", style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
options_in = options
self.panel = ULC_Panel(self, col_headers=headers, list_data=data_table, options=options_in,
dlg=self, selected_list=selected_list)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.panel, 1, wx.EXPAND)
self.SetSizer(sizer)
(self.width, self.height) = self.panel.myGetNeedWH()
def myGetNeedWH(self):
return (self.width, self.height)
def myGetSelectedState(self):
return self.panel.myGetSelectedState()
class TestFrame(wx.Frame):
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="MvP UltimateListCtrl Demo", size=(850,600))
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = TestFrame()
col_headers = ['col0', 'col1', 'col2']
list_data = [
["Newsboys", "Go", "Rock"],
["Puffy", "Bring It!", "Pop"],
["Family Force 5", "III", "Pop"],
["Me2", "III", "Pop"],
["Duffy", "III", "Pop"],
["Fluffy", "III", "Pop"],
]
# sel_data passes in a list of which rows are already selected
sel_data = [
False,
False,
False,
False,
True,
False,
]
opt=ULC.ULC_MASK_CHECK # just reusing this to indicate I want a column of checkboxes.
dlg = FilterListDiag(frame, -1, "hi", headers=col_headers, data_table=list_data, options=opt, selected_list=sel_data)
(w, h) = dlg.myGetNeedWH()
print w,h
dlg.SetSizeWH(w, 300)
val = dlg.ShowModal()
selected = dlg.myGetSelectedState()
print "okay, can, val= ", wx.ID_OK, wx.ID_CANCEL, val
dlg.Destroy()
print 'selected=', selected
I have something different.
I created a dictionary and updated every time there is an entry in the list ctrl. Now, when I have a search box, which changes the list ctrl with each input from the keyboard and I clear and re-update my dictionary. This way, I always have the latest index for the values.
Code is as below:
class ViewUserDialog(wx.Dialog):
def __init__(self):
title = 'View Users Records'
super().__init__(parent=None, size=(750, 600), title=title)
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
self.list_sizer = wx.BoxSizer(wx.VERTICAL)
self.row_obj_dict = {}
.
.
.
self.list_ctrl_View_User.InsertColumn(0, "ID", width=150)
self.list_ctrl_View_User.InsertColumn(1, "User Name", width=150)
for index, users in original_user_frame.iterrows():
self.list_ctrl_View_User.InsertItem(indexes, user_id_value)
self.list_ctrl_View_User.SetItem(indexes, 1, users.NAME)
user_objects.append(users)
self.row_obj_dict[indexes] = users
indexes += 1
Now on searching, and selecting an item in lstctrl, you will still get your results:
def on_view(self, event):
selection = self.list_ctrl_View_User.GetFocusedItem()
self.list_ctrl_View_User.ClearAll()
if selection >= 0:
user = self.row_obj_dict[selection]
print(user)
self.list_ctrl_View_User.InsertColumn(0, "ID", width=150)
self.list_ctrl_View_User.InsertColumn(1, "User Name", width=150)
for index, users in original_user_frame.iterrows():
self.list_ctrl_View_User.InsertItem(indexes, user_id_value)
self.list_ctrl_View_User.SetItem(indexes, 1, users.NAME)
This will always give the currect selected item from the lstctrl and update the list with every input.