wxPython 2.8: Advanced Building Blocks of a User Interface

0
139
10 min read

 

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

LEAVE A REPLY

Please enter your comment!
Please enter your name here