ComboBox with autocomplete wxPython - python

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()

Related

wxPython: ListBox not selectable/clickable when used with ComboCtrl

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.

Continuously check for radio button status [WxPython]

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/

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()

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