In this chapter, we will take a quick overview on getting started with wxPython, including how to get an app started as well as handling events and supporting basic integration with various operating system features for the environments that the application may be operated in. These concepts are used throughout the recipes in this book as well as in any wxPython application you may develop. The recipes throughout this book target wxPython 3.0 running on Python 2.7. Many features exist and work in earlier versions of wxPython as well, but your mileage may vary with the recipes in this book when using a version earlier than 3.0.
The App
object is an object that all wxPython applications must create before any other GUI object. This object creates the application and provides its main event loop, which is used to dispatch events and connect actions in the UI with the actions in your programs.
This recipe will introduce how to create a minimal wxPython application, which will be used as foundation for every other recipe in this book.
Make the script as follows:
import wx class MyApp(wx.App): def OnInit(self): wx.MessageBox("Hello wxPython", "wxApp") return True if __name__ == "__main__": app = MyApp(False) app.MainLoop()
Run the script and take a look at the result:
There are three things to take note of in this simple application: the first, we created a subclass of the wx.App
object; the second, we overrode the OnInit
method; and the third, we called the MainLoop
method of the application object. These simple steps set up the base for any application.
The OnInit
method is called by the application's MainLoop
method when it is started and provides an entry point to start up the main logic and user interface of your application. In this example, we just used it to show a simple pop-up dialog box. The application's MainLoop
method continues to run until the last window associated with the application is closed. The OnInit
method must return true
in order to continue the initialization of the MainLoop
applications.
The MainLoop
method processes and dispatches all the messages that are needed to present the UI and direct messages for user actions initiated with button clicks. When the OK button is clicked on the dialog, it sends a message that is dispatched by the MainLoop
method to close the dialog. In this example, once the dialog has returned, OnInit
will also return, and there will be no window objects remaining. So, the application's MainLoop
method will return as well, and this script will exit.
Though generally the wx.App
object is created as we did in this example, the class constructor also has four optional keyword arguments that can be used to modify some of its behavior:
wx.App(redirect=False, filename=None, useBestVisual=False, clearSigInt=True)
The four optional keyword arguments are as follows:
redirect
: If set toTrue
,stdout
is redirected to a debug windowfilename
: If redirect isTrue
and this is notNone
, thenstdout
can be redirected to a file specified by this argumentuseBestVisual
: This specifies whether the application should try to use the best visuals provided by the underlying toolkit. (This has no effect on most systems.)clearSigInt
: Setting this toTrue
will allow the application to be terminated by pressing Ctrl+C from the command line.
The Handling errors gracefully recipe in Chapter 10, Getting Your Application Ready for Release, provides additional information on methods that can be overridden in
wx.App
.
Most applications have some sort of main window that they want to show to allow their users to interact with the software. In wxPython, this window is called a frame. The frame is the main top-level container and the base for building most user interfaces in wxPython. This recipe will show how to create a frame and add it to an application.
You can do this by performing the following steps:
Start by making a subclass of
wx.Frame
with the following code:class MyFrame(wx.Frame): def __init__(self, parent, title=""): super(MyFrame, self).__init__(parent, title=title) # Set an application icon self.SetIcon(wx.Icon("appIcon.png")) # Set the panel self.panel = wx.Panel(self)
Next, create an instance of the frame and show it using the following code:
class MyApp(wx.App): def OnInit(self): self.frame = MyFrame(None, title="Main Frame") self.frame.Show() return True
Run the script and take a look at what we made:
The Frame
class creates a top-level window that can be used to present any number of other controls. A frame can be created without any parent window and will remain in the application until it is dismissed by the user.
In this recipe, we set up a couple of items on the MyFrame
class:
We called
SetIcon
to set the custom application icon on the title bar of the frame. This icon was created from theappIcon.png
file, which exists in the same directory as the script.We created a
Panel
object and set the frame as its parent object. A panel is a plain rectangular control used to contain other controls and is shown as the rectangular area inside the frame's borders. A panel must have a parent window in order to be created.
Finally, in the App
object's OnInit
method, we created an instance of the frame specifying the title that we wanted to show in the frame's title bar and then called its Show
method to display it on the screen. This recipe can be used as the preparation to create any wxPython application.
The wx.Frame
constructor has several style flags that can be specified in its constructor to modify its behavior and appearance.
These style flags can be passed in any combination using a bitwise operator to turn off any of the features that you may not want to provide on all frames. Multiple flags can be combined using a bitwise or operation.
Bitmaps are the basic data type used to represent images in an application. The wx.Bitmap
object can seamlessly load and decompress most common image file formats into a common representation that is usable by many UI controls. Adding bitmaps to the controls can make a UI more intuitive and easier to use.
Perform the following steps:
Let's start this time by making a subclass of
wx.Panel
to use as the container for the control that will show our bitmap on the screen, as follows:class ImagePanel(wx.Panel): def __init__(self, parent): super(ImagePanel, self).__init__(parent) # Load the image data into a Bitmap theBitmap = wx.Bitmap("usingBitmaps.png") # Create a control that can display the # bitmap on the screen. self.bitmap = wx.StaticBitmap(self, bitmap=theBitmap)
Next, we will create an instance of the panel in a frame:
class MyFrame(wx.Frame): def __init__(self, parent, title=""): super(MyFrame, self).__init__(parent, title=title) # Set the panel self.panel = ImagePanel(self)
Run it and see the image shown on the panel:
The Bitmap
class is used to load image data from the usingBitmaps.png
file, which is located in the same directory as the script. This loads the PNG file data into a bitmap object, which can be used by the controls.
In this example, we used the StaticBitmap
control, which is one of the easiest ways to display a bitmap in the UI. This control takes the bitmap data object and handles drawing it on the screen.
wxPython is an event-driven framework; this means that all actions and the running of the UI is driven by events. Events are fired by objects to indicate that something has happened or needs to happen. MainLoop
then dispatches these events to callback methods that are registered to be notified of the event. This recipe will show how to bind callback functions to events.
Perform the following functions:
First, start by creating a frame and binding to some of its events with the following code:
class MyApp(wx.App): def OnInit(self): self.frame = wx.Frame(None, title="Binding Events") # Bind to events we are interested in self.frame.Bind(wx.EVT_SHOW, self.OnFrameShow) self.frame.Bind(wx.EVT_CLOSE, self.OnFrameExit) # Show the frame self.frame.Show() return True
Next, define the event handler callback methods we specified in the
Bind
calls. These will get executed when the bound event occurs, as follows:def OnFrameShow(self, event): theFrame = event.EventObject print("Frame (%s) Shown!" % theFrame.Title) event.Skip() def OnFrameExit(self, event): theFrame = event.EventObject print("Frame (%s) is closing!" % theFrame.Title) event.Skip()
In the OnInit
method, we created a frame object and then called Bind
on it two times in order to bind our own two callback methods to these events that the frame emits. In this case, we bound to EVT_SHOW
and EVT_CLOSE
; these two events will be emitted by a window when the window transitions from being hidden to shown on screen and then when it is closed. Binding to events allows us to add some application-specific response when these two events occur. Now, our app's OnFrameShow
and OnFrameExit
callbacks will be executed by the framework in response to the event and allow us to print our log messages.
The first event, EVT_SHOW
, happens as part of when the Show
method is called on the frame in the app's OnInit
method. The other event, EVT_CLOSE
, occurs when the frame's close button is clicked on.
The event handler methods used in Bind
always take one argument, which is an event object. This object is passed into the handler by the framework when it is called. The event object contains information about the event, such as a reference to the object that emitted it and other state information depending on what type of event was emitted.
The Bind
function can also take some additional optional parameters to set more fine-grain control on when the callback should be executed, as follows:
Bind(event, handler, source=None, id=-1, id2=-1)
The arguments to this function are described as follows:
event
: This is the event to bind to.handler
: This is the event handler callback function to bind.source
: This can be used to specify the window object that is the source of the event. If specified, then only when the source object generates the event will the handler be executed. By default, any event of the type that gets to the control will cause the handler to execute.id1
: This is used to specify the source object's ID instead of using the instance.id2
: When specified withid1
, this can be used to specify a range of IDs to bind to.
There are many kinds of events that can be bound to depending on the type of control. The wxPython and wxWidgets online documentation provides a fairly complete list of events that are available for each control in the library. Note that the documentation is based on object hierarchy, so you may have to look to the base classes of an object to find the more general events that many controls share. You can find the documentation at http://wxpython.org/onlinedocs.php.
There are certain rules and requirements to create a user interface; in its most fundamental form, the UI is just a collection of rectangles contained within other rectangles. This recipe will discuss how the hierarchy of controls is linked together.
You need to perform the following steps:
Let's start by defining the top-level window that resides at the top of the hierarchy:
class MyFrame(wx.Frame): def __init__(self, parent, title=""): super(MyFrame, self).__init__(parent, title=title) self.panel = MyPanel(self)
Next, let's define the
Panel
class, which will serve as the general container for user controls and give it a child control through the following code:class MyPanel(wx.Panel): def __init__(self, parent): super(MyPanel, self).__init__(parent) self.button = wx.Button(self, label="Push Me")
All controls have an argument for a parent in their constructor. The parent is the container that the child control belongs to; so, in the first snippet when the MyPanel
object was created, it was passed into the Frame
object as its parent. This caused the panel rectangle to be placed inside of the rectangle of the Frame
object. Then again, inside of the MyPanel
object, a Button
object was created with Panel
as the parent, which instructed the button rectangle to be positioned inside the area owned by Panel
.
There are three layers of containment in the window hierarchy for different categories of control types:
Top-level Windows (Frames and Dialogs): These cannot be contained by any type of container when displayed on screen. They are always at the top of the visual hierarchy.
General Containers (Panels, Notebooks, and so on): These are general container windows that serve the purpose of grouping other controls together and providing layout. They can contain other general containers or controls.
Controls (Buttons, CheckBoxes, ComboBoxes, and so on): These are user controls that cannot contain any other controls. They are the leaves at the bottom of the tree.
When building an application with a user interface, this hierarchy is important to remember as it plays a critical role in how the layout and design of the interface is performed. Most notably, in the middle general containers layer, the nesting and composition of the control layout in combination with event handling can lead to unexpected issues if this hierarchy is forgotten. So, just remember this tree structure when building out your application's interface:
There are two main types of events in wxPython:
Normal events
Command events
Understanding how these events travel through the framework is important to understanding how to develop an application in this event-driven framework. This recipe will develop an example to show how to control the way an event is propagated.
The following steps can help us:
Let's start by creating a panel that has two buttons in it, by creating the following class:
class MyPanel(wx.Panel): def __init__(self, parent): super(MyPanel, self).__init__(parent) sizer = wx.BoxSizer() self.button1 = wx.Button(self, label="Button 1") sizer.Add(self.button1) self.button2 = wx.Button(self, label="Button 2") sizer.Add(self.button2) self.SetSizer(sizer) self.Bind(wx.EVT_BUTTON, self.OnButton)
Next, let's define the event handler for the panel to handle the button events as follows:
def OnButton(self, event): button = event.EventObject print("Button (%s) event at Panel!" % button.Label) if button is self.button1: event.Skip()
In the next layer, let's make a frame to hold the panel and also set it up to catch button events through the following code:
class MyFrame(wx.Frame): def __init__(self, parent, title=""): super(MyFrame, self).__init__(parent, title=title) self.panel = MyPanel(self) self.Bind(wx.EVT_BUTTON, self.OnButton) def OnButton(self, event): button = event.EventObject print("Button (%s) event at Frame!" % button.Label) event.Skip()
Finally, let's do the same thing at the app level:
class MyApp(wx.App): def OnInit(self): self.frame = MyFrame(None, title="Event Propagation") self.frame.Show(); self.Bind(wx.EVT_BUTTON, self.OnButton) return True def OnButton(self, event): button = event.EventObject print("Button (%s) event at App!" % button.Label) event.Skip()
Tip
Downloading the example code
You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.
When you run this application and click on each of the buttons, you should see two distinct differences in behavior between how the events propagate. When clicking on the first button, the event handlers from the panel to the frame and finally to the app will be executed, whereas when clicking the second button, only the event handler at the panel is executed.
The EVT_BUTTON
object is a command event, meaning it will propagate upward until it is stopped or it reaches the app. In this example, the second button is only propagated to the Panel handler because we called event.Skip
when the event originated from the first button. Calling the Skip
method tells the framework to propagate the event to the next handler in the chain, whereas not calling the Skip
method tells the framework that the event has been handled and does not require further processing. So, it's important to know when to call Skip
and when not to as sometimes it is necessary for the event to propagate to the base default handler in order for additional processing to occur. Try going back to the Binding to events recipe and removing the call to Skip
in OnFrameExit
to ensure that it prevents the frame from closing.
As discussed at the beginning of this recipe, there are two types of events. This recipe only explored the more common types of command events that were propagated. Normal events stay local to where they are generated and do not propagate.
You can also create your own custom events if you want to use the event loop to pass messages using the newevent
module in wx.lib
, as follows:
import wx import wx.lib.newevent # Create a special event MyEvent, EVT_MYEVENT = wx.lib.newevent.NewCommandEvent();
This creates a new event object type and event binder object. The EVT_MYEVENT
binder object can be bound to as any other event. Then, the MyEvent
event object can be emitted through the use of the wx.PostEvent
function, which sends the instance of the event through the event handler chain.
The clipboard is a system resource used to pass user data between applications in the operating system. This is most often associated with copying and pasting actions in an application. This recipe will show some basics on putting and getting data from the clipboard.
Let's first define a helper function to get some text from the clipboard, as follows:
def GetClipboardText(): text_obj = wx.TextDataObject() rtext = "" if wx.TheClipboard.IsOpened() or wx.TheClipboard.Open(): if wx.TheClipboard.GetData(text_obj): rtext = text_obj.GetText() wx.TheClipboard.Close() return rtext
Now, let's do the reverse and define a helper function to put text into the clipboard, as done with the following code:
def SetClipboardText(text): data_o = wx.TextDataObject() data_o.SetText(text) if wx.TheClipboard.IsOpened() or wx.TheClipboard.Open(): wx.TheClipboard.SetData(data_o) wx.TheClipboard.Close()
Both functions work by creating TextDataObject
, which provides a platform-independent way to represent the systems' native data format. Then, TheClipboard
object is opened, and it is used to either get data from the clipboard of the given type or put data in the clipboard from the application. This can be boiled down to a simple three step process for any clipboard interaction:
Open clipboard
Set or get
DataObject
Close the clipboard
Closing the clipboard after using is very important; it may prevent other processes from accessing it. The clipboard should only be kept open momentarily.
Many applications allow users to open files by dragging a file from the operating system and dropping it in the application. wxPython, of course, provides support for this as well, through its controls using DropTargets
. This recipe will show how to set up a DropTarget
to allow handling the dragging and dropping of files in an application.
First, let's create a drop target object to accept files that are dragged over and dropped in the application with the following code:
class MyFileDropTarget(wx.FileDropTarget): def __init__(self, target): super(MyFileDropTarget, self).__init__() self.target = target def OnDropFiles(self, x, y, filenames): for fname in filenames: self.target.AppendText(fname)
Next, all that is left is to connect the drop target to the window that should accept the dropped file(s). An example of this is shown in the following code:
class MyFrame(wx.Frame): def __init__(self, parent, title=""): super(MyFrame, self).__init__(parent, title=title) # Set the panel self.text = wx.TextCtrl(self, style=wx.TE_MULTILINE) self.text.AppendText("Drag and drop some files here!") dropTarget = MyFileDropTarget(self.text) self.text.SetDropTarget(dropTarget)
Drag and drop functions with the use of DropSources
and DropTargets
. In this case, we wanted to allow files to be dropped in the application, so FileDropTarget
was created and associated with the TextCtrl
window. DropTargets
have several virtual callback functions that can be overridden to intercept different actions during the drag and drop events. As FileDropTarget
is specialized for files, it only required overriding OnDropFiles
, which is called with the list of filenames that were dropped in the application. It is necessary to subclass the drop target in order to intercept and handle the data it receives.
In order for a window to accept drag and drop actions, it must have a DropTarget
set; the DropTarget
then gives feedback on whether the data can be accepted or not as well as handling the reception of the data. Try out the example code with this recipe and you will see the mouse cursor change as you drag a file over; then, try again by dragging some text from another application to see the difference.
It's also possible to create more application-specific drag and drop handling if needed for custom datatypes by deriving a custom drop target class from PyDropTarget
. This class provides several more overridable methods to allow the handling of various events during the action. Here's a table explaining this:
AppleEvents
are special kinds of high-level system events used by the OS X operating system to pass information between processes. In order to handle system events, such as when a file is dropped in the application's Dock icon, it's necessary to handle AppleEvents
. Implementing the handlers for these methods can allow your app to behave more natively when run on OS X.
Note
This recipe is specific to the OS X operating system and will have no effect on other operating systems.
Perform the following steps:
Define an app object in which we will override the available
AppleEvent
handlers through the following code:class MyApp(wx.App): def OnInit(self): self.frame = MyFrame("Apple Events") self.frame.Show() return True
Override the handlers that are available to deal with the opening of files:
def MacNewFile(self): """Called in response to an open-application event""" self.frame.AddNewFile() def MacOpenFiles(self, fileNames): """Called in response to an openFiles message in Cocoa or an open-document event in Carbon """ self.frame.AddFiles(fileNames)
Finally, let's also override the remaining available
AppleEvent
handlers that are defined inwx.App
and have them redirected to some actions with our application's main window. The following code can help us do this:def MacOpenURL(self, url): """Called in response to get-url event""" self.frame.AddURL(url) def MacPrintFile(self, fileName): """Called in response to a print-document event""" self.frame.PrintFile(fileName) def MacReopenApp(self): """Called in response to a reopen-application event""" if self.frame.IsIconized(): self.frame.Iconize(False) self.frame.Raise()
In the Carbon and Cocoa builds of wxPython, these additional Mac-specific virtual overrides are available for apps to implement. The app object has special handling for these events and turns them into simple function calls that can be overridden in derived applications to provide an app-specific handling of them.
The first two methods that we overrode are called in response to creating a new file or opening existing files, such as when a file is dragged and dropped in the application icon in the dock. The MacOpenFiles
method is new since wxPython 2.9.3 and should be used instead of the MacOpenFile
method that was provided in previous versions.
The other method that most apps should implement in some form is the MacReopenApp
method. This method is called when a user clicks on the application icon in the dock. In this implementation, we ensure that the app is brought back to the foreground in response to this action.
If there are other OS X-specific actions you want your app to handle, it is also possible to add support for additional AppleEvents
to a wxPython application. It is not a particularly easy task as it requires writing a native extension module to catch the event, block the event loop
, and then restore the Python interpreter's state back to wx
after handling the event. There is a pretty good example that can be used as a starting point on the wxPython Wiki page (refer to http://wiki.wxpython.org/Catching%20AppleEvents%20in%20wxMAC) if you find yourself needing to venture down this route.