Introduction

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

Process Overview

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. :)

Special Concerns

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.

Code Sample

   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

Comments

Please place any questions or comments here. I've subscribed to the page, so I should see any changes.

TypeSelectListBox (last edited 2008-03-11 10:50:39 by localhost)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.