Building Controls
Creating new wxPython windows is a simple task. You override a few methods, maybe catch a few events and your class does what you want it to do. You can distribute this new "control", people can use it, life is good. What this document attempts to describe is how to go beyond the basics of a functional window into creating a reusable control.
It should be noted that this document is describing "best practice" as understood by the author, rather than current practice as exemplified by the wxWindows developers. Certain of the built in controls provide counterexamples to the advice here.
Controls normally have the following properties:
- Common API -- generally allow for all the options you associate with wxWindow classes, including standard method signatures. Event generators -- generating comprehensive change notices, often with their own associated event classes. Parent windows can use event mappings to accomplish most interactions with the control. Programmatic API -- allowing for direct manipulation and retrieval of control value, adding/removing items, and altering the functionality of the control. Use styles -- manipulation of general class behavior is controlled through the application of style bitmasks in the style argument to the class constructor. Allow for validators -- although not easy to do for custom-built controls, validators are part of the general interface of controls. Virtual methods/OnX methods -- provide customisation points for commonly subclassed behavior.
Most controls follow one or more of these patterns:
Value editors -- include a "value" argument, GetValue and SetValue methods, and value-changed events (wxTextCtrl, wxCheckBox, wxCalendarCtrl, wxChoice, wxListBox, wxSpinCtrl, wxComboBox) Event generators -- largely unconcerned with value editing, generally provide a source for events through interactions with the user (wxButton, wxScrollBar, wxSlider, wxSpinButton) Visualisers -- largely unconcerned with user interactions, take a value (often continuously updating) and present that value visually (wxGauge, wxStaticBitmap, wxStaticText) List views -- provide customisation points for interacting with collections of objects/choices, providing the ability to associate arbitrary objects with internal structures (wxListCtrl, wxTreeCtrl, wxChoice, wxComboBox)
Creating a Control from a Window
For our working example, let's take a very simple control candidate, a simple floating point value editor control. This will be a simple subclassing of the wxTextControl to provide conversion into and from float values. Here is the "window level" class.
from wxPython.wx import * import types class FloatControl (wxTextCtrl): def __init__ (self, parent, value= 0.0): wxTextCtrl.__init__( self, parent,-1, self.toGUI(value)) def get(self): return self.fromGUI( self.GetValue()) def set(self, value): self.SetValue(self.toGUI(value)) def toGUI( self, value ): if not type(value) in ( types.FloatType, types.IntType, types.LongType): raise ValueError ('''FloatControl requires float values, passed %s'''%repr(value)) return str(value) def fromGUI( self, value ): return string.atof( value )
As you can see, this is a fairly simple implementation. It allows for basic getting and setting of the float value, with the editing by the user. Within a particular application, you can use this control to provide editing of a particular float value and be quite happy with its operation.
Common API
Unfortunately, is not a particularly reusable control. It does not follow the common API conventions for wxPython controls (and particularly, value editing controls).
Initializers in wxPython controls, generally provide a fairly complete signature of this basic pattern:
def __init__ ( self, parent, id=-1, [value = 0.0,] pos = wxDefaultPosition, size = wxDefaultSize, [choices=[]], style = 0, validator= wxDefaultValidator, name = "float", ):
Where the [] brackets indicate optional elements in the control's argument list. The names of the "value" and "choices" arguments are subject to variation, as are (potentially) their position within the argument list. When building new controls for use by other developers, is generally a good idea to follow this pattern (including the order of arguments), so that those developers can guess that the appropriate position and names for the values to be passed.
Similarly, value editing controls generally have two methods, "GetValue" and "SetValue" which are used to get the current value of the control. As you can see above, we're actually using these methods to get the current value of the text control in our "get" and "set" methods. A user of the control, however, would expect that the GetValue method would return the current float value, and would not expect the ValueError that would be raised were they to use the SetValue method with a float value. To make the control match the value editing control pattern, we override the expected method names:
def GetValue(self): return self.fromGUI( wxTextCtrl.GetValue(self)) def SetValue(self, value): wxTextCtrl.SetValue(self, self.toGUI(value))