wxPython 2.8: Advanced Building Blocks of a User Interface

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

£14.99    £7.50
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:

  • Listing data with a ListCtrl
  • Browsing files with the CustomTreeCtrl
  • Creating a VListBox
  • StyledTextCtrl using lexers
  • Working with tray icons
  • Adding tabs to a Notebook
  • Using the FlatNotebook
  • Scrolling with a ScrolledPanel
  • Simplifying the FoldPanelBar

 

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 wxPython, see here.)

Introduction

Displaying collections of data and managing complex window layouts are a task that most UI developers will be faced with at some point. wxPython provides a number of components to help developers meet the requirements of these more demanding interfaces.

As the amount of controls and data that an application is required to display in its user interface increases, so does the task of efficiently managing available screen real estate. To fit this information into the available space requires the use of some more advanced controls and containers; so let's dive in and begin our exploration of some of the more advanced controls that wxPython has to offer.

Listing data with a ListCtrl

The ListCtrl is a versatile control for displaying collections of text and/or images. The control supports many different display formats, although typically its most often-used display mode is the report mode. Report mode has a visual representation that is very similar to a grid or spreadsheet in that it can have multiple rows and columns with column headings. This recipe shows how to populate and retrieve data from a ListCtrl that was created in report mode.

wxPython 2.8: Advanced Building Blocks of a User Interface

How to do it...

The ListCtrl takes a little more set up than most basic controls, so we will start by creating a subclass that sets up the columns that we wish to have in the control:

class MyListCtrl(wx.ListCtrl):
def __init__(self, parent):
super(MyListCtrl, self).__init__(parent,
style=wx.LC_REPORT)

# Add three columns to the list
self.InsertColumn(0, "Column 1")
self.InsertColumn(1, "Column 2")
self.InsertColumn(2, "Column 3")

def PopulateList(self, data):
"""Populate the list with the set of data. Data
should be a list of tuples that have a value for each
column in the list.
[('hello', 'list', 'control'),]
"""
for item in data:
self.Append(item)

Next we will create an instance of our ListCtrl and put it on a Panel, and then use our PopulateList method to put some sample data into the control:

class MyPanel(wx.Panel):
def __init__(self, parent):
super(MyPanel, self).__init__(parent)

# Attributes
self.lst = MyListCtrl(self)

# Setup
data = [ ("row %d" % x,
"value %d" % x,
"data %d" % x) for x in range(10) ]
self.lst.PopulateList(data)

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

# Event Handlers
self.Bind(wx.EVT_LIST_ITEM_SELECTED,
self.OnItemSelected)

def OnItemSelected(self, event):
selected_row = event.GetIndex()
val = list()
for column in range(3):
item = self.lst.GetItem(selected_row, column)
val.append(item.GetText())
# Show what was selected in the frames status bar
frame = self.GetTopLevelParent()
frame.PushStatusText(",".join(val))

How it works...

Usually there tends to be a fair amount of set up with the ListCtrl, and due to this it is good to encapsulate the usage of the control in a specialized subclass instead of using it directly. We kept things pretty basic here in our ListCtrl class. We just used the InsertColumn method to set our list up with three columns. Then the PopulateList method was added for convenience, to allow the population of the ListCtrl from a Python list of data. It simply wraps the Append method of ListCtrl, which just takes an iterable that has a string for each column in the list.

The MyPanel class is there to show how to use the ListCtrl class that we created. First we populate it with some data by generating a list of tuples and calling our PopulateList method. To show how to retrieve data from the list, we created an event handler for EVT_LIST_ITEM_SELECTED which will be fired each time a new selection is made in the control. In order to retrieve a value from a ListCtrl, you need to know the row and column index of the cell that you wish to retrieve the data from, and then call GetItem with the row and column to get the ListItem object that represents that cell. Then the string value of the cell can be retrieved by calling the GetText method of ListItem.

There's more...

Depending on the style flags that are used to create a ListCtrl, it will behave in many different possible ways. Because of this, it is important to know some of the different style flags that can be used to create a ListCtr.

Style flags

Description

LC_LIST

In List mode, the control will calculate the columns automatically, so there is no need to call InsertColumn. It can be used to display strings and, optionally, small icons

LC_REPORT

Single or multicolumn report view that can be shown with or without headers

LC_ICON

Large icon view that can optionally have labels

LC_SMALL_ICON

Small icon view that can optionally have labels

LC_EDIT_LABELS

Allow the item labels to be editable by users

LC_NO_HEADER

Hide the column headers (report mode)

LC_SORT_ASCENDING

Sort items in ascending order (must provide a SortItems callback method)

LC_SORT_DESCENDING

Sort items in descending order (must provide a SortItems callback method)

LC_HRULE

Draw a horizontal line between rows (report mode)

LC_VRULE

Draw a vertical line between columns (report mode)

LC_SINGLE_SEL

Only allow a single item to be selected at a time (Default is to allow for multiple selections)

LC_VIRTUAL

Fetch items to display in the list on demand (report mode)

Virtual Mode

When a ListCtrl is created in virtual mode (using the LC_VIRTUAL style flag), it does not store the data internally; instead it will instead ask for the data from a datasource when it needs to display it. This mode is useful when you have a very large set of data where preloading it in the control would present performance issues. To use a ListCtrl in virtual mode, you must call SetItemCount to tell the control how many rows of data there are, and override the OnGetItemText method to return the text for the ListItem when the control asks for it.

Browsing files with the CustomTreeCtrl

A TreeCtrl is a way of displaying hierarchical data in a user interface. The CustomTreeCtrl is a fully owner-drawn TreeCtrl that looks and functions much the same way as the default TreeCtrl, but that offers a number of additional features and customizability that the default native control cannot. This recipe shows how to make a custom file browser class by using the CustomTreeCtrl.

How to do it...

To create this custom FileBrowser control, we will use its constructor to set up the images to use for the folders and files in the tree:

import os
import wx
import wx.lib.customtreectrl as customtree

class FileBrowser(customtree.CustomTreeCtrl):
FOLDER, \
ERROR, \
FILE = range(3)
def __init__(self, parent, rootdir, *args, **kwargs):
super(FileBrowser, self).__init__(parent,
*args,
**kwargs)
assert os.path.exists(rootdir), \
"Invalid Root Directory!"
assert os.path.isdir(rootdir), \
"rootdir must be a Directory!"

# Attributes
self._il = wx.ImageList(16, 16)
self._root = rootdir
self._rnode = None

# Setup
for art in (wx.ART_FOLDER, wx.ART_ERROR,
wx.ART_NORMAL_FILE):
bmp = wx.ArtProvider.GetBitmap(art, size=(16,16))
self._il.Add(bmp)
self.SetImageList(self._il)
self._rnode = self.AddRoot(os.path.basename(rootdir),
image=FileBrowser.FOLDER,
data=self._root)
self.SetItemHasChildren(self._rnode, True)
# use Windows-Vista-style selections
self.EnableSelectionVista(True)

# Event Handlers
self.Bind(wx.EVT_TREE_ITEM_EXPANDING,
self.OnExpanding)
self.Bind(wx.EVT_TREE_ITEM_COLLAPSED,
self.OnCollapsed)

def _GetFiles(self, path):
try:
files = [fname for fname in os.listdir(path)
if fname not in ('.', '..')]
except OSError:
files = None
return files

The following two event handlers are used to update which files are displayed when a node is expanded or collapsed in the tree:

def OnCollapsed(self, event):
item = event.GetItem()
self.DeleteChildren(item)

def OnExpanding(self, event):
item = event.GetItem()
path = self.GetPyData(item)
files = self._GetFiles(path)

# Handle Access Errors
if files is None:
self.SetItemImage(item, FileBrowser.ERROR)
self.SetItemHasChildren(item, False)
return

for fname in files:
fullpath = os.path.join(path, fname)
if os.path.isdir(fullpath):
self.AppendDir(item, fullpath)
else:
self.AppendFile(item, fullpath)

The following methods are added as an API for working with the control to add items and retrieve their on-disk paths:

def AppendDir(self, item, path):
"""Add a directory node"""
assert os.path.isdir(path), "Not a valid directory!"
name = os.path.basename(path)
nitem = self.AppendItem(item, name,
image=FileBrowser.FOLDER,
data=path)
self.SetItemHasChildren(nitem, True)

def AppendFile(self, item, path):
"""Add a file to a node"""
assert os.path.isfile(path), "Not a valid file!"
name = os.path.basename(path)
self.AppendItem(item, name,
image=FileBrowser.FILE,
data=path)

def GetSelectedPath(self):
"""Get the selected path"""
sel = self.GetSelection()
path = self.GetItemPyData(sel)
return path

def GetSelectedPaths(self):
"""Get a list of selected paths"""
sels = self.GetSelections()
paths = [self.GetItemPyData(sel)
for sel in sels ]
return paths

How it works...

With just a few lines of code here we have created a pretty useful little widget for displaying and working with the file system. Let's take a quick look at how it works.

In the classes constructor, we added a root node with the control's AddRoot method. A root node is a top-level node that has no other parent nodes above it. The first argument is the text that will be shown, the image argument specifies the default image for the TreeItem, and the data argument specifies any type of data associated with the item—in this case we are setting a string for the items path. We then called SetItemHasChildren for the item so that it will get a button next to it to allow it to be expanded. The last thing that we did in the constructor was to Bind the control to two events so that we can update the tree when one of its nodes is being expanded or collapsed.

Immediately before the node is going to be expanded our handler for EVT_TREE_ITEM_ EXPANDING will be called. It is here where we find all the files and folders under a directory node, and then add them as children of that node by calling AppendItem, which works just like AddRoot but is used to add items to already-existing nodes in the tree.

Conversely when a node in the tree is going to be collapsed, our EVT_TREE_ITEM_COLLAPED event handler will be called. Here we simply call DeleteChildren in order to remove the children items from the node so that we can update them more easily the next time that the node is expanded. Otherwise, we would have to find what was different the next time it was expanded, and then remove the items that have been deleted and insert new items that may have been added to the directory.

The last two items in our class are for getting the file paths of the selected items, which—since we store the file path in each node—is simply just a matter of getting the data from each of the currently-selected TreeItems with a call to GetPyData.

There's more...

Most of what we did in this recipe could actually also be replicated with the standard TreeCtrl. The difference is in the amount of extra customizability that the CustomTreeCtrl provides. Since it is a fully owner-drawn control, nearly all of the visible attributes of it can be customized. Following is a list of some of the functions that can be used to customize its appearance:

wxPython 2.8: Advanced Building Blocks of a User Interface

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

(For more resources on wxPython, see here.)

Creating a VListBox

The VListBox control is much like a ListBox control, but it is virtual (it doesn't store the data internally) and allows for items to have variable row heights. It works by providing a number of virtual callback methods that you must override in a subclass in order to draw the items on demand. Because of this requirement to override pure virtual methods, the VListBox will always be subclassed. This recipe shows how to create a VListBox derived control that supports an icon and text in each of its items.

How to do it...

To create our user list control, we just need to subclass a VListBox and override some of its callback methods to perform the necessary actions:

class UserListBox(wx.VListBox):
"""Simple List Box control to show a list of users"""
def __init__(self, parent, users):
"""@param users: list of user names"""
super(UserListBox, self).__init__(parent)

# Attributes
# system-users.png is a sample image provided with
# this chapters sample code.
self.bmp = wx.Bitmap("system-users.png",
wx.BITMAP_TYPE_PNG)
self.bh = self.bmp.GetHeight()
self.users = users

# Setup
self.SetItemCount(len(self.users))

def OnMeasureItem(self, index):
"""Called to get an items height"""
# All our items are the same so index is ignored
return self.bh + 4

def OnDrawSeparator(self, dc, rect, index):
"""Called to draw the item separator"""
oldpen = dc.GetPen()
dc.SetPen(wx.Pen(wx.BLACK))
dc.DrawLine(rect.x, rect.y,
rect.x + rect.width,
rect.y)
rect.Deflate(0, 2)
dc.SetPen(oldpen)

def OnDrawItem(self, dc, rect, index):
"""Called to draw the item"""
# Draw the bitmap
dc.DrawBitmap(self.bmp, rect.x + 2,
((rect.height - self.bh) / 2) + rect.y)
# Draw the label to the right of the bitmap
textx = rect.x + 2 + self.bh + 2
lblrect = wx.Rect(textx, rect.y,
rect.width - textx,
rect.height)
dc.DrawLabel(self.users[index], lblrect,
wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL)

Here is a screenshot of what the UserListBox looks like with some sample data in it.

wxPython 2.8: Advanced Building Blocks of a User Interface

How it works...

Our custom VListBox control could be used in any kind of application that wants to display a list of users. The constructor takes a list of usernames and calls SetItemCount to tell the control how many items it needs to be able to display. We also loaded a bitmap to use in our list's items. This bitmap is available in the sample code that accompanies this topic.

The main thing to take from this recipe is the three virtual callback methods that we overrode in order to draw the items in our control:

  1. The first required override is OnMeasureItem. This method will be called for each item in the list, and it needs to return the height of the item.
  2. The next method is OnDrawSeparator. This method is optional and can be used to draw a separator between each item in the control. It can also modify the Rect if necessary, so that when OnDrawItem is called it will know not to draw over the separator.
  3. The final method is OnDrawItem. This method is used to draw the actual item. For our control, we draw a bitmap and then position the users' name as a label to the right of it. That's all there is to it; pretty easy right.

There's more...

There are a couple more methods available that can be useful in implementing a VListBox subclass. The following list describes these methods.

wxPython 2.8: Advanced Building Blocks of a User Interface

StyledTextCtrl using lexers

The StyledTextCtrl is an advanced text control class supplied by the wx.stc module. The class is a wrapping around the Scintilla source control editing component (see http://www.scintilla.org). The StyledTextCtrl is primarily intended for displaying and working with source code for various programming languages. It provides built-in syntax highlighting support for many different types of source code files, and is extendable to work with custom lexers. This recipe shows how to setup the control to perform source code highlighting using its built-in lexer for Python.

How to do it...

To get started, we will define a language-generic editor class that will manage all the common style settings so that we can easily create other classes that support different types of programming languages:

import wx
import wx.stc as stc
import keyword

class CodeEditorBase(stc.StyledTextCtrl):
def __init__(self, parent):
super(CodeEditorBase, self).__init__(parent)

# Attributes
font = wx.Font(10, wx.FONTFAMILY_MODERN,
wx.FONTSTYLE_NORMAL,
wx.FONTWEIGHT_NORMAL)
self.face = font.GetFaceName()
self.size = font.GetPointSize()

# Setup
self.SetupBaseStyles()

def EnableLineNumbers(self, enable=True):
"""Enable/Disable line number margin"""
if enable:
self.SetMarginType(1, stc.STC_MARGIN_NUMBER)
self.SetMarginMask(1, 0)
self.SetMarginWidth(1, 25)
else:
self.SetMarginWidth(1, 0)

def GetFaces(self):
"""Get font style dictionary"""
return dict(font=self.face,
size=self.size)

def SetupBaseStyles(self):
"""Sets up the the basic non lexer specific
styles.
"""
faces = self.GetFaces()
default = "face:%(font)s,size:%(size)d" % faces
self.StyleSetSpec(stc.STC_STYLE_DEFAULT, default)
line = "back:#C0C0C0," + default
self.StyleSetSpec(stc.STC_STYLE_LINENUMBER, line)
self.StyleSetSpec(stc.STC_STYLE_CONTROLCHAR,
"face:%(font)s" % faces)

Now here we will derive a new class from our CodeEditorBase class that specializes the control for Python files:

class PythonCodeEditor(CodeEditorBase):
def __init__(self, parent):
super(PythonCodeEditor, self).__init__(parent)

# Setup
self.SetLexer(wx.stc.STC_LEX_PYTHON)
self.SetupKeywords()
self.SetupStyles()
self.EnableLineNumbers(True)

def SetupKeywords(self):
"""Sets up the lexers keywords"""
kwlist = u" ".join(keyword.kwlist)
self.SetKeyWords(0, kwlist)

def SetupStyles(self):
"""Sets up the lexers styles"""
# Python styles
faces = self.GetFaces()
fonts = "face:%(font)s,size:%(size)d" % faces
default = "fore:#000000," + fonts
# Default
self.StyleSetSpec(stc.STC_P_DEFAULT, default)
# Comments
self.StyleSetSpec(stc.STC_P_COMMENTLINE,
"fore:#007F00," + fonts)
# Number
self.StyleSetSpec(stc.STC_P_NUMBER,
"fore:#007F7F," + fonts)
# String
self.StyleSetSpec(stc.STC_P_STRING,
"fore:#7F007F," + fonts)
# Single quoted string
self.StyleSetSpec(stc.STC_P_CHARACTER,
"fore:#7F007F," + fonts)
# Keyword
self.StyleSetSpec(stc.STC_P_WORD,
"fore:#00007F,bold," + fonts)
# Triple quotes
self.StyleSetSpec(stc.STC_P_TRIPLE,
"fore:#7F0000," + fonts)
# Triple double quotes
self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE,
"fore:#7F0000," + fonts)
# Class name definition
self.StyleSetSpec(stc.STC_P_CLASSNAME,
"fore:#0000FF,bold," + fonts)
# Function or method name definition
self.StyleSetSpec(stc.STC_P_DEFNAME,
"fore:#007F7F,bold," + fonts)
# Operators
self.StyleSetSpec(stc.STC_P_OPERATOR, "bold," + fonts)
# Identifiers
self.StyleSetSpec(stc.STC_P_IDENTIFIER, default)
# Comment-blocks
self.StyleSetSpec(stc.STC_P_COMMENTBLOCK,
"fore:#7F7F7F," + fonts)
# End of line where string is not closed
eol_style = "fore:#000000,back:#E0C0E0,eol," + fonts
self.StyleSetSpec(stc.STC_P_STRINGEOL, eol_style)

How it works...

We created two classes: a base editor class and a specialized class for Python source files. Let's first start by taking a look at the CodeEditorBase class.

The CodeEditorBase sets up the basic functionality of the control, and is just there to encapsulate some of the common items, should we decide to add other specialized classes for different types of source files later on.

First and foremost, it initializes the basic window styles and provides font information. The StyledTextCtrl has a number of style specifications for styling different text in the buffer. These styles are specified using the StyleSetSpec method, which takes the style ID and style specification string as arguments. The style IDs that are generic to all lexers are identified with the STC_STYLE_ prefix. The style specification string is formatted in the following way:

ATTRIBUTE:VALUE,ATTRIBUTE:VALUE,MODIFIER

Here, ATTRIBUTE and VALUE are replaced by any combination of the possible specifications in the following table:

wxPython 2.8: Advanced Building Blocks of a User Interface

There is also support from some additional MODIFER attributes that don't take a VALUE argument:

wxPython 2.8: Advanced Building Blocks of a User Interface

The StyledTextCtrl also supports special margins on the left-hand side of the buffer for displaying things such as line numbers, breakpoints, and code folding buttons. Our CodeEditorBase shows how to enable line numbers in the left-most margin with its EnableLineNumbers method.

Our derived PythonCodeEditor class simply does the three basic things necessary to set up the proper lexer:

  1. First, it calls SetLexer to set the lexer mode. This method simply takes one of the STC_LEX_FOO values that are found in the stc module.
  2. Second, it sets up the keywords for the lexer. There is little documentation on what keyword sets are available for each lexer, so it is sometimes necessary to look at the Scintilla source code to see what keyword sets have been defined for each lexer. The Python lexer supports two keyword sets: one for language keywords and a second for user- defined keywords. The SetKeywords method takes two arguments: a keyword ID and a string of space-separated keywords to associate with that ID. Each keyword ID is associated with a Style ID for the given lexer. In this example, the keyword ID of zero is associated with the Style ID: STC_P_WORD.
  3. Third and finally, it sets all of the styling specifications for the lexer. This is done just as we did in our base class by calling StyleSetSpec for each lexer style specification ID that the lexer defines. A quick reference for what styles relate to what lexers can be found in the wxPython wiki (http://wiki.wxpython.org/StyledT extCtrl%20Lexer%20Quick%20Reference).

There's more...

The StyledTextCtrl is a big class that comes with a very large API. It has many additional features that we did not discuss here, such as pop-up lists for implementing auto-completion, clickable hotspots, code folding, and custom highlighting. Following are links to some references and documentation about the StyledTextCtrl:

Working with tray icons

Tray icons are UI components that integrate with the window manager's Task Bar (Windows / Linux) or Dock (OS X). They can be used for notifications and to provide a pop-up menu when the user clicks on the notification icon. This recipe shows how to create and use a tray icon through the use of the TaskBarIcon class.

How to do it...

To create the icon bar for this recipe's sample code, we create a subclass of TaskBarIcon that loads an image to use for its display, and that has handling for showing a Menu when it is clicked on:

class CustomTaskBarIcon(wx.TaskBarIcon):
ID_HELLO = wx.NewId()
ID_HELLO2 = wx.NewId()
def __init__(self):
super(CustomTaskBarIcon, self).__init__()

# Setup
icon = wx.Icon("face-monkey.png", wx.BITMAP_TYPE_PNG)
self.SetIcon(icon)

# Event Handlers
self.Bind(wx.EVT_MENU, self.OnMenu)

def CreatePopupMenu(self):
"""Base class virtual method for creating the
popup menu for the icon.
"""
menu = wx.Menu()
menu.Append(CustomTaskBarIcon.ID_HELLO, "HELLO")
menu.Append(CustomTaskBarIcon.ID_HELLO2, "Hi!")
menu.AppendSeparator()
menu.Append(wx.ID_CLOSE, "Exit")
return menu

def OnMenu(self, event):
evt_id = event.GetId()
if evt_id == CustomTaskBarIcon.ID_HELLO:
wx.MessageBox("Hello World!", "HELLO")
elif evt_id == CustomTaskBarIcon.ID_HELLO2:
wx.MessageBox("Hi Again!", "Hi!")
elif evt_id == wx.ID_CLOSE:
self.Destroy()
else:
event.Skip()

How it works...

The TaskBarIcon class is pretty easy and straightforward to use. All that needs to be done is to create an icon and call SetIcon to create the UI part of the object that will be shown in the system tray. Then we override the CreatePopupMenu method that the base class will call when the icon is clicked on, to create the menu. All that this method needs to do is create a Menu object and then return it; the TaskBarIcon class will take care of the rest. Finally, we added an event handler for EVT_MENU to handle the menu events from our pop-up menu.

There's more...

The TaskBarIcon class has a number of events associated with it, if you want to customize what different types of clicks do. Please see the following table for a list of available events and a description of when they are called.

wxPython 2.8: Advanced Building Blocks of a User Interface

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

(For more resources on wxPython, see here.)

Adding tabs to a Notebook

The Notebook class is a container control that is used to manage multiple panels through the use of tabs. When a tab is selected, the associated panel is shown and the previous one is hidden. This recipe shows how to use the default native Notebook class to create a tab-based user interface like the one shown in the following screenshot:

wxPython 2.8: Advanced Building Blocks of a User Interface

How to do it...

T he following example code snippet defines a Notebook class that has three tabs in it:

class MyNotebook(wx.Notebook):
def __init__(self, parent):
super(MyNotebook, self).__init__(parent)

# Attributes
self.textctrl = wx.TextCtrl(self, value="edit me",
style=wx.TE_MULTILINE)
self.blue = wx.Panel(self)
self.blue.SetBackgroundColour(wx.BLUE)
self.fbrowser = wx.GenericDirCtrl(self)

# Setup
self.AddPage(self.textctrl, "Text Editor")
self.AddPage(self.blue, "Blue Panel")
self.AddPage(self.fbrowser, "File Browser")

How it works...

This recipe just shows the fundamental basics of how to use the Notebook control. We simply created some window objects that we wish to put in the Notebook. In this case, we created three different objects: a TextCtrl, a Panel, and a GenericDirCtrl. The important thing to note is that the items that we wish to put in the Notebook must be children of the Notebook.

The objects are then added to the Notebook by calling its AddPage method. This method takes a window object and a label to put on the tab as arguments.

There's more...

The basic Notebook class doesn't offer too many more features beyond what was shown above. However, there are a few additional styles, and some events related to when tabs are selected. Included below are some quick references to these additional items.

Styles

The following styles can be provided to the Notebook's constructor:

wxPython 2.8: Advanced Building Blocks of a User Interface

Events

The Notebook emits the following events:

wxPython 2.8: Advanced Building Blocks of a User Interface

Using the FlatNotebook

The FlatNotebook class is a custom Notebook implementation that provides a large array of features over the default Notebook. The additional features include such things as being able to have a close button on each tab, drag and drop tabs to different positions, and a number of different tab styles to change the look and feel of the control. This recipe will explore some of the extended functionality that this control provides.

How to do it...

As an example of how to use the FlatNotebook, we will define a subclass that has some specializations for displaying multiple TextCtrls:

import wx
import wx.lib
import wx.lib.flatnotebook as FNB

class MyFlatNotebook(FNB.FlatNotebook):
def __init__(self, parent):
mystyle = FNB.FNB_DROPDOWN_TABS_LIST|\
FNB.FNB_FF2|\
FNB.FNB_SMART_TABS|\
FNB.FNB_X_ON_TAB
super(MyFlatNotebook, self).__init__(parent,
style=mystyle)
# Attributes
self._imglst = wx.ImageList(16, 16)

# Setup
bmp = wx.Bitmap("text-x-generic.png")
self._imglst.Add(bmp)
bmp = wx.Bitmap("text-html.png")
self._imglst.Add(bmp)
self.SetImageList(self._imglst)

# Event Handlers
self.Bind(FNB.EVT_FLATNOTEBOOK_PAGE_CLOSING,
self.OnClosing)

def OnClosing(self, event):
"""Called when a tab is closing"""
page = self.GetCurrentPage()
if page and hasattr(page, "IsModified"):
if page.IsModified():
r = wx.MessageBox("Warning unsaved changes"
" will be lost",
"Close Warning",
wx.ICON_WARNING|\
wx.OK|wx.CANCEL)
if r == wx.CANCEL:
event.Veto()

In the following screenshot, we can see the above subclass in action, in a simple file editor application. The full source code for the below application is available with the code that accompanies this recipe.

wxPython 2.8: Advanced Building Blocks of a User Interface

How it works...

T his little recipe demonstrates quite a few of the features that the FlatNotebook offers over the standard Notebook class. So let's break it down, section by section, starting with the constructor.

In our subclass's constructor we specified four style flags. The first, FNB_DROPDOWN_TAB_ LIST, specifies that we want to have a drop-down list that shows all the open tabs. The dropdown list is the small down-arrow button: clicking on it will show a pop-up menu that allows one of the currently open tabs to be selected from the list. The second style flag, FNB_FF2, specifies that we want tabs that use the Firefox 2 tab renderer, which will draw tabs that look and feel similar to the ones in Firefox 2. The third style flag, FNB_SMART_TABS, specifies that the Ctrl + Tab shortcut will pop up a dialog that shows the open tabs and allows them to be cycled through by pressing the Tab key. The fourth and final style flag that we used, FNB_X_ ON_TAB, specifies that we want a close button to be shown on the active tab. This allows the user to dismiss a tab when this button is clicked on.

In order to be able to show icons on the tabs, we also created and assigned an ImageList to the control. An ImageList is simply a container for holding Bitmap objects, and the control will use it for retrieving the bitmap data when it draws the tabs. The important point to notice is that we keep a reference to the object by assigning it to self._imglst; it is important to keep a reference to it so that it doesn't get garbage collected.

The last thing that we did was Bind the control to the page closing event EVT_FLATNOTEBOOK_ PAGE_CLOSING. In this example, we are expecting our pages to provide an IsModified method so that we can check for unsaved changes prior to closing the page, in order to give the user a chance to cancel closing the page.

There's more...

Because the FlatNotebook is a pure Python class, it is more customizable than the basic Notebook class. Included below is a listing of the style flags that can be used to customize the appearance and behavior of the control:

Style flags

Here is a list of the other style flags that are available that we didn't already cover:

wxPython 2.8: Advanced Building Blocks of a User Interface

Scrolling with a ScrolledPanel

The ScrolledPanel class is a custom Panel class that has built in ScrollBars. This class is provided by the scrolledpanel module in wx.lib. By default, Panels do not have the ability to scroll when their contents overflow the windows given area. This recipe shows how to use the ScrolledPanel, by using it to create a custom image list widget.

How to do it...

To create our custom image viewer control that uses the ScrolledPanel, we will define this simple class that manages a list of Bitmaps:

import wx
import wx.lib.scrolledpanel as scrolledpanel

class ImageListCtrl(scrolledpanel.ScrolledPanel):
"""Simple control to display a list of images"""
def __init__(self, parent, bitmaps=list(),
style=wx.TAB_TRAVERSAL|wx.BORDER_SUNKEN):
super(ImageListCtrl, self).__init__(parent,
style=style)
# Attributes
self.images = list()
self.sizer = wx.BoxSizer(wx.VERTICAL)

# Setup
for bmp in bitmaps:
self.AppendBitmap(bmp)
self.SetSizer(self.sizer)

def AppendBitmap(self, bmp):
"""Add another bitmap to the control"""
self.images.append(bmp)
sbmp = wx.StaticBitmap(self, bitmap=bmp)
self.sizer.Add(sbmp, 0, wx.EXPAND|wx.TOP, 5)
self.SetupScrolling()

How it works...

The ScrolledPanel makes it pretty easy to work with ScrollBars, so let's take a quick look at how it works.

We created a simple class called ImageListCtrl. This control can be used for displaying a list of bitmaps. We derived our class from ScrolledPanel so that if it contains many images, the user will be able to scroll to see them all. The only special thing needed to use the ScrolledPanel is to call its SetupScrolling method when all of the panels' child controls have been added to its Sizer. Typically, this is done in the subclasses __init__ method, but since our widget can add more Bitmap items at any time we need to call it after each Bitmap that is added in the AppendBitmap method.

The SetupScrolling method works by calculating the minimum size of the contents of the Panel and setting up the virtual size of the containment area for the ScrollBar objects to work with.

Simplifying the FoldPanelBar

The FoldPanelBar is a custom container class that allows multiple controls to be grouped together into FoldPanelItem controls that allow them to be expanded or contracted by clicking on its CaptionBar. The FoldPanelBar doesn't work with layouts based on a Sizer and as such its API can get a little cumbersome, because it requires you to add each control one by one and set its layout by using various flags. This recipe shows how to create a custom FoldPanelBar that works with Panel objects. This class will allow for you to modularize your code into Panel classes and then just add them to the FoldPanelBar instead of directly adding everything to the FoldPanelBar itself.

How to do it...

This custom FoldPanelBar class uses a factory approach to simplify and abstract the addition of a new Panel to the control:

import wx
import wx.lib.foldpanelbar as foldpanel

class FoldPanelMgr(foldpanel.FoldPanelBar):
"""Fold panel that manages a collection of Panels"""
def __init__(self, parent, *args, **kwargs):
super(FoldPanelMgr, self).__init__(parent,
*args,
**kwargs)
def AddPanel(self, pclass, title=u"", collapsed=False):
"""Add a panel to the manager
@param pclass: Class constructor (callable)
@keyword title: foldpanel title
@keyword collapsed: start with it collapsed
@return: pclass instance
"""
fpitem = self.AddFoldPanel(title, collapsed=collapsed)
wnd = pclass(fpitem)
best = wnd.GetBestSize()
wnd.SetSize(best)
self.AddFoldPanelWindow(fpitem, wnd)
return wnd

How it works...

Our subclass of FoldPanelBar, adds one new method to the class AddPanel. The AddPanel method is a simple wrapper around the FoldPanelBar control's AddFoldPanel and AddFoldPanelWindow methods. The AddFoldPanel method is used to create the CaptionBar and container for the controls and the AddFoldPanelWindow method is used to add a Window object to the FoldPanel.

Our AddPanel method takes a callable object as its first parameter. The callable must accept a "parent" argument and return a new window that is a child of that parent window when called. We do this because our panels need to be created as children of the FoldPanelItem that is returned by AddFoldPanel. This is an important point to remember when working with the FoldPanelBar. All of the controls that are added to it must be children of one of its FoldPanelItems and not children of the FoldPanelBar itself.

Since the FoldPanelBar internally works with a manual layout, we need to set an explicit size on each Panel as it is added. This is done by getting the best size from each Panel object's GetBestSize method.

There's more...

The CaptionBar of the FoldPanelBar can be customized by making a custom CaptionBarStyle object and passing it to the AddFoldPanel method. A CaptionBarStyle object has methods for changing the colors, fonts, and styles that the CaptionBar will use. The AddFoldPanel method also accepts an optional foldIcons argument, which accepts an ImageList object that must have two 16x16 pixel bitmaps in it. The first will be used for the button's expanded state and the second will be used for its collapsed state.

Summary

This article introduced you to some of the advanced widgets available in the wxPython control library.


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