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.
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.
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))
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.
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)
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.
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.
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
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.
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:
I remember deciding to pursue my first IT certification, the CompTIA A+. I had signed…
Key takeaways The transformer architecture has proved to be revolutionary in outperforming the classical RNN…
Once we learn how to deploy an Ubuntu server, how to manage users, and how…
Key-takeaways: Clean code isn’t just a nice thing to have or a luxury in software projects; it's a necessity. If we…
While developing a web application, or setting dynamic pages and meta tags we need to deal with…
Software architecture is one of the most discussed topics in the software industry today, and…