This recipe extends the MozillaStyleListBox recipe.
wx.ListBox provides a truly annoying default type-selection behavior: Whatever key you type, it selects the first item that starts with that key. So if, for example, you have a large number of items beginning with w (such as in a list of wxPython constants!), you still have to use the arrow keys (a lot!) or the mouse.
This recipe offers a wx.ListBox subclass that will jump directly to the item that starts with the phrase you type in (as opposed to selecting the next item that starts with the character you just typed). It will reset the phrase (to allow the type-selection to begin again from the start) after a definable interval of inactivity.
What Objects are Involved
When the user presses any printable key, that key is appended to a selection prefix. The TypeSelectListBox then deselects any items already selected and goes to the first item that begins with the selection phrase. The process is case insensitive.
The selection prefix is automatically cleared after a configurable interval, to allow the user to pause, then type a new prefix. This behavior is very common in GUI applications. This timeout behavior can be suppressed by passing timeout=0 to the constructor.
The user may type Backspace to remove the trailing character from the prefix, or Delete to clear the prefix and start over.
The selection prefix reset timer is restarted when the user types any character, so the user has the specified interval to locate and press the next key. The timer is restarted even if the user types a non-printable character other than backspace or delete, to allow the user more time to find the key he or she meant to hit.
If you plan to use any method other than Set to add items to the wx.ListBox (such as Append), you need to provide a method for it that sets self.choices to the items that make up the wx.ListBox.
1 import wx 2 from string import printable as _printableChars 3 4 class TypeSelectListBox( wx.ListBox ): 5 def __init__( self, parent, id, pos=wx.DefaultPosition, 6 size=wx.DefaultSize, choices=, timeout=1.6, style=0, 7 validator=wx.DefaultValidator, name="listBox" ): 8 """ 9 @param timeout: Type-selection timeout in seconds. 10 To suppress timeout behavior, pass timeout=0. 11 Default value of 1.6 seconds is empirical. 12 """ 13 14 super(TypeSelectListBox,self).__init__( parent, id, 15 pos, size, choices, style, validator, name ) 16 17 self._choices = choices 18 self._prefix = '' 19 self._timeoutInterval = timeout 20 if self._timeoutInterval: 21 # Convert timeout interval to milliseconds 22 self._timeoutInterval = int( 1000 * self._timeoutInterval ) 23 # Timeout was specified, so initialize my timer. 24 self._timerId = wx.NewId() 25 self._timer = wx.Timer( self, self._timerId ) 26 # Process timeouts 27 self.Bind( wx.EVT_TIMER, self._onTimer ) 28 29 # Intercept character key events 30 self.Bind( wx.EVT_CHAR, self._onEvtChar ) 31 32 def _onTimer( self, evt ): 33 """ 34 Clear prefix, but don't clear current selection. 35 When user resumes typing, we'll start with a new prefix. 36 37 Do not restart timer. It will be restarted when (if) user 38 resumes typing. 39 """ 40 self._prefix = '' 41 42 def _startTimer( self ): 43 # (Re)start the timeout timer 44 if self._timeoutInterval: 45 self._timer.Start( self._timeoutInterval, wx.TIMER_ONE_SHOT ) 46 47 def _onEvtChar( self, evt ): 48 # Suspend timer 49 self._timer.Stop() 50 51 keycode = evt.GetKeyCode() 52 if keycode == wx.WXK_BACK: 53 # Backspace removes last letter 54 self._prefix = self._prefix[:-1] 55 elif keycode == wx.WXK_DELETE: 56 # Delete erases prefix 57 self._prefix = '' 58 elif keycode>127 or chr(keycode) not in _printableChars: 59 # Do the default action for nonascii keycodes 60 evt.Skip() 61 # Restart the timer 62 self._startTimer() 63 return 64 else: 65 # It's a printable character 66 self._prefix += chr(keycode).lower() 67 68 # De-select currently-selected item 69 for item in self.GetSelections(): 70 self.Deselect(item) 71 72 if self._prefix != '': 73 # Locate first item with this prefix 74 for choice in self._choices: 75 if choice.lower().startswith( self._prefix ): 76 self.SetStringSelection( choice ) 77 break 78 # Restart the timer 79 self._startTimer() 80 81 def Set( self, choices ): 82 self._choices = choices 83 self._prefix = '' 84 wx.ListBox.Set(self, choices) 85 return 0
Please place any questions or comments here. I've subscribed to the page, so I should see any changes.