wxPython: ListBox not selectable/clickable when used with ComboCtrl - python

I am trying to couple wx.ListBox with wx.combo.Comboctrl. A sample code is below. For some reason, the items in the ListBox are not clickable/selectable. I wonder how I can make it work. Thanks!
EDIT: Missing code added
import wx, wx.combo
class MainFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title="", size=(300, 100))
gbs = wx.GridBagSizer()
ComboCtrlBox = wx.combo.ComboCtrl(self)
ComboCtrlPopup = ListBoxComboPopup()
ComboCtrlBox.SetPopupControl(ComboCtrlPopup)
ComboCtrlPopup.ListBox.Append("Apple")
ComboCtrlPopup.ListBox.Append("Banana")
ComboCtrlPopup.ListBox.Append("Orange")
ComboCtrlPopup.ListBox.Bind(wx.EVT_LISTBOX, self.OnSelect) #ADDED
gbs.Add(ComboCtrlBox, pos = (0, 0), span = (1, 1), flag = wx.EXPAND|wx.ALL, border = 10)
gbs.AddGrowableCol(0)
self.SetSizer(gbs)
self.Layout()
def OnSelect(self, evt): #ADDED
print "HAHA"
class ListBoxComboPopup(wx.combo.ComboPopup):
def Init(self):
self.ItemList = []
def Create(self, parent):
self.ListBox = wx.ListBox(parent, -1, size = (-1, 20), choices = self.ItemList)
def GetControl(self):
return self.ListBox
def OnPopup(self):
pass
#-----------------------------------------------------------------------------#
if __name__ == '__main__':
APP = wx.App(False)
FRAME = MainFrame(None)
FRAME.Show()
APP.MainLoop()

You are missing a few things in your ListBoxComboPopup class needed to make it work well with the ComboCtrl. At a minimum you are missing some event binding and handler to catch selection events from the ListBox, and an implementation of the GetStringValue method which the combo will call to get the value to be displayed. Please see the ComboCtrl sample in the wxPython demo for more details and example code.

Related

WxPython using Listbox and other UserInput with a Button

I am trying to create a web crawler based on specific user input. For example, the User Input I am trying to receive is from a ListBox and a text field. Once I have that information, I would like the user to click a button to start the search with the information collected.
This is where I have been getting problems. The EVT function doesn't recognize the listbox since its been linked to the Button evt. Is there a way to solve the problem? Can EVT information be shared with other EVTs?
Here is what I have so far:
import wx # for gui
class MyFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Title', size=(300,200))
tournLevel = ['$10,000', '$15,000', '$20,000', '$50,000','$75,000','$100,000']
levelBox = wx.ListBox(panel, -1, (40, 50), (90, 90), tournLevel)
levelBox.SetSelection(1) # default selection
checkButton = wx.Button(panel, label= "Check Now", pos = (150, 50), size = (90, 40))
self.Bind(wx.EVT_BUTTON, self.OnClick, checkButton)
def OnClick(self, event):
currLevel = event.GetSelection()
print(currLevel) # to test if GetSelection is working
if __name__ == '__main__':
app = wx.App()
frame = MyFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
I would be very happy if I could just get the button to recognize the ListBox results.
Thank you for your time!
You can just grab it from the listbox, you don't need it from the event. See below:
import wx # for gui
class MyFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Title', size=(300,200))
tournLevel = ['$10,000', '$15,000', '$20,000', '$50,000','$75,000','$100,000']
self.levelBox = wx.ListBox(panel, -1, (40, 50), (90, 90), tournLevel)
self.levelBox.SetSelection(1) # default selection
self.checkButton = wx.Button(panel, label= "Check Now", pos = (150, 50), size = (90, 40))
self.Bind(wx.EVT_BUTTON, self.OnClick, self.checkButton)
def OnClick(self, event):
currLevel = self.levelBox.GetSelection()
print(currLevel) # to test if GetSelection is working
if __name__ == '__main__':
app = wx.App()
frame = MyFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
More specifically, if you store levelBox as self.levelBox, it will be accessible inside the OnClick method as a MyFrame attribute. You can then use the GetSelection method for this object (not the event), which will get the current selection.
You can make levelBox into a property of the class by turning it into self.levelBox and accessing it that way as #brettb mentioned. However you can get a bit sneakier and do it using a lambda for your callback to pass the Listbox widget to the event handler:
import wx # for gui
class MyFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Title', size=(300,200))
panel = wx.Panel(self)
tournLevel = ['$10,000', '$15,000', '$20,000', '$50,000','$75,000','$100,000']
levelBox = wx.ListBox(panel, -1, (40, 50), (90, 90), tournLevel)
levelBox.SetSelection(1) # default selection
checkButton = wx.Button(panel, label= "Check Now", pos = (150, 50), size = (90, 40))
evt = lambda caller, widget=levelBox: self.OnClick(caller, widget)
checkButton.Bind(wx.EVT_BUTTON, evt)
def OnClick(self, event, widget):
currLevel = widget.GetSelection()
print(currLevel) # to test if GetSelection is working
print widget.GetString(currLevel)
if __name__ == '__main__':
app = wx.App()
frame = MyFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
Also note that you didn't have panel defined, so your original code doesn't work. See the following for more information:
http://wiki.wxpython.org/Passing%20Arguments%20to%20Callbacks

wxPython checkbox true or false

So let's say that I have a checkbox in wxPython:
cb1 = wx.CheckBox(panelWX, label='TIME', pos=(20, 20))
cb1.SetValue(False)
Is there a simple way I could check to see whether it has changed to true?
Like this maybe?
if cb1.SetValue == True:
And from that point append something from the action of it being true?
Like so:
selectionSEM1.append('Time')
you just need to use GetValue() method. look at this example from wxpython wiki:
#!/usr/bin/python
# checkbox.py
import wx
class MyCheckBox(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size=(250, 170))
panel = wx.Panel(self, -1)
self.cb = wx.CheckBox(panel, -1, 'Show Title', (10, 10))
self.cb.SetValue(True)
wx.EVT_CHECKBOX(self, self.cb.GetId(), self.ShowTitle)
self.Show()
self.Centre()
def ShowTitle(self, event):
if self.cb.GetValue():#here you check if it is true or not
self.SetTitle('checkbox.py')
else: self.SetTitle('')
app = wx.App(0)
MyCheckBox(None, -1, 'checkbox.py')
app.MainLoop()
You want to use Events.
def do_something(event):
box = event.GetEventObject()
setting = box.GetValue()
if setting:
selectionSEM1.append('Time')
event.Skip()
cb1.Bind(wx.EVT_CHECKBOX, do_something, cb1)

How to link multiple wx.Dialogs in wxPython

I want to make a game in wxPython (no other modules) and I want to make it so that you can enter some values in popup screens before the game starts, and then the game will be drawn on a canvas which in turn is drawn on a panel, which is bound to the main game.
I made the gamescreen with all fancy stuff (works solo)
I made the input screens
But I cannot link them.
How do I start the game so it will open a dialog box, then on the closure of it open another one, and then open the game ?
I tried the following, but it will not open my canvas:
# makes a game by showing 2 dialogs
# after dialogs have been answered, starts the game by drawing the canvas.
# imports
import wx
import Speelveld3
# globals
SCRWIDTH = 950
SCRHEIGHT = 700
# dialogbox class
class MyDialog1(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent)
self.username = wx.TextCtrl(self)
self.okButton = wx.Button(self, wx.ID_OK, "OK")
class MyDialog2(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent)
self.canvasWidth = wx.TextCtrl(self)
self.okButton = wx.Button(self, wx.ID_OK, "OK")
# main class
class Game(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, title='My game', size=(SCRWIDTH, SCRHEIGHT))
self.username = ""
self.canvasWidth = 10
# hide the frame for now
self.Hide()
def OnInit(self):
#Make your dialogs
dlg1 = MyDialog1(self)
#if the user pressed "OK" (i.e. NOT "Cancel" or any other button you might add)
if dlg1.ShowModal() == wx.ID_OK:
#get the username from the dialog
self.username = dlg1.username.GetValue()
#clean up the dialog (AFTER you get the username)
dlg1.Destroy()
dlg2 = MyDialog2(self)
#if the user pressed "OK" (i.e. NOT "Cancel" or any other button you might add)
if dlg2.ShowModal() == wx.ID_OK:
#get the username from the dialog
self.canvasWidth = dlg2.canvasWidth.GetValue()
#clean up the dialog (AFTER you get the username)
dlg2.Destroy()
# Now that you have your settings, Make the gameboard
# THIS PART IS STILL BROKEN!
# I can paste the whole board class (structure of it is taken from the tetris tutorial)
# but that seems a bit much tbh...
self.gameBoard = Board.Board(self)
self.gameBoard = SetFocus()
self.gameBoard.start()
self.Centre()
self.Show(True) #show the frame
if __name__ == '__main__':
# how can I start the game here?
app = wx.App()
frame = Game()
board = Speelveld3.Speelveld(frame)
board.start()
frame.Show()
app.MainLoop()
You've double posted, and the lack of any wx.Dialog in your sample code suggests to me that you haven't even looked at a tutorial yet, but I will give you the benefit of the doubt.
First, if you want to return information from a dialog, the easiest way is to define a custom dialog. Define a new class that inherits from wx.Dialog and then set it up just like you would a normal panel or a frame. It seems to me that you will need two of these. They'll look something like this:
class MyDialog1(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent)
self.username = wx.TextCtrl(self) #this is where users will enter their username
self.okButton = wx.Button(self, wx.ID_OK, "OK") #Note that I'm using wx.ID_OK. This is important
Now, for the logic you want. Pretty much every object in wxPython that you actually see has the functions Show() and Hide() (API here). You don't want to show your frame until AFTER the dialogs are finished, so in your __init__(), call Hide(). I'm also initializing a variable, username, which is where I will store the data from my dialog.
class Game(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size=(SCRWIDTH, SCRHEIGHT))
self.username = ""
self.Hide() #don't show the frame just yet
#self.Hide() is the exact same as self.Show(False)
Now, for your dialogs. Like Mike Driscoll suggested, you call your dialogs BEFORE making your canvas. wx.Dialogs are launched using ShowModal(). By setting the ID of self.okButton to the constant wx.ID_OK, wxPython recognizes that the dialog should be closed after the button in clicked. You should also be aware of wx.ID_CANCEL.
def OnInit(self):
#Make your dialogs
dlg1 = MyDialog1(self)
if dlg1.ShowModal() == wx.ID_OK:
#if the user pressed "OK" (i.e. NOT "Cancel" or any other button you might add)
self.username = dlg1.username.GetValue() #get the username from the dialog
dlg1.Destroy() #clean up the dialog (AFTER you get the username)
#do this again for your second dialog
#Now that you have your settings, Make the gameboard
self.gameBoard = Board.Board(self)
self.gameBoard = SetFocus()
self.gameBoard.start()
self.Centre()
self.Show(True) #show the frame
In your OnInit you just need to call your dialogs and show them modally BEFORE you create your Board instance. Then it should work correctly.
EDIT (6-28-12): Here's some code:
import wx
########################################################################
class MyDlg(wx.Dialog):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Dialog.__init__(self, None, title="I'm a dialog!")
lbl = wx.StaticText(self, label="Hi from the panel's init!")
btn = wx.Button(self, id=wx.ID_OK, label="Close me")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(lbl, 0, wx.ALL, 5)
sizer.Add(btn, 0, wx.ALL, 5)
self.SetSizer(sizer)
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
# show a custom dialog
dlg = MyDlg()
dlg.ShowModal()
dlg.Destroy()
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, evt):
pdc = wx.PaintDC(self)
try:
dc = wx.GCDC(pdc)
except:
dc = pdc
rect = wx.Rect(0,0, 100, 100)
for RGB, pos in [((178, 34, 34), ( 50, 90)),
(( 35, 142, 35), (110, 150)),
(( 0, 0, 139), (170, 90))
]:
r, g, b = RGB
penclr = wx.Colour(r, g, b, wx.ALPHA_OPAQUE)
brushclr = wx.Colour(r, g, b, 128) # half transparent
dc.SetPen(wx.Pen(penclr))
dc.SetBrush(wx.Brush(brushclr))
rect.SetPosition(pos)
dc.DrawRoundedRectangleRect(rect, 8)
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Example frame")
# show a MessageDialog
style = wx.OK|wx.ICON_INFORMATION
dlg = wx.MessageDialog(parent=None,
message="Hello from the frame's init",
caption="Information", style=style)
dlg.ShowModal()
dlg.Destroy()
# create panel
panel = MyPanel(self)
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
frame.Show()
app.MainLoop()

allocating more size in sizer to wx.CollapsiblePane when expanded

I have several CollapsiblePanes in a vertical BoxSizer. I would like to be able to expand and collapse them without having them run into each other. I am running wxPython 2.8.10.1 on Windows 7.
Runnable sample application demonstrating the problem is below.
import wx
class SampleCollapsiblePane(wx.CollapsiblePane):
def __init__(self, *args, **kwargs):
wx.CollapsiblePane.__init__(self,*args,**kwargs)
sizer = wx.BoxSizer(wx.VERTICAL)
for x in range(5):
sizer.Add(wx.Button(self.GetPane(), label = str(x)))
self.GetPane().SetSizer(sizer)
class Main_Frame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.main_panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
for x in range(5):
sizer.Add(SampleCollapsiblePane(self.main_panel, label = str(x)), 1)
self.main_panel.SetSizer(sizer)
class SampleApp(wx.App):
def OnInit(self):
frame = Main_Frame(None, title = "Sample App")
frame.Show(True)
frame.Centre()
return True
def main():
app = SampleApp(0)
app.MainLoop()
if __name__ == "__main__":
main()
The documentation explicitly states that you should use proportion=0 when adding collapsible panes to a sizer.
http://docs.wxwidgets.org/stable/wx_wxcollapsiblepane.html
So, first, change the 1 at the end of this line to a 0:
sizer.Add(SampleCollapsiblePane(self.main_panel, label = str(x)), 1)
Next, add this to your SampleCollapsiblePane to force the parent frame to re-layout when a pane is collapsed or expanded:
def __init__(...):
...
self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.on_change)
def on_change(self, event):
self.GetParent().Layout()
There might be a better way, but this is what I've got working at the moment. I'm good with wxPython but haven't used CollapsiblePanes before.

Using the same handler for multiple wx.TextCtrls?

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.

Categories