wxPython: Design Approaches and Techniques

Exclusive offer: get 50% off this eBook here
wxPython 2.8 Application Development Cookbook

wxPython 2.8 Application Development Cookbook — Save 50%

Over 80 practical recipes for developing feature-rich applications using wxPython

$23.99    $12.00
by Cody Precord | December 2010 | Open Source

In today's world of desktop applications, there is a great amount of incentive to be able to develop applications that can run in more than one environment. Currently, there are a handful of options available for cross-platform frameworks to develop desktop applications in Python. wxPython is one such cross-platform GUI toolkit for the Python programming language. It allows Python programmers to create programs with a complete, highly-functional graphical user interface, simply and easily.

In this article by Cody Precord, author of the book wxPython 2.8 Application Development Cookbook, we will cover:

  • Creating Singletons
  • Implementing an observer pattern
  • Strategy pattern
  • Model View Controller
  • Using mixin classes
  • Using decorators

 

wxPython 2.8 Application Development Cookbook

wxPython 2.8 Application Development Cookbook

Over 80 practical recipes for developing feature-rich applications using wxPython

  • Develop flexible applications in wxPython.
  • Create interface translatable applications that will run on Windows, Macintosh OSX, Linux, and other UNIX like environments.
  • Learn basic and advanced user interface controls.
  • Packed with practical, hands-on cookbook recipes and plenty of example code, illustrating the techniques to develop feature rich applications using wxPython.
        Read more about this book      

(For more resources on Python, see here.)

Introduction

Programming is all about patterns. There are patterns at every level, from the programming language itself, to the toolkit, to the application. Being able to discern and choose the optimal approach to use to solve the problem at hand can at times be a difficult task. The more patterns you know, the bigger your toolbox, and the easier it will become to be able to choose the right tool for the job.

Different programming languages and toolkits often lend themselves to certain patterns and approaches to problem solving. The Python programming language and wxPython are no different, so let's jump in and take a look at how to apply some common design approaches and techniques to wxPython applications.

Creating Singletons

In object oriented programming, the Singleton pattern is a fairly simple concept of only allowing exactly one instance of a given object to exist at a given time. This means that it only allows for only one instance of the object to be in memory at any given time, so that all references to the object are shared throughout the application. Singletons are often used to maintain a global state in an application since all occurrences of one in an application reference the same exact instance of the object. Within the core wxPython library, there are a number of singleton objects, such as ArtProvider , ColourDatabase , and SystemSettings . This recipe shows how to make a singleton Dialog class, which can be useful for creating non-modal dialogs that should only have a single instance present at a given time, such as a settings dialog or a special tool window.

How to do it...

To get started, we will define a metaclass that can be reused on any class that needs to be turned into a singleton. We will get into more detail later in the How it works section. A metaclass is a class that creates a class. It is passed a class to it's __init__ and __call__ methods when someone tries to create an instance of the class.

class Singleton(type):
def __init__(cls, name, bases, dict):
super(Singleton, cls).__init__(name, bases, dict)
cls.instance = None

def __call__(cls, *args, **kw):
if not cls.instance:
# Not created or has been Destroyed
obj = super(Singleton, cls).__call__(*args, **kw)
cls.instance = obj
cls.instance.SetupWindow()

return cls.instance

Here we have an example of the use of our metaclass, which shows how easy it is to turn the following class into a singleton class by simply assigning the Singleton class as the __metaclass__ of SingletonDialog. The only other requirement is to define the SetupWindow method that the Singleton metaclass uses as an initialization hook to set up the window the first time an instance of the class is created.

Note that in Python 3+ the __metaclass__ attribute has been replaced with a metaclass keyword argument in the class definition.

class SingletonDialog(wx.Dialog):
__metaclass__ = Singleton

def SetupWindow(self):
"""Hook method for initializing window"""
self.field = wx.TextCtrl(self)
self.check = wx.CheckBox(self, label="Enable Foo")

# Layout
vsizer = wx.BoxSizer(wx.VERTICAL)
label = wx.StaticText(self, label="FooBar")
hsizer = wx.BoxSizer(wx.HORIZONTAL)
hsizer.AddMany([(label, 0, wx.ALIGN_CENTER_VERTICAL),
((5, 5), 0),
(self.field, 0, wx.EXPAND)])
btnsz = self.CreateButtonSizer(wx.OK)
vsizer.AddMany([(hsizer, 0, wx.ALL|wx.EXPAND, 10),
(self.check, 0, wx.ALL, 10),
(btnsz, 0, wx.EXPAND|wx.ALL, 10)])
self.SetSizer(vsizer)
self.SetInitialSize()

How it works...

There are a number of ways to implement a Singleton in Python. In this recipe, we used a metaclass to accomplish the task. This is a nicely contained and easily reusable pattern to accomplish this task. The Singleton class that we defined can be used by any class that has a SetupWindow method defined for it. So now that we have done it, let's take a quick look at how a singleton works.

The Singleton metaclass dynamically creates and adds a class variable called instance to the passed in class. So just to get a picture of what is going on, the metaclass would generate the following code in our example:

class SingletonDialog(wx.Dialog):
instance = None

Then the first time the metaclass's __call__ method is invoked, it will then assign the instance of the class object returned by the super class's __call__ method, which in this recipe is an instance of our SingletonDialog. So basically, it is the equivalent of the following:

SingletonDialog.instance = SingletonDialog(*args,**kwargs)

Any subsequent initializations will cause the previously-created one to be returned, instead of creating a new one since the class definition maintains the lifetime of the object and not an individual reference created in the user code.

Our SingletonDialog class is a very simple Dialog that has TextCtrl, CheckBox, and Ok Button objects on it. Instead of invoking initialization in the dialog's __init__ method, we instead defined an interface method called SetupWindow that will be called by the Singleton metaclass when the object is initially created. In this method, we just perform a simple layout of our controls in the dialog. If you run the sample application that accompanies this topic, you can see that no matter how many times the show dialog button is clicked, it will only cause the existing instance of the dialog to be brought to the front. Also, if you make changes in the dialog's TextCtrl or CheckBox, and then close and reopen the dialog, the changes will be retained since the same instance of the dialog will be re-shown instead of creating a new one.

Implementing an observer pattern

The observer pattern is a design approach where objects can subscribe as observers of events that other objects are publishing. The publisher(s) of the events then just broadcasts the events to all of the subscribers. This allows the creation of an extensible, loosely-coupled framework of notifications, since the publisher(s) don't require any specific knowledge of the observers. The pubsub module provided by the wx.lib package provides an easy-to-use implementation of the observer pattern through a publisher/subscriber approach. Any arbitrary number of objects can subscribe their own callback methods to messages that the publishers will send to make their notifications. This recipe shows how to use the pubsub module to send configuration notifications in an application.

How to do it...

Here, we will create our application configuration object that stores runtime configuration variables for an application and provides a notification mechanism for whenever a value is added or modified in the configuration, through an interface that uses the observer pattern:

import wx
from wx.lib.pubsub import Publisher

# PubSub message classification
MSG_CONFIG_ROOT = ('config',)

class Configuration(object):
"""Configuration object that provides
notifications.
"""
def __init__(self):
super(Configuration, self).__init__()
# Attributes
self._data = dict()

def SetValue(self, key, value):
self._data[key] = value
# Notify all observers of config change
Publisher.sendMessage(MSG_CONFIG_ROOT + (key,),
value)

def GetValue(self, key):
"""Get a value from the configuration"""
return self._data.get(key, None)

Now, we will create a very simple application to show how to subscribe observers to configuration changes in the Configuration class:

class ObserverApp(wx.App):
def OnInit(self):
self.config = Configuration()
self.frame = ObserverFrame(None,

title="Observer Pattern")
self.frame.Show()
self.configdlg = ConfigDialog(self.frame,
title="Config Dialog")
self.configdlg.Show()
return True

def GetConfig(self):
return self.config

This dialog will have one configuration option on it to allow the user to change the applications font:

class ConfigDialog(wx.Dialog):
"""Simple setting dialog"""
def __init__(self, *args, **kwargs):
super(ConfigDialog, self).__init__(*args, **kwargs)

# Attributes
self.panel = ConfigPanel(self)

# Layout
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.panel, 1, wx.EXPAND)
self.SetSizer(sizer)
self.SetInitialSize((300, 300))
class ConfigPanel(wx.Panel):
def __init__(self, parent):
super(ConfigPanel, self).__init__(parent)

# Attributes
self.picker = wx.FontPickerCtrl(self)

# Setup
self.__DoLayout()

# Event Handlers
self.Bind(wx.EVT_FONTPICKER_CHANGED,
self.OnFontPicker)

def __DoLayout(self):
vsizer = wx.BoxSizer(wx.VERTICAL)
hsizer = wx.BoxSizer(wx.HORIZONTAL)

vsizer.AddStretchSpacer()
hsizer.AddStretchSpacer()
hsizer.AddWindow(self.picker)
hsizer.AddStretchSpacer()
vsizer.Add(hsizer, 0, wx.EXPAND)
vsizer.AddStretchSpacer()
self.SetSizer(vsizer)

Here, in the FontPicker's event handler, we get the newly-selected font and call SetValue on the Configuration object owned by the App object in order to change the configuration, which will then cause the ('config', 'font') message to be published:

def OnFontPicker(self, event):
"""Event handler for the font picker control"""
font = self.picker.GetSelectedFont()
# Update the configuration
config = wx.GetApp().GetConfig()
config.SetValue('font', font)

Now, here, we define the application's main window that will subscribe it's OnConfigMsg method as an observer of all ('config',) messages, so that it will be called whenever the configuration is modified:

class ObserverFrame(wx.Frame):
"""Window that observes configuration messages"""
def __init__(self, *args, **kwargs):
super(ObserverFrame, self).__init__(*args, **kwargs)

# Attributes
self.txt = wx.TextCtrl(self, style=wx.TE_MULTILINE)
self.txt.SetValue("Change the font in the config "
"dialog and see it update here.")

# Observer of configuration changes
Publisher.subscribe(self.OnConfigMsg, MSG_CONFIG_ROOT)

def __del__(self):
# Unsubscribe when deleted
Publisher.unsubscribe(self.OnConfigMsg)

Here is the observer method that will be called when any message beginning with 'config' is sent by the pubsub Publisher. In this sample application, we just check for the ('config', 'font') message and update the font of the TextCtrl object to use the newly-configured one:

def OnConfigMsg(self, msg):
"""Observer method for config change messages"""
if msg.topic[-1] == 'font':
# font has changed so update controls
self.SetFont(msg.data)
self.txt.SetFont(msg.data)

if __name__ == '__main__':
app = ObserverApp(False)
app.MainLoop()

How it works...

This recipe shows a convenient way to manage an application's configuration by allowing the interested parts of an application to subscribe to updates when certain parts of the configuration are modified. Let's start with a quick walkthrough of how pubsub works.

Pubsub messages use a tree structure to organize the categories of different messages. A message type can be defined either as a tuple ('root', 'child1', 'grandchild1') or as a dot-separated string ('root.child1.grandchild1'). Subscribing a callback to ('root',) will cause your callback method to be called for all messages that start with ('root',). This means that if a component publishes ('root', 'child1', 'grandchild1') or ('root', 'child1'), then any method that is subscribed to ('root',) will also be called

Pubsub basically works by storing the mapping of message types to callbacks in static memory in the pubsub module. In Python, modules are only imported once any other part of your application that uses the pubsub module shares the same singleton Publisher object.

In our recipe, the Configuration object is a simple object for storing data about the configuration of our application. Its SetValue method is the important part to look at. This is the method that will be called whenever a configuration change is made in the application. In turn, when this is called, it will send a pubsub message of ('config',) + (key,) that will allow any observers to subscribe to either the root item or more specific topics determined by the exact configuration item.

Next, we have our simple ConfigDialog class. This is just a simple example that only has an option for configuring the application's font. When a change is made in the FontPickerCtrl in the ConfigPanel, the Configuration object will be retrieved from the App and will be updated to store the newly-selected Font. When this happens, the Configuration object will publish an update message to all subscribed observers.

Our ObserverFrame is an observer of all ('config',) messages by subscribing its OnConfigMsg method to MSG_CONFIG_ROOT. OnConfigMsg will be called any time the Configuration object's SetValue method is called. The msg parameter of the callback will contain a Message object that has a topic and data attribute. The topic attribute will contain the tuple that represents the message that triggered the callback and the data attribute will contain any data that was associated with the topic by the publisher of the message. In the case of a ('config', 'font') message, our handler will update the Font of the Frame and its TextCtrl.

wxPython 2.8 Application Development Cookbook Over 80 practical recipes for developing feature-rich applications using wxPython
Published: December 2010
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources on Python, see here.)

Strategy pattern

The strategy pattern is an approach that allows for an application to choose the strategy or behavior that will be used at run time. It accomplishes this by encapsulating different algorithms and making them usable by the client regardless of what the underlying behavior of the algorithm is. This is probably one of the most fundamental design patterns in programming, and you're probably already using it in one form or another without even knowing it. This recipe shows how to create a reusable Dialog class that uses the strategy pattern to allow for the main content to vary depending on the strategy used.

How to do it...

First, we will start by defining a base interface with all of the strategies that our dialog class will use:

class BaseDialogStrategy:
"""Defines the strategy interface"""
def GetWindowObject(self, parent):
"""Must return a Window object"""
raise NotImplementedError, "Required method"

def DoOnOk(self):
"""@return: bool (True to exit, False to not)"""
return True

def DoOnCancel(self):
"""@return: bool (True to exit, False to not)"""
return True

Now let's define our simple Ok/Cancel dialog, which will use a strategy derived from our BaseDialogStrategy class to allow its main content area to vary depending on the strategy used:

class StrategyDialog(wx.Dialog):
"""Simple dialog with builtin OK/Cancel button and
strategy based content area.
"""
def __init__(self, parent, strategy, *args, **kwargs):
super(StrategyDialog, self).__init__(parent,
*args,
**kwargs)
# Attributes
self.strategy = strategy
self.pane = self.strategy.GetWindowObject(self)

# Layout
self.__DoLayout()

# Event Handlers
self.Bind(wx.EVT_BUTTON, self.OnButton)

Here, in the following methods of our StrategyDialog, we just delegate to the current strategy to allow it to define the behavior of the dialog:

def __DoLayout(self):
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.pane, 1, wx.EXPAND)
btnsz = self.CreateButtonSizer(wx.OK|wx.CANCEL)
sizer.Add(btnsz, 0, wx.EXPAND|wx.ALL, 8)
self.SetSizer(sizer)

def GetStrategy(self):
return self.strategy

def OnButton(self, event):
evt_id = event.GetId()
bCanExit = False
if evt_id == wx.ID_OK:
bCanExit = self.strategy.DoOnOk()
elif evt_id == wx.ID_OK:
bCanExit = self.strategy.DoOnCancel()
else:
evt.Skip()

if bCanExit:
self.EndModal(evt_id)

Now let's implement a simple strategy that can be used to get the dialog to display a control for selecting a file:

class FileTreeStrategy(BaseDialogStrategy):
"""File chooser strategy"""
def GetWindowObject(self, parent):
assert not hasattr(self, 'dirctrl')
self.dirctrl = wx.GenericDirCtrl(parent)
return self.dirctrl

def DoOnOk(self):
path = self.dirctrl.GetPath()
if path:
wx.MessageBox("You selected: %s" % path)
return True
else:
wx.MessageBox("No file selected!")
return False

Then, in an application, all that needs to be done to create a dialog that uses this strategy would be the following:

# Note: 'self' is some window object (i.e a Frame)
strategy = FileTreeStrategy()
dlg = StrategyDialog(self, strategy, title="Choose File")
dlg.ShowModal()

How it works...

Since all strategies that our dialog will use must be interchangeable, it is important to define an interface that they will implement. So, in our BaseDialogStrategy class, we defined a simple three-method interface that our StrategyDialog will delegate to.

The StrategyDialog is basically just a simple generic shell that delegates all decisions regarding its appearance and behavior to the strategy. When the dialog is initialized, it asks the strategy for a window object that will be used as the main content area of the dialog. The dialog then creates and adds some standard OK/Cancel buttons to the interface.

When a user clicks on one of these buttons, the StrategyDialog will then simply delegate to its strategy, to allow the strategy to handle the user action. This allows us to reuse this dialog class in many different ways, by simply implementing different strategies.

Model View Controller

Model View Controller (MVC) is a design pattern that creates a clear separation of concerns within a program's architecture. It breaks down into three layers: the Model, which is the application's data objects and business logic at the bottom, the View at the top, which typically consists of controls for displaying and editing data, and finally the Controller in the middle, which is responsible for mediating the flow of data from the Model to the View and vice versa:

wxPython: Design Approaches and Techniques

M VC is really one big monster pattern made up of other, simpler patterns working together. The Model implements an observer pattern to keep interested parties updated on changes, which allows it to be implemented separately from the View and Controller. The View and the Controller, on the other hand, implement a strategy pattern where the Controller is a strategy that implements the behavior of the View.

In this recipe, we explore how to create a simple number generator application that implements this pattern in wxPython.

How to do it...

Since there are multiple components that need to work together, having defined interfaces is an important step in the process, so first let us define some base classes that define the interface for our number generator's model and controller.

Beginning with our Model's interface, we provide a class that simply requires its Generate method to be overridden in order to provide the implementation-specific behavior. We have also built in a simple observer pattern mechanism to allow the view to subscribe to update notifications in the model:

class ModelInterface(object):
"""Defines an interface for a simple value
generator model.
"""
def __init__(self):
super(ModelInterface, self).__init__()

# Attributes
self.val = 0
self.observers = list()

def Generate(self):
"""Interface method to be implemented by
subclasses.
"""
raise NotImplementedError

def SetValue(self, val):
self.val = val
self.NotifyObservers()

def GetValue(self):
return self.val

def RegisterObserver(self, callback):
"""Register an observer callback
@param: callable(newval)
"""
self.observers.append(callback)

def NotifyObservers(self):
"""Notify all observers of current value"""
for observer in self.observers:
observer()

Next we have the base interface definition for the controllers of our framework's view to derive from. This just defines one simple DoGenerateNext method that must be overridden by the specific implementation:

class ControllerInterface(object):
"""Defines an interface a value generator
controller.
"""
def __init__(self, model):
super(ControllerInterface, self).__init__()

# Attributes
self.model = model
self.view = TheView(None, self, self.model,
"Fibonacci Generator")

# Setup
self.view.Show()

def DoGenerateNext(self):
"""User action request next value"""
raise NotImplementedError

Now let's define some subclasses that implement the interface and provide the specialization.

Beginning with our FibonacciModel class, we define a model that will generate Fibonacci numbers:

class FibonacciModel(ModelInterface):
def Generate(self):
cval = self.GetValue()
# Get the next one
for fib in self.fibonacci():
if fib > cval:
self.SetValue(fib)
break
@staticmethod
def fibonacci():
"""Fibonacci generator method"""
a, b = 0, 1
while True:
yield a
a, b = b, a + b

Then our FibonacciController provides the controller specialization, which in this example just makes one update to the user interface, to disable the button while the model is calculating the next value:

class FibonacciController(ControllerInterface):
def DoGenerateNext(self):
self.view.EnableButton(False)
self.model.Generate()

Now that the model and controller have been defined, let's take a look at our view, which is composed of a Frame, a Panel that has a TextCtrl for displaying the current value stored in the model, and a Button for retrieving the next value in the sequence defined by the model:

class TheView(wx.Frame):
def __init__(self, parent, controller, model, title):
"""The view for """
super(TheView, self).__init__(parent, title=title)

# Attributes
self.panel = ViewPanel(self, controller, model)

# Layout
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.panel, 1, wx.EXPAND)
self.SetSizer(sizer)
self.SetInitialSize((300, 300))

def EnableButton(self, enable=True):
self.panel.button.Enable(enable)

Here, the ViewPanel is where we interface with the model and controller. We retrieve the initial value from the model on initialization and then register as an observer of changes in the model:

class ViewPanel(wx.Panel):
def __init__(self, parent, controller, model):
super(ViewPanel, self).__init__(parent)

# Attributes
self.model = model
self.controller = controller
initial = str(self.model.GetValue())
self.text = wx.TextCtrl(self, value=initial)
self.button = wx.Button(self, label="Generate")

# Layout
self.__DoLayout()

# Setup
self.model.RegisterObserver(self.OnModelUpdate)

# Event Handlers
self.Bind(wx.EVT_BUTTON, self.OnAction)

def __DoLayout(self):
vsizer = wx.BoxSizer(wx.VERTICAL)
hsizer = wx.BoxSizer(wx.HORIZONTAL)

vsizer.AddStretchSpacer()
vsizer.Add(self.text, 0, wx.ALIGN_CENTER|wx.ALL, 8)
hsizer.AddStretchSpacer()
hsizer.AddWindow(self.button)
hsizer.AddStretchSpacer()
vsizer.Add(hsizer, 0, wx.EXPAND)
vsizer.AddStretchSpacer()
self.SetSizer(vsizer)

Here is our observer method that will be called when the model is updated with a new value:

def OnModelUpdate(self):
"""Observer method"""
value = self.model.GetValue()
self.text.SetValue(str(value))
self.button.Enable(True)

This event handler is for the Button, and it delegates to the controller in order to allow the controller to perform the implementation-specific action:


def OnAction(self, event):
self.controller.DoGenerateNext()

Finally, we put it all together and implement an application:

class ModelViewApp(wx.App):
def OnInit(self):
self.model = FibonacciModel()
self.controller = FibonacciController(self.model)
return True

if __name__ == '__main__':
app = ModelViewApp(False)
app.MainLoop()

How it works...

Using MVC to design an application's framework takes a fair amount of discipline. As can be seen in this simple example, there is quite a bit of extra "stuff" that needs to be done. As described before, MVC separates concerns into three main roles:

  1. The Model
  2. The View
  3. The Controller

So let's take a look at how these roles came together in our sample recipe.

First the Model: This has the ability to store a value and to generate the next one in the sequence when its Generate method is called. In this recipe, we implemented a Model that calculates and stores Fibonacci numbers. The important part to take away from this is that the Model does not have any direct knowledge of the View or the Controller.

Next let's jump to the View, which just displays a TextCtrl field and a Button. It does not know any of the details of how the Controller or Model works. It only interacts with them through a defined interface. When the user clicks on the Button, it asks the Controller to decide what to do. In order to know when the Model has changed, it registers a callback function with the Model, as an observer of when the Model's SetValue method is called.

Now to the Controller which is the glue that holds the Model and View together. The Controller is primarily charged with implementing the View's behavior in regards to the Model's state. Our simple Controller for this recipe only has one interface method that is called in response to a Button click in the View. This action first disables the Button, and then tells the Model to generate the next number in the sequence.

There's more...

You may be wondering "what's the point?" of all that extra rigmarole to create such a simple application. Well since the Model is completely separate from the View, it can be more easily unit tested in an automated test suite. In addition to this, since the View is just simply a view and does not implement any behavior, it can easily be reused if, for example, we wanted to add a Prime Number generator model to our application.

Maintainability is also improved since all three parts are separated and can be worked on individually without interfering with the other components. Because of these benefits, many other toolkits, such as Django and web2py make use of this pattern.

wxPython 2.8 Application Development Cookbook Over 80 practical recipes for developing feature-rich applications using wxPython
Published: December 2010
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources on Python, see here.)

Using mixin classes

A mixin class is a design approach that is similar to the strategy pattern, but directly uses inheritance in order to add extended/common functionality to a new class. This recipe shows how to create a mixin class that adds debug logging facilities to any class that uses it.

How to do it...

First, let's start by creating our LoggerMixin class, which will provide the logging functionality to classes that need to have logging. It simply provides a Log method that will write the passed in string to a file:

import os
import time
import wx

class LoggerMixin:
def __init__(self, logfile="log.txt"):
"""@keyword logfile: path to log output file"""
# Attributes
self.logfile = logfile

def Log(self, msg):
"""Write a message to the log.
Automatically adds timestamp and originating class
information.
"""
if self.logfile is None:
return

# Open and write to the log file
with open(self.logfile, 'ab') as handle:
# Make a time stamp
ltime = time.localtime(time.time())
tstamp = "%s:%s:%s" % (str(ltime[3]).zfill(2),
str(ltime[4]).zfill(2),
str(ltime[5]).zfill(2))
# Add client info
client = getattr(self, 'GetName',
lambda: "unknown")()
# Construct the final message
output = "[%s][%s] %s%s" % (tstamp, client,
msg, os.linesep)
handle.write(output)

Then, to use the LoggerMixin in an application, it can simply be mixed in to any class to give it a Log method:

class MixinRecipeFrame(wx.Frame, LoggerMixin):
"""Main application window"""
def __init__(self, parent, *args, **kwargs):
wx.Frame.__init__(self, parent, *args, **kwargs)
LoggerMixin.__init__(self)
self.Log("Creating instance...")

# Attributes
self.panel = MixinRecipePanel(self)

# Layout
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.panel, 1, wx.EXPAND)
self.SetSizer(sizer)
self.SetInitialSize((300, 300))

How it works...

The mixin class in this recipe is the LoggerMixin class. It will add a Log method to the classes that use it, which will take a simple string as an argument and write it to the specified log file with a timestamp and ID that shows where the message came from.

A mixin works by using multiple inheritance in order to add additional functionality to a class. The LoggerMixin mixin class can be used with any Python class, but it expects (but doesn't require) that the class it is being mixed into has a GetName method to use for getting the ID portion of the log message:

[17:42:24][unknown] OnInit called
[17:42:24][frame] Creating instance...
[17:42:24][panel] Begin Layout
[17:42:24][panel] End Layout
[17:42:26][panel] Button -203: Clicked
[17:42:26][panel] Button -203: Clicked
[17:42:27][panel] Button -203: Clicked

There's more

There are a number of handy mixin classes provided by the wx.lib.mixins package. Here is a quick rundown on some of the mixins that are available and what functionality they provide.

ListCtrl mixins

All of the following mixins classes are intended for use with a ListCtrl subclass and are provided by the wx.lib.mixins.listctrl module:

wxPython: Design Approaches and Techniques

TreeCtrl mixins

All of the following mixin classes are for use with a TreeCtrl subclass, and are provided by the wx.lib.mixins.treectrl module:

wxPython: Design Approaches and Techniques

Using decorators

Due to the window hierarchy, there are some architectural issues that can be presented to the programmer that lead to some tedious and unnecessary code duplication due to the need to have delegate accessor methods or properties at each level of the containment hierarchy. Typically, any Frame or Dialog in an application is structured as shown in the following diagram:

wxPython: Design Approaches and Techniques

When needing to retrieve or modify the data that is shown in the window, it is the widgets and Controls that need to be accessed. These are contained by the Panel which is in turn contained by the Top Level Window. Since the Panel is responsible for its children, it will often have methods for modifying and accessing the data that is maintained by its children's controls. Because of this, the top-level window class often needs to have duplicate methods that simply delegate to the Panel's methods for getting and setting the window's data. These delegate methods are needed because the top-level window is the object that is instantiated at the application level and the application should not need to know the details of the top-level window's Panel in order to use it.

T his recipe shows how to create a simple decorator method that takes advantage of Python's dynamic nature in order to expose a select method of a custom Panel class to its top-level window container.

How to do it...

This decorator class takes the name of a class as an argument and will dynamically define the delegate method in the targeted child Panel of the top level window:

class expose(object):
"""Expose a panels method to a to a specified class
The panel that is having its method exposed by this
decorator must be a child of the class its exposing
itself too.
"""
def __init__(self, cls):
"""@param cls: class to expose the method to"""
super(expose, self).__init__()
self.cls = cls

Here is where the magic occurs. We use setattr to dynamically add a function with the same name as the function being decorated to the targeted class. When called from the targeted class, the new method will walk through the window's children to find its Panel, and will delegate the call to the child class's method:

def __call__(self, funct):
"""Dynamically bind and expose the function
to the toplevel window class.
"""
fname = funct.func_name
def delegate(*args, **kwargs):
"""Delegate method for panel"""
self = args[0] # The TLW
# Find the panel this method belongs to
panel = None
for child in self.GetChildren():
if isinstance(child, wx.Panel) and \
hasattr(child, fname):
panel = child
break
assert panel is not None, "No matching child!"
# Call the panels method
return getattr(panel, fname)(*args[1:], **kwargs)

# Bind the new delegate method to the tlw class
delegate.__name__ = funct.__name__
delegate.__doc__ = funct.__doc__
setattr(self.cls, fname, delegate)

# Return original function to the current class
return funct

The example code that accompanies this article has a sample application that shows how to use this decorator.

How it works...

This recipe isn't so much a design pattern as it is a technique to help make writing new Dialog and Frame classes quicker and also to reduce code duplication. To do this, we created a decorator class for exposing methods from child Panel classes to their parent top-level window. Let's start with a look at the expose decorator to see how it works its magic.

The expose decorator takes a single argument, which is the class that the method should be exposed to. A reference to this is saved in the constructor for later use when the decorator is applied in its __call__ method. The __call__ method creates a method called delegate which will search for the first child panel that has a method with the same name as the one that is being exposed. If it finds an appropriate panel, then it simply calls the panel's method and returns its value. Next, it uses setattr to insert the newly-generated delegate method with an alias matching the Panel's method into the namespace of the class specified in the decorator's constructor. At this point, the method is available for use in the top-level window that expose was called with. Finally, we just return the unaltered original function to the Panel class it belongs to.

Just to be clear, this decorator, as it is defined in this recipe, can only be used by Panel subclasses that have a known relationship of being the only child of their parent window. This is typical of how most Frame and Dialog subclasses are constructed, as can be seen in the example CommentDialog class that is included in this article's sample code.

Summary

This article introduced you to a number of common programming patterns, and explained how to apply them to wxPython applications. The information in this article provided you with an understanding of some strong approaches and techniques to software design that will not only serve you in writing wxPython applications but can also be generally applied to other frameworks as well, to expand your programming toolbox.


Further resources on this subject:


About the Author :


Cody Precord

Cody Precord is a Software Engineer based in Minneapolis, MN, USA. He has been designing and writing systems and application software for AIX, Linux, Windows, and Macintosh OSX for the last 10 years using primarily C, C++, Perl, Bash, Korn Shell, and Python. The constant need to be working on multiple platforms naturally led Cody to the wxPython toolkit, which he has been using intensely for that last 5 years. Cody has been primarily using wxPython for his open source project Editra which is a cross platform development tool. He is interested in promoting cross platform development practices and improving usability in software.

Books From Packt


Python 2.6 Text Processing Beginners Guide
Python 2.6 Text Processing Beginners Guide

Python Text Processing with NLTK 2.0   Cookbook
Python Text Processing with NLTK 2.0 Cookbook

Python Geo-Spatial Development
Python Geo-Spatial Development

Python 3 Object Oriented Programming
Python 3 Object Oriented Programming

Python 2.6 Graphics Cookbook
Python 2.6 Graphics Cookbook

Spring Python 1.1
Spring Python 1.1

Python Multimedia
Python Multimedia

MySQL for Python
MySQL for Python


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software