Well, here's the tutorial like you asked for. I've expanded on it quite a bit. Could people please read over it, in case I've made mistakes? There's a better version at my blog because it features screenshots. Link: http://fuse.baywords.com/wxpython-tutorial/

Tutorial: GUI programming with wxPython

Index
Introduction
Start
Sizers
Putting it in classes
Event handling
Binding the enter key
Creating dropdown menus
Message and file browsing dialogues
Appendix

Introduction
Hi there! So you've made a few scripts in Python. They work really well, you maybe know your way around file IO, string manipulation, and list splicing (and heck maybe even regular expressions and other cool stuff)... but you're thinking "Is that it? Surely there's more to Python." So this is it: GUI programming. Here I explain how to make the move from the command line to full-blown graphical programmes like you see in KDE, Mac or Windows.

GUI programming is fast, self-contained, and simple. So you can code your functions first, or your GUI first - it's up to you. GUI programming is like LEGO: you have a few core blocks and you build everything else from those blocks. There's not much you can't build, and once you understand how those few core blocks work, where they fit, GUI programming will become second nature. There are numerous GUI packages available to use with Python. Tkinter comes with Python, whilst Qt and GTK are used to programme KDE and Gnome for linux, respectively. Which do I suggest you use? wxPython. It's not used by Gnome, KDE, or Windows, but it is, in my opinion, the most 'Pythonic' GUI package. It's powerful and efficient. It also inherits the native look, so you don't need to worry about it not 'fitting in' or looking correct.

To start with, download wxPython here: http://www.wxpython.org/download.php

Windows, Linux/UNIX, and Mac are supported. You might like to note the words of Guido van Rossum, creator of Python:
"wxPython is the best and most mature cross-platform GUI toolkit, given a number of constraints. The only reason wxPython isn't the standard Python GUI toolkit is that Tkinter was there first."

Once you've installed that, load up IDLE or your IDE of choice, and create a new script:

Start

import wx 
# always add this line, or your code won't compile - the wx module contains the GUI code you'll use

"""The start of our wxPython GUI tutorial"""

app = wx.App(redirect=False) # create a wx application

window = wx.Frame(None, title = 'Sample GUI App') # create a window
btn = wx.Button(window) 
# create a button 'widget' on the window - note how the button receives the window. This creates a tree-like structure where the window is the parent node and anything else like buttons are child nodes.

window.Show() # make the frame (and hence button) visible
app.MainLoop() # keep things going

Moderator's note: Some programming editors don't like the extra long comment lines, so run the above code without them ...

import wx 

"""The start of our wxPython GUI tutorial"""

app = wx.App(redirect=False)

window = wx.Frame(None, title = 'Sample GUI App')
btn = wx.Button(window) 

window.Show()
app.MainLoop()

This makes a window. It then puts a button on the window. Yes, the button takes up the whole window. Have a read, understand it, fiddle if you can, then go on. Please compile this code now to confirm it runs.

Note the bit 'redirect=False'. This is so that errors go to the interpreter instead of pop up in their own window. You may want to turn this back to True (or just delete it) later on, but for now you'll likely be encountering errors that crash the programme and require you to kill it to exit - so you want a record of the error. To kill the programme when you've made an error, right-click it in the taskbar, then select close, wait a bit, then press 'End Task' at the prompt. If this isn't working, press ctrl+alt+delete, go to processes and kill pythonw.exe (make sure it's the one that's about 23mb, not the 7mb ones, which are where you're typing your code). Onwards:

import wx

"""Example with custom sizes and positions for widgets and frames."""

app = wx.App(redirect=False)
window = wx.Frame(None, title = 'Sample GUI App',
                  pos = (100,100), size = (400,500))

helloBtn = wx.Button(window, label = 'Hello',
                pos = (400 - 60 - 15, 10), size = (60,25))
byeBtn = wx.Button(window, label = 'Bye',
                pos = (400 - 120 - 15, 10), size = (60,25))
printArea = wx.TextCtrl(window,
                        pos = (10, 10), size = (400 - 120 - 15 - 10, 25),
                        style = wx.TE_READONLY)

window.Show()
app.MainLoop()

Compile and run this code first. Now look at the placement of widgets and what they are. Notice wx.TextCtrl is a text box. It's quite flexible - allowing multiline text with scroll bars, read only mode, write mode, etc. I've set it to read only for what I am about to do with it. You modify many features of a 'widget' by changing or adding things to the 'style' parameter, as above.

Also look at the position and size parameters. Fiddle with them a bit - they move and change the size of the widget. Compile and run, etc. Press the buttons. Note how they do nothing? That's because we need 'event handlers'.

Before I go on to event handlers, I'd like to make this GUI more flexible. For example, press the maximise button (top right). Notice how the programme doesn't expand to fit the screen? Let's fix that. It's fairly simple. Just add an extra 'node' in the tree; put a layer between the window frame and the widgets. It's called a 'sizer':

Comments
You make wxPython sound very interesting
nicely done, easy to understand
Really really good tutorial

Sizers

import wx

"""Example with sizers for dynamic resizing."""

app = wx.App(redirect=False)

window = wx.Frame(None, title = 'Sample GUI App',
                  pos = (100,100), size = (400,500))
background = wx.Panel(window)

loadBtn = wx.Button(background, label = 'Load')
transferBtn = wx.Button(background, label = 'Transfer')
inputArea = wx.TextCtrl(background)
transferArea = wx.TextCtrl(background, style = wx.TE_READONLY | wx.TE_MULTILINE)

horizontalBox = wx.BoxSizer()
horizontalBox.Add(inputArea, proportion = 1, border = 0)
horizontalBox.Add(transferBtn, proportion = 0, border = 0)
horizontalBox.Add(loadBtn, proportion = 0, border = 0)

verticalBox = wx.BoxSizer(wx.VERTICAL)
verticalBox.Add(horizontalBox, proportion = 0, flag = wx.EXPAND, border = 0)
verticalBox.Add(transferArea, proportion = 1, flag = wx.EXPAND, border = 0)

background.SetSizer(verticalBox)
window.Show()
app.MainLoop()

Notice how the 'tree' is preserved? You can eventually trace the buttons and text box back to the parent window through the background widget.

Remember, anything starting with a lower case letter tends to be an arbitrary variable name which you can rename to whatever you want. It is the things with capitals at the start that are important. Conventionally (at least in C++, Java and Haskell), you name variables and functions: thisIsAVariable, and classes: ThisIsAClass. So you could rename 'window' and 'background' to anything you wanted. Same applies to the widget names.

Have a fiddle with that. Sizers are a bit complex at first but you should use them instead of statically defining the size and start co-ords. Note the hierarchy: horizontalBox is a child of verticalBox. Position in the programme depends on location in the code, as you'll see below.

Note I changed the button and textbox names and added a new textbox. Examine the 'style' parameters. One is for input, one is for output. The output box is also multiline - like word wrap. Because the output box isn't for typing in, it was given the read-only style. Note how to combine two styles we used something called "bit-wise OR"? You may know it as a single pipe: |

Resize the window and confirm it works.

Oh, and the wx.VERTICAL value indicates the sizer is vertical. Otherwise the default is horizontal. Proportion=0 means make the widget as small as possible. If a widget had proportion = 1 and another had proportion = 2, then the first widget would take up 1/3 of the space (1+2 = 3) whilst the second would take up 2/3, after the proportion = 0 widgets had been considered.

Now, I want you to try and add TWO more widgets to the application. Add two buttons above inputArea. These two buttons need to be side-by-side, like the load and transfer buttons.

Did it work? Or did you accidentally add them above each-other, like this:

Screenshot: http://img149.imageshack.us/img149/6139/wronglayoutvq5.png

To fully describe the location of a widget in the frame, you need to have one vertical box sizer, and as many horizontal box sizers as you have rows with more than one widget. So if you're adding a row of widgets that are side-by-side, you'll need a new horizontal box sizer:

import wx

"""Example with a 2D grid of sizers of sorts for more complex widget placement.""" 

app = wx.App(redirect=False)

window = wx.Frame(None, title = 'Sample GUI App',
                  pos = (100,100), size = (400,500))
background = wx.Panel(window)

loadBtn = wx.Button(background, label = 'Load')
transferBtn = wx.Button(background, label = 'Transfer')
inputArea = wx.TextCtrl(background)
transferArea = wx.TextCtrl(background, style = wx.TE_READONLY | wx.TE_MULTILINE)

exOneBtn = wx.Button(background, label = 'example one')
exTwoBtn = wx.Button(background, label = 'example two')

horizontalBoxOne = wx.BoxSizer()
horizontalBoxOne.Add(exOneBtn, proportion = 1, border = 0)
horizontalBoxOne.Add(exTwoBtn, proportion = 1, border = 0)

horizontalBoxTwo = wx.BoxSizer()
horizontalBoxTwo.Add(inputArea, proportion = 1, border = 0)
horizontalBoxTwo.Add(transferBtn, proportion = 0, border = 0)
horizontalBoxTwo.Add(loadBtn, proportion = 0, border = 0)

verticalBox = wx.BoxSizer(wx.VERTICAL)
verticalBox.Add(horizontalBoxOne, proportion = 0, flag = wx.EXPAND, border = 0)
verticalBox.Add(horizontalBoxTwo, proportion = 0, flag = wx.EXPAND, border = 0)
verticalBox.Add(transferArea, proportion = 1, flag = wx.EXPAND, border = 0)

background.SetSizer(verticalBox)
window.Show()
app.MainLoop()

Screenshot: http://img237.imageshack.us/img237/2748/rightlayoutuq4.png

Comments
This starting guide in wxPython is great :-)

Putting it in classes

One more thing before event handling... let's make our code more shall we say 'standard':

import wx

# Declare constants in capitals and as global variables at the start of your code
# This makes it much easier to change them later, especially if they are used often.
# It also indicates they are values that won't change throughout the programme's execution.

WINDOW_WIDTH = 400
WINDOW_HEIGHT = 500

class MainFrame(wx.Frame):

    def __init__(self):
        wx.Frame.__init__(self, None, title = 'Sample GUI App',
                          pos = (200,75), size = (WINDOW_WIDTH, WINDOW_HEIGHT))
        
        self.background = wx.Panel(self)

        self.loadBtn = wx.Button(self.background, label = 'Load')
        self.transferBtn = wx.Button(self.background, label = 'Transfer')
        self.inputArea = wx.TextCtrl(self.background)
        self.transferArea = wx.TextCtrl(self.background, style = wx.TE_READONLY | wx.TE_MULTILINE)

        self.horizontalBox = wx.BoxSizer()
        self.horizontalBox.Add(self.inputArea, proportion = 1, border = 0)
        self.horizontalBox.Add(self.transferBtn, proportion = 0, border = 0)
        self.horizontalBox.Add(self.loadBtn, proportion = 0, border = 0)

        self.verticalBox = wx.BoxSizer(wx.VERTICAL)
        self.verticalBox.Add(self.horizontalBox, proportion = 0, flag = wx.EXPAND, border = 0)
        self.verticalBox.Add(self.transferArea, proportion = 1, flag = wx.EXPAND, border = 0)

        self.background.SetSizer(self.verticalBox)
        self.Show()
    
app = wx.App(redirect=False)
window = MainFrame()
app.MainLoop()

I have thrown it all in a class. That's about it. Look over it, compile it, run it, understand it, etc. Note how you can always use classes to seperate parts of your programme. Generally whenever you have a new panel/frame/window, you should put it in its own class. Now on to the good stuff.

Event handling

import wx

WINDOW_WIDTH = 400
WINDOW_HEIGHT = 500

class MainFrame(wx.Frame):
    """A working programme! Event handling, binding, methods, the lot."""
    def __init__(self):
        wx.Frame.__init__(self, None, title = 'Sample GUI App',
                          pos = (200,75), size = (WINDOW_WIDTH, WINDOW_HEIGHT))
        
        self.background = wx.Panel(self)

        self.loadBtn = wx.Button(self.background, label = 'Load')
        self.loadBtn.Bind(wx.EVT_BUTTON, self.loadEvent)
        
        self.transferBtn = wx.Button(self.background, label = 'Transfer')
        self.transferBtn.Bind(wx.EVT_BUTTON, self.transferEvent)
        
        self.inputArea = wx.TextCtrl(self.background)
        self.transferArea = wx.TextCtrl(self.background, style = wx.TE_READONLY | wx.TE_MULTILINE)

        self.horizontalBox = wx.BoxSizer()
        self.horizontalBox.Add(self.inputArea, proportion = 1, border = 0)
        self.horizontalBox.Add(self.transferBtn, proportion = 0, border = 0)
        self.horizontalBox.Add(self.loadBtn, proportion = 0, border = 0)

        self.verticalBox = wx.BoxSizer(wx.VERTICAL)
        self.verticalBox.Add(self.horizontalBox, proportion = 0, flag = wx.EXPAND, border = 0)
        self.verticalBox.Add(self.transferArea, proportion = 1, flag = wx.EXPAND, border = 0)

        self.background.SetSizer(self.verticalBox)
        self.Show()

    def loadEvent(self, event):
        fileContents = open(self.inputArea.GetValue(), 'rb')
        self.transferArea.SetValue(fileContents.read())
        fileContents.close()

    def transferEvent(self, event):
        self.transferArea.SetValue(self.inputArea.GetValue())
    
app = wx.App(redirect=False)
window = MainFrame()
app.MainLoop()

I added two bind commands, one for each button, and two methods to the class. That's it. Have fun fiddling with your own methods!

(The programme transfer whatever you type into the small box, inputArea, into the larger box, transferArea once you press 'Transfer'. Alternatively, you can type a file location into the inputArea and press load and it will load that file into the transferArea.)

Binding the enter key

Maybe you'd like to know how to make a textbox call a certain method when somebody hits the enter key? Add the style wx.TE_PROCESS_ENTER to the textbox's style section, then add a new binding below the textbox: textboxWidget.Bind(wx.EVT_TEXT_ENTER, functionNameHere), like so:

import wx

WINDOW_WIDTH = 400
WINDOW_HEIGHT = 500

class MainFrame(wx.Frame):
    """Now we've added an event handler for somebody hitting the enter key."""
    def __init__(self):
        wx.Frame.__init__(self, None, title = 'Sample GUI App',
                          pos = (200,75), size = (WINDOW_WIDTH, WINDOW_HEIGHT))
        
        self.background = wx.Panel(self)

        self.loadBtn = wx.Button(self.background, label = 'Load')
        self.loadBtn.Bind(wx.EVT_BUTTON, self.loadEvent)
        
        self.transferBtn = wx.Button(self.background, label = 'Transfer')
        self.transferBtn.Bind(wx.EVT_BUTTON, self.transferEvent)
        
        self.inputArea = wx.TextCtrl(self.background, style=wx.TE_PROCESS_ENTER)
        self.inputArea.Bind(wx.EVT_TEXT_ENTER, self.transferEvent)
        
        self.transferArea = wx.TextCtrl(self.background, style = wx.TE_READONLY | wx.TE_MULTILINE)

        self.horizontalBox = wx.BoxSizer()
        self.horizontalBox.Add(self.inputArea, proportion = 1, border = 0)
        self.horizontalBox.Add(self.transferBtn, proportion = 0, border = 0)
        self.horizontalBox.Add(self.loadBtn, proportion = 0, border = 0)

        self.verticalBox = wx.BoxSizer(wx.VERTICAL)
        self.verticalBox.Add(self.horizontalBox, proportion = 0, flag = wx.EXPAND, border = 0)
        self.verticalBox.Add(self.transferArea, proportion = 1, flag = wx.EXPAND, border = 0)

        self.background.SetSizer(self.verticalBox)
        self.Show()

    def loadEvent(self, event):
        fileContents = open(self.inputArea.GetValue(), 'rb')
        self.transferArea.SetValue(fileContents.read())
        fileContents.close()

    def transferEvent(self, event):
        self.transferArea.SetValue(self.inputArea.GetValue())
    
app = wx.App(redirect=False)
window = MainFrame()
app.MainLoop()

Whenever you type something into the top textbox, then hit enter, it transfers it to the bottom one - just like if you'd hit the 'Transfer' button.

Creating dropdown menus

import wx

WINDOW_WIDTH = 400
WINDOW_HEIGHT = 500

class MainFrame(wx.Frame):
    """We've finally added menubars."""
    def __init__(self):
        wx.Frame.__init__(self, None, title = 'Sample GUI App',
                          pos = (200,75), size = (WINDOW_WIDTH, WINDOW_HEIGHT))

        # Menubar.  
        self.menuBar = wx.MenuBar()
        self.menuFile = wx.Menu() # Create a dropdown menu for 'File'
        self.menuInfo = wx.Menu() # Create a dropdown menu for 'Info'
        self.SetMenuBar(self.menuBar) # Tell the main frame what menubar to use.

        self.menuBar.Append(self.menuFile, '&File') # Add a menu.
        
        self.loadItem = self.menuFile.Append(-1, '&Load') # Add an item to the menu.
        self.Bind(wx.EVT_MENU, self.loadEvent, self.loadItem)
        # Bind an event to the menu item. Note how for menu items the Bind format is different?
        # I specify the widget to be bound as the last parameter, not just before 'Bind', as I usually do.
        # You can use this version for any binding you do, if you want. It's necessary for menus, though.
       
        self.exitItem = self.menuFile.Append(-1, 'E&xit')
        self.Bind(wx.EVT_MENU, self.exitEvent, self.exitItem)
        
        self.menuBar.Append(self.menuInfo, '&Info')
        # Add another menu. Note how their order on the menubar depends on the order they appear in your code?
        
        self.aboutItem = self.menuInfo.Append(-1, '&About')
        self.Bind(wx.EVT_MENU, self.aboutEvent, self.aboutItem)
        
        self.helpItem = self.menuInfo.Append(-1, '&Help')
        self.Bind(wx.EVT_MENU, self.helpEvent, self.helpItem)

        # Start of sizers and widgets contained within.
        self.background = wx.Panel(self)
        
        self.transferBtn = wx.Button(self.background, label = 'Transfer')
        self.transferBtn.Bind(wx.EVT_BUTTON, self.transferEvent)
        
        self.inputArea = wx.TextCtrl(self.background, style=wx.TE_PROCESS_ENTER)
        self.inputArea.Bind(wx.EVT_TEXT_ENTER, self.transferEvent)
        
        self.transferArea = wx.TextCtrl(self.background, style = wx.TE_READONLY | wx.TE_MULTILINE)

        self.horizontalBox = wx.BoxSizer()
        self.horizontalBox.Add(self.inputArea, proportion = 1, border = 0)
        self.horizontalBox.Add(self.transferBtn, proportion = 0, border = 0)

        self.verticalBox = wx.BoxSizer(wx.VERTICAL)
        self.verticalBox.Add(self.horizontalBox, proportion = 0, flag = wx.EXPAND, border = 0)
        self.verticalBox.Add(self.transferArea, proportion = 1, flag = wx.EXPAND, border = 0)

        self.background.SetSizer(self.verticalBox)
        self.Show()

    def loadEvent(self, event):
        print "placeholder"

    def exitEvent(self, event):
        print "placeholder"

    def helpEvent(self, event):
        print "placeholder"

    def aboutEvent(self, event):
        print "placeholder"

    def transferEvent(self, event):
        self.transferArea.SetValue(self.inputArea.GetValue())
    
app = wx.App(redirect=False)
window = MainFrame()
app.MainLoop()

Now, the 'Load' button is gone, and I've added a menu bar with 2 menus each with 2 menu items. Each menu item does something, but it's only to print 'placeholder'. Let's give them a real purpose:

Message and file browsing dialogues

The widgets we need are wx.FileDialog and wx.MessageDialog. Now, because we only want it when we go to 'File' -> 'Load' on the menubar, we will actually create it in the 'Load' event method. Similarly, I will expand the event methods of 'Exit', 'Help', and 'About'.

import wx
import os

WINDOW_WIDTH = 400
WINDOW_HEIGHT = 500

class MainFrame(wx.Frame):
    """We've finally added menubars."""
    def __init__(self):
        wx.Frame.__init__(self, None, title = 'Sample GUI App',
                          pos = (200,75), size = (WINDOW_WIDTH, WINDOW_HEIGHT))

        # Menubar.  
        self.menuBar = wx.MenuBar()
        self.SetMenuBar(self.menuBar)
        self.menuFile = wx.Menu()
        self.menuInfo = wx.Menu()

        self.menuBar.Append(self.menuFile, '&File')

        self.loadItem = self.menuFile.Append(-1, '&Load')
        self.Bind(wx.EVT_MENU, self.loadEvent, self.loadItem)
       
        self.exitItem = self.menuFile.Append(-1, 'E&xit')
        self.Bind(wx.EVT_MENU, self.exitEvent, self.exitItem)
        
        self.menuBar.Append(self.menuInfo, '&Info')
        
        self.aboutItem = self.menuInfo.Append(-1, '&About')
        self.Bind(wx.EVT_MENU, self.aboutEvent, self.aboutItem)
        
        self.helpItem = self.menuInfo.Append(-1, '&Help')
        self.Bind(wx.EVT_MENU, self.helpEvent, self.helpItem)

        # Start of sizers and widgets contained within.
        self.background = wx.Panel(self)
      
        self.transferBtn = wx.Button(self.background, label = 'Transfer')
        self.transferBtn.Bind(wx.EVT_BUTTON, self.transferEvent)
        
        self.inputArea = wx.TextCtrl(self.background, style=wx.TE_PROCESS_ENTER)
        self.inputArea.Bind(wx.EVT_TEXT_ENTER, self.transferEvent)
        
        self.transferArea = wx.TextCtrl(self.background, style = wx.TE_READONLY | wx.TE_MULTILINE)

        self.horizontalBox = wx.BoxSizer()
        self.horizontalBox.Add(self.inputArea, proportion = 1, border = 0)
        self.horizontalBox.Add(self.transferBtn, proportion = 0, border = 0)

        self.verticalBox = wx.BoxSizer(wx.VERTICAL)
        self.verticalBox.Add(self.horizontalBox, proportion = 0, flag = wx.EXPAND, border = 0)
        self.verticalBox.Add(self.transferArea, proportion = 1, flag = wx.EXPAND, border = 0)

        self.background.SetSizer(self.verticalBox)
        self.Show()

    def loadEvent(self, event):
        """Create a load dialogue box, load a file onto transferArea, then destroy the load box."""
        loadBox = wx.FileDialog(self, message="Open",
                                defaultDir=os.getcwd(), defaultFile="", style=wx.OPEN)
        if loadBox.ShowModal() == wx.ID_OK: # When the user clicks 'Open', do this:
            fileName = loadBox.GetPath()
            target = open(fileName, 'rb')
            self.transferArea.SetValue(target.read())
            target.close()
        loadBox.Destroy()
        # Note how I destroy the load box now so I can return to the programme.

    def exitEvent(self, event):
        """Exit programme."""
        self.Destroy()

    def helpEvent(self, event):
        """Writes help information to the transferArea."""
        self.transferArea.SetValue("""You could type a help file for the user here.""")

    def aboutEvent(self, event):
        """Pops up a little message box that tells you stuff."""
        aboutInfo = """Hello there!
I am a person, and I coded this programme."""
        aboutBox = wx.MessageDialog(self, message=aboutInfo, caption='About',
                                    style = wx.ICON_INFORMATION | wx.STAY_ON_TOP | wx.OK)
        if aboutBox.ShowModal() == wx.ID_OK: # Until the user clicks OK, show the message
            aboutBox.Destroy()
            
    def transferEvent(self, event):
        """Moves data in inputArea onto transferArea."""
        self.transferArea.SetValue(self.inputArea.GetValue())
    
app = wx.App(redirect=False)
window = MainFrame()
app.MainLoop()

Note how I imported the module os, aswell. Also notice how the 'Exit' item in the menubar now closes the programme (the X still closes it, too).

The end result: http://img291.imageshack.us/img291/711/finalthingwy1.png

The end result: http://img291.imageshack.us/img291/711/finalthingwy1.png

If you have any further inquiries, do a google search, read a few forum threads, and read the documentation for the method or class in question. Feel free to ask me (and others) here, too, but try and make it a last resort - you learn more through trial and error, generally.

Also, if you have any corrections, tips, or suggestions for this tutorial, please leave a comment in this thread, or on my blog: http://fuse.baywords.com/wxpython-tutorial/

Appendix

Widgets
wx.TextCtrl(parent, style=?)
Standard GUI textbox.
Styles:
wx.TE_PROCESS_ENTER
-- When the user hits the enter key, a method is called. The method called depends on the binding, as in Binding the enter key above.

wx.TE_MULTILINE
-- Textbox spans multiple lines, starting a new line when the sentence would otherwise go off-screen. Has a vertical scrollbar by default.

wx.HSCROLL
-- Create a horizontal scrollbar. I believe this works for some other widgets (hence no 'TE' prefix), but haven't tested it.

wx.TE_READONLY
-- Textbox cannot be typed in, but may still be written to by calling widgetName.SetValue(valueHere)

The are more. If you know what they are, please post them in this thread so that a moderator can update this section! I can't find them in the API... no surprise there.

wx.Button(parent, label='?')
Standard GUI button.
Styles: ?

wx.SearchCtrl(parent, style=?)
This class inherits wx.TextCtrl, so generally all the stuff (e.g. styles) you can do with that applies here.
This widget can have a dropdown menu added to it. There is a semi-decent explanation here: http://www.wxpython.org/docs/api/wx.SearchCtrl-class.html
I personally use this class for the SetDescriptiveText("text here") method that allows you to display greyed out text which dissapears when the user clicks and types in the box. You can turn off the left-hand side search icon, too.
Extra styles: ?

wx.MenuBar and wx.Menu
The manu bar at the top left-hand side of almost all programmes in almost all OS'es
Sample use:
# Create menubar
self.menuBar = wx.MenuBar()
# Set it as the programme's menubar
self.SetMenuBar(self.menuBar)
# Create a menu
self.menuFile = wx.Menu()
# Add the menu to the menubar
self.menuBar.Append(self.menuFile, '&File')
# Create and add an item to the menu
self.loadItem = self.menuFile.Append(-1, '&Load')
# Give the item an event handler
self.Bind(wx.EVT_MENU, self.loadEvent, self.loadItem)

wx.MessageDialog
Used for errors, warnings, about, etc.
Styles here: http://www.wxpython.org/docs/api/wx.MessageDialog-class.html

Events
wx.EVT_TEXT_ENTER
wx.EVT_BUTTON
wx.EVT_MENU

There are many more here: http://www.wxpython.org/docs/api/wx.Event-class.html
I believe they are all listed, but navigating to find the event you want is very much a guessing game.

References
Style guide: http://wiki.wxpython.org/wxPython%20Style%20Guide
-- It would be wise to read that style guide. It was written in 2006, so it is relatively up to date, and offers good advice on style to use for your more advanced GUI programming.

vegasat's binding example:
http://www.daniweb.com/forums/post335474-3.html
-- How to place widgets in a class, how to do event handling.

wxPython standard documentation:
http://www.wxpython.org/docs/api/wx-module.html
-- Don't rely on it for anything useful. There's a reason I wrote this guide. ;)

General Python style guide:
http://www.python.org/dev/peps/pep-0008/
-- How you should probably format your code.

http://fuse.baywords.com/wxpython-tutorial/

Source code for final programme and hard copy of tutorial:
http://www.mediafire.com/?fwdynnltynj

Also, you might be interested in making an executable version of your programme in Windows. I suggest you use py2exe: http://www.py2exe.org/

The executable produced is minuscule in size, but the catch is that needs like half the Python libraries as well as the full wx library to run. So most programmes you make will be at about the 15mb mark. They can easily be compressed to less than half that size, however using 7-zip, rar or zip for example.

py2exe seems to produce application that have more of a Windows 95/98/ME feel rather than an XP feel, I've found. I'm not sure if I'm missing libraries or what, but at the end of the day, it runs perfectly and looks native enough, so. But if anybody knows how to keep the XP theme it has in IDLE, let me know.

Here is a templet file that allows you to package your wxPython program to an executable file with the py2exe module. It contains an XML manifest that gives the wxPython widgets a Windows XP appearance on XP machines:

# Py2Exe version 6.6 setup file for wxPython GUI programs.
# Creates a single executable file.
#
# Simply change the filename entry to whatever you called your source file.
# Optionally edit the version info and add the name of your icon file.
# It's easiest to save the modified templet code for instance as
# wx2exe.py to the same folder that containes your source file
# and the optional iconfile like "icon.ico"
#
# Now run the customized wx2exe.py ...
#
# Two subfolders will be created called build and dist.
# The dist folder contains your .exe file, MSVCR71.dll and w9xpopen.exe
# w9xpopen.exe is needed for os.popen() only.
# Your .exe file contains your byte code, all needed modules
# and the Python interpreter.
# The MSVCR71.dll can be distributed, but is often already in
# the Windows system32 folder.
# The build folder is for info only and can be deleted.


from distutils.core import setup
import py2exe
import sys

# Enter the filename of your wxPython source code file to compile.
# Your distribution file will be this filename with a .exe extension.
filename = "wxButton1.py"

# this creates the filename of your .exe file in the dist folder
if filename.endswith(".py"):
    distribution = filename[:-3]
elif filename.endswith(".pyw"):
    distribution = filename[:-4]

# if run without args, build executables in quiet mode
if len(sys.argv) == 1:
    sys.argv.append("py2exe")
    sys.argv.append("-q")

class Target:
    def __init__(self, **kw):
        self.__dict__.update(kw)
        # for the versioninfo resources, edit to your needs
        self.version = "0.6.6"
        self.company_name = "My Company"
        self.copyright = "no copyright"
        # fill this in with your own program description
        self.name = "WxPython Button Test"

# start of manifest for custom icon and appearance ...
#
# This XML manifest will be inserted as resource into your .exe file
# It gives the controls a Windows XP appearance (if run on XP)
#
manifest_template = '''
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
    version="5.0.0.0"
    processorArchitecture="x86"
    name="%(prog)s"
    type="win32"
/>
<description>%(prog)s Program</description>
<dependency>
    <dependentAssembly>
        <assemblyIdentity
            type="win32"
            name="Microsoft.Windows.Common-Controls"
            version="6.0.0.0"
            processorArchitecture="X86"
            publicKeyToken="6595b64144ccf1df"
            language="*"
        />
    </dependentAssembly>
</dependency>
</assembly>
'''

RT_MANIFEST = 24

# description is the versioninfo resource
# script is the wxPython code file
# manifest_template is the above XML code
# distribution will be the exe filename
# icon_resource is optional, remove any comment and give it 
# an iconfile you have, otherwise a default icon is used
# dest_base will be the exe filename
test_wx = Target(
    description = "A GUI app",
    script = filename,
    other_resources = [(RT_MANIFEST, 1, manifest_template % dict(prog=distribution))],
    #icon_resources = [(1, "icon.ico")],
    dest_base = distribution)

# end of manifest

setup(
    options = {"py2exe": {"compressed": 1,
                          "optimize": 2,
                          "ascii": 1,
                          "bundle_files": 1}},
    zipfile = None,
    windows = [test_wx]
    )

Any questions? Ask in a separate thread in the Python forum please.

Comments
works well

To draw simple shapes like lines, circles, rectangles you have to use the wx.PaintDC surface as the canvas. Here are some basic examples:

# the wx.PaintDC surface is wxPython's canvas
# draw a line on this canvas
# use help(wx.PaintDC) to get more info

import wx

class DrawPanel(wx.Panel):
    """draw a line on a panel's wx.PaintDC surface/canvas"""
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)
        # bind the panel to the paint event
        wx.EVT_PAINT(self, self.onPaint)

    def onPaint(self, event=None):
        # this is the wx drawing surface/canvas
        dc = wx.PaintDC(self)
        dc.Clear()
        # sets pen color and width
        dc.SetPen(wx.Pen("blue", 1))
        x1 = 20
        y1 = 10
        x2 = 500
        y2 = 300
        # draw a line from coordinates (x1,y1) to (x2,y2)
        dc.DrawLine(x1, y1, x2, y2)


app = wx.App()
frame = wx.Frame(None, -1, "Draw a line", size=(550, 350))
dp = DrawPanel(frame)
frame.Show(True)
app.MainLoop()

You can get a little more playful, using just about the same frame work:

# the wx.PaintDC surface is wxPython's canvas
# draw a series of lines on this canvas
# DC stands for Device Context 

import wx

class DrawPanel(wx.Panel):
    """draw lines on a panel's wx.PaintDC surface/canvas"""
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)
        # bind the panel to the paint event
        wx.EVT_PAINT(self, self.onPaint)

    def onPaint(self, event=None):
        # this is the wx drawing surface/canvas
        dc = wx.PaintDC(self)
        dc.Clear()
        # sets pen color and width
        dc.SetPen(wx.Pen("red", 1))
        # set the starting point coordinates to (0,0)
        x1 = y1 = 0
        # use a loop to set the endpoint coordinates (x2,y2)
        # and draw a series of lines
        for y2 in range(100, 400, 20):
            x2 = 500
            dc.DrawLine(x1, y1, x2, y2)


app = wx.App()
frame = wx.Frame(None, -1, "Draw lines", size=(550, 420))
dp = DrawPanel(frame)
frame.Show(True)
app.MainLoop()

Let's finish this off with the circle method:

# the wx.PaintDC surface is wxPython's canvas
# draw 2 circles on this canvas
# DC stands for Device Context 

import wx

class DrawPanel(wx.Panel):
    """draw circles on a panel's wx.PaintDC surface/canvas"""
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)
        # bind the panel to the paint event
        wx.EVT_PAINT(self, self.onPaint)

    def onPaint(self, event=None):
        # this is the wx drawing surface/canvas
        dc = wx.PaintDC(self)
        dc.Clear()
        # sets pen color and width
        dc.SetPen(wx.Pen("black", 2))
        # sets the fill color (default is background)
        dc.SetBrush(wx.Brush('yellow'))
        x = 200
        y = 200
        r = 150
        # draw a circle with center coordinates (x,y) and radius r
        dc.DrawCircle(x, y, r)
        # change the fill color
        dc.SetBrush(wx.Brush('red'))
        # draw a smaller circle
        r = 20
        dc.DrawCircle(x, y, r)


app = wx.App()
frame = wx.Frame(None, -1, "Draw circles", size=(420, 420))
dp = DrawPanel(frame)
frame.Show(True)
app.MainLoop()

Now it's up to you to do some fancy random art.

Nice stuff there Fuse and Ene!
Here is my contribution, showing you how easy it is to have wxPython display an image from image file:

# show  .jpg .png .bmp or .gif image on wx.Panel

import wx

class ImagePanel(wx.Panel):
  """ create the panel and put image on it """
  def __init__(self, parent, id):
    # create the panel, this will be self
    wx.Panel.__init__(self, parent, id)
    try:
        # pick your image file you have in the working folder
        # or use the full file path
        image_file = 'strawberry.jpg'
        bmp = wx.Bitmap(image_file)
        # show the bitmap, image's upper left corner anchors
        # at panel coordinates (5, 5), default is center
        wx.StaticBitmap(self, -1, bmp, (5, 5))
        # show some image information
        info = "%s  %dx%d" % (image_file, bmp.GetWidth(), bmp.GetHeight())
        # the parent is the frame 
        parent.SetTitle(info)
    except IOError:
        print "Image file %s not found" % imageFile
        raise SystemExit


# redirect=False sends stdout/stderr to the console window
# redirect=True sends stdout/stderr to a wx popup window (default) 
app = wx.App(redirect=False)
# create window/frame, no parent, -1 is the default ID
# also increase the size of the frame for larger images
frame = wx.Frame(None, -1, size = (480, 320))
# create the panel instance
imp = ImagePanel(frame, -1)
# show the frame
frame.Show(True)
# start the GUI event loop
app.MainLoop()
Comments
I like the frame work, thanks

Well, if you do web-page designing, you will be familiar with the repeating image tile wallpaper. You can do something similar to give your wxPython frame or panel very sexy backgrounds. I leaned on one of vegaseat's snippets to come up with this example:

# wallpaper wxPython panel using one image as repeating tile
# (image is obtained from base 64 encoded png image string)

import wx
import cStringIO
import base64


class MyPanel(wx.Panel):
    """ 
    class creates panel for the tile image contained in data_stream
    """
    def __init__(self, parent, id, frame_w, frame_h, data_stream):
        # create the panel
        wx.Panel.__init__(self, parent, id)
        # frame/panel width and height
        self.frame_w = frame_w
        self.frame_h = frame_h

        # convert to bitmap
        self.bmp = wx.BitmapFromImage(wx.ImageFromStream(data_stream))
        # do the wall papering on the PaintDC canvas...
        wx.EVT_PAINT(self, self.on_paint)
        # now put some widgets on top of the panel's wallpaper
        self.button1 = wx.Button(self, -1, label='Button1', pos=(15, 10))
        self.button2 = wx.Button(self, -1, label='Button2', pos=(15, 45))
        
        
    def on_paint(self, event=None):
        # create the paint canvas
        dc = wx.PaintDC(self)
        dc.Clear()
        # get image width and height
        image_w = self.bmp.GetWidth()
        image_h = self.bmp.GetHeight()
        # use repeating image tiles to wallpaper the canvas
        for x in range(0, self.frame_w, image_w):
            for y in range(0, self.frame_h, image_h):
                dc.DrawBitmap(self.bmp, x, y, True)


# the base64 encoded png image string
png_b64='''\
iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAMFBMVEUEAgQEAoYEAkgEAsYEAicE
AqgEAmkEAucEAhgEApgEAlkEAtYEAjgEArkEAnsEAvxjNQuZAAAFBUlEQVR4nNWXT2gcVRjAv91O
l+kyjd02kSSFYY21CtpUGIKHMGTBFkIpgVAeMpFtNlZi20MEc5jDHrKtRXOQ4LYGHA/rKuhBNB4W
CXQM2QY9GEWEdSgxPLsvhNBoO6Gp6dI/OH5vN7U97XsDXvwOe3o/vj/z5pvfQsDjHoC2UoTm0d0O
Px+4sP8lqCN3EdkrQpRk/GrNDoJHyBdJAQLJM1f54UfI5ykBoabUrqcfR2LdoiQYGmw8RG4hclYC
UWHXQ2QiBVOvSyA4g+Ft5OtfYCEphUCkgdzf8QCuyxEAz9eRy3FLLcoB/bk3OXI/p1jxnBQReyMb
wN8f47wUS7YsPrG/duENU3+XRaKI3IHDD0A5KYtAXwD3YNcmKAVpZCdHIndBGZRG4Bosws4tUPLy
iAar8OMQKE/KIwBVGGmF+I0wSA4u7IGWqjwQ5T97IJILk6UeB0IT/79QDp79VOpgLoersdeseGsn
iGclxcAAmeBvaq02u2yYnjX9vpD4LGjErDteqqQ9kv9QiJAGUatlXaPiEcv5QKKw+Tpju6USTRMn
v1eMQPQaX4azbsmkHnEKKxIIf7+Del003TGoy2TB7RbU7Oy4QQlxOhJy74U2UbN5Em+JJArtUghs
2Fm316RpyymwVTlkN9aF3VtOXtd9OeTIMk9CrOk8W2Pl5mcvHDl+cHRthLUtG5UKIdY6W2XN+59h
jfD1cyV8KFYizzLdfzRFcsd8fl5PsLhpEJLHVsrvjOWaVzaA5y8ZprOPUs+ZdhgrxkTNKL6+v0Rd
1zCXPGeQsZlUfKbYHME0jK3PlswKdayOgv4CaGsiBMawtOWSQa02PuKihKlAC07sxBylQ5F8gnVi
qXszzYGFL/nE/jQqZEjFuvCZaKuCj7WawOfNPjFMMgTnEqwVkfY9grpG/MwVVk5TRLCtKiLXRcgU
q6oMzhHyEcAVfo219qoAUf3rMJCCm/Sb0cVX7G95LyIE4hn1VE9jlwXBHYmJYSRPbZ/fdruVpBAp
K6PDjyNig1RBPa/NbyNbuG+EBslDgbe3kVuSBokRnfi3sNhrcgi83EDmkzAgZ5DYUV8dOfwiHChL
Io1ubu9+S9YgY22J+sx+jRIlKZljB1/+wW1QnGhOEokFweZGKIMEZT54EAs2w+ggPIvG+W4ogwQF
kUgogwQ4ehkiW2F6weiCHSidehikHb66CFpnCELNwMxqOIPUkEKDzIQpDCMDkZAExnPhkf80lP7J
EKfXRs8eP4Nyl5IFxsDmX3jPskS7Gmfe1UYIma9B1p0zqIzc1Z2LixpkswbP4gjt5uE2tGHcNWja
cfIXRcjk/oZB2mCbRg/KXUJC7r6vJ8lCCY0o7VgFGbk72iis1Gv2YCtMSiGHsS4XXEqnl5y8nNy1
BDYW5lbQu5w8k1uKfXYWezHpkoVGJPf6PWFnS1Cpe5eus5QMos26Lsw16lrxTwtOq+8tLCyOXsIs
tIeQjnU9M9J0ysfGfJ/roK4fcoGml3iW5MBYM+SnGb0ejCkGmEvTToL5mdhYuWldnOlwHHaIArXw
tugj5bhfbIpEdf3mRNY2zAp4noOeehA0kQ8+pbcl1rlBQsNTUe46BYjm+/q6W5qjMPSDc1LvzEn4
4BR28+pchcJvmqPz/yjKjeav8mR/Nw55n2l6MATdut4q1q7zdUm/iX9qEIkyVuVyJ7jLjJ0eYd/h
JQZpubvCqnE99Uya/APf6sMx1/lYCAAAAABJRU5ErkJggg==
'''

# convert to png image bytes
png_bytes = base64.b64decode(png_b64)
# convert png bytes to data stream
data_stream = cStringIO.StringIO(png_bytes)

app = wx.App()
# create window/frame instance, no parent, -1 is default ID
frame_w = 500
frame_h = 400
frame = wx.Frame(None, -1, "tiling the wx.PaintDC canvas", 
    size=(frame_w, frame_h))
# create panel class instance
mp = MyPanel(frame, -1, frame_w, frame_h, data_stream)
frame.Show(True)
# start the event loop
app.MainLoop()

paulthom had an idea of adding help text to a texbox when the programme loads. The help text just explains what to type in the box and needs to disappear once the user types anything or clicks in the box. If you're interested in doing similar, or want to know how to use mouse events, go here:

http://www.daniweb.com/forums/thread128788.html

Aside from being example code that shows you how to implement such a feature, it demonstrates how funny things happen if you don't return focus to the calling widget by typing event.Skip() at the end of the mouse event's handler method.

The wxPython documentation on mouse events is a decent reference: http://www.wxpython.org/docs/api/wx.MouseEvent-class.html

To get used to wxPython, I took vegaseat's mortgage calculator snippet written in Csharp and converted it to Python. I was surprised how easy it was:

# a simple mortgage calulator using wxPython
# checked it out with the online mortgage calculator at:
# http://www.mortgage-calc.com/mortgage/simple.php

import wx
import math

class MyFrame(wx.Frame):
    """frame and widgets to handle input and output of mortgage calc"""
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title)
        # add panel, labels, text and sizer widgets
        panel = wx.Panel(self, -1)
        panel.SetBackgroundColour('green')
        label1 = wx.StaticText(panel, -1, "Enter total loan amount:")
        label2 = wx.StaticText(panel, -1, "Enter annual interest (%):")
        label3 = wx.StaticText(panel, -1, "Enter years to pay:")
        self.loan = wx.TextCtrl(panel, -1, "100000")
        self.interest = wx.TextCtrl(panel, -1, "6.5")
        self.years = wx.TextCtrl(panel, -1, "30")
        self.calc_btn = wx.Button(panel, -1, ' Perform Mortgage Calculation ')
        self.calc_btn.SetBackgroundColour('light blue')
        self.calc_btn.Bind(wx.EVT_BUTTON, self.onCalc)
        info = "Modify the above data to your needs!"
        self.result = wx.TextCtrl(panel, -1, info, size=(290, 100), 
            style=wx.TE_MULTILINE)
        
        # use gridbagsizer for layout of widgets
        sizer = wx.GridBagSizer(vgap=5, hgap=10)
        sizer.Add(label1, pos=(0, 0))
        sizer.Add(self.loan, pos=(0, 1))  # row 0, column 1
        sizer.Add(label2, pos=(1, 0))
        sizer.Add(self.interest, pos=(1, 1))
        sizer.Add(label3, pos=(2, 0))
        sizer.Add(self.years, pos=(2, 1))
        sizer.Add(self.calc_btn, pos=(3, 0), span=(1, 2))
        # span=(1, 2) --> allow to span over 2 columns 
        sizer.Add(self.result, pos=(4, 0), span=(1, 2))
        
        # use boxsizer to add border around sizer
        border = wx.BoxSizer()
        border.Add(sizer, 0, wx.ALL, 20)
        panel.SetSizerAndFit(border)
        self.Fit()
        
    def onCalc(self, event):
        """do the mortgage calcuations"""
        # get the values from the input widgets
        principal = float(self.loan.GetValue())
        interest = float(self.interest.GetValue())
        years = float(self.years.GetValue())
        # calculate        
        interestRate = interest/(100 * 12)
        paymentNum = years * 12
        paymentVal = principal * \
            (interestRate/(1-math.pow((1+interestRate), (-paymentNum))))
        # show the result
        resultStr1 = "Your monthly payment will be $%.2f for\n" % paymentVal
        resultStr2 = "a %.1f year $%.2f loan at %.2f%s interest" % \
            (years, principal, interest, '%') 
        self.result.SetValue(resultStr1 + resultStr2) 
        

app = wx.App()
frame = MyFrame(None, -1, "Mortgage Calculator")
frame.Show()
app.MainLoop()
Comments
interesting code

I took Lardmeister's code and made a generic wxPython templet that you can then flesh out for cases when you need the user to enter data, press a button to process the data, and then show the result in an output area:

# basic wx.Frame with panel (needed for sizers), label, 
# edit, button, display, sizer, and border sizer

import wx

class MyFrame(wx.Frame):
    """
    frame and panel
    panel is neded for any sizer widgets
    """
    def __init__(self, parent, id, title):
        # this will be self
        wx.Frame.__init__(self, parent, id, title)
        # add panel
        panel = wx.Panel(self, -1)
        panel.SetBackgroundColour('green')

        # now add the needed widgets
        self.label1 = wx.StaticText(panel, -1, 'Enter ... :')
        self.entry1 = wx.TextCtrl(panel, -1, '...')
        self.button1 = wx.Button(panel, -1, 'Do ... ')
        self.button1.SetBackgroundColour('yellow')
        self.button1.Bind(wx.EVT_BUTTON, self.onCmd)
        info = "Optional instructive message!"
        self.display = wx.TextCtrl(panel, -1, info, size=(250, 100), 
            style=wx.TE_MULTILINE)
        
        # use gridbagsizer for layout of widgets
        # set optional vertical and horizontal gaps
        sizer = wx.GridBagSizer(vgap=5, hgap=10)
        sizer.Add(self.label1, pos=(0, 0))       # pos(row,column)
        sizer.Add(self.entry1, pos=(0, 1))  # row 0, column 1

        # span=(1, 2) --> allow to span over 2 columns 
        sizer.Add(self.button1, pos=(1, 0), span=(1, 2))
        sizer.Add(self.display, pos=(2, 0), span=(1, 2))
        
        # use boxsizer to add border around sizer
        border = wx.BoxSizer()
        border.Add(sizer, 0, wx.ALL, 20)
        panel.SetSizerAndFit(border)
        self.Fit()
        
    def onCmd(self, event):
        """process data and show result"""
        # get the data from the input widget
        string1 = self.entry1.GetValue()
        # do the processing ...
        proc_data = string1 * 3   
        # show the result ...
        self.display.SetValue(proc_data) 
        

app = wx.App(redirect=False)
frame = MyFrame(None, -1, "Title ...")
frame.Show()
app.MainLoop()

Note how we take care of an "Optional instructive message" without much fanfare.

I took the above wxPython templet and added a background or splash image. So now you have a templet that shows you how to create a frame, a panel, a label, an entry (input), a button, sizers, a multiline display and show an image. Could be the backbone of many wxPython GUI applications:

# basic wx.Frame with splash image panel (panel needed for sizers)
# label, edit, button, display, sizer, and border sizer

import wx

class MyFrame(wx.Frame):
    """
    frame and panel
    panel is neded for any sizer widgets
    """
    def __init__(self, parent, id, title):
        # this will be self
        wx.Frame.__init__(self, parent, id, title)
        # add panel
        panel = wx.Panel(self, -1)
        
        # pick a splash image file you have in the working folder
        image_file = 'HIVirus1.jpg'
        bmp = wx.Bitmap(image_file)
        # allow for alternative if splash image file not found
        if bmp:
            splash = wx.StaticBitmap(panel, -1, bmp)
        else:
            panel.SetBackgroundColour('green')
            splash = panel

        # now add the needed widgets
        self.label1 = wx.StaticText(splash, -1, 'Enter ... :')
        self.entry1 = wx.TextCtrl(splash, -1, '...')
        self.button1 = wx.Button(splash, -1, 'Do ... ')
        self.button1.SetBackgroundColour('yellow')
        self.button1.Bind(wx.EVT_BUTTON, self.onCmd)
        info = "Optional instructive message!"
        self.display = wx.TextCtrl(splash, -1, info, size=(250, 100), 
            style=wx.TE_MULTILINE)
        
        # use gridbagsizer for layout of widgets
        # set optional vertical and horizontal gaps
        sizer = wx.GridBagSizer(vgap=5, hgap=10)
        sizer.Add(self.label1, pos=(0, 0))       # pos(row,column)
        sizer.Add(self.entry1, pos=(0, 1))  # row 0, column 1

        # span=(1, 2) --> allow to span over 2 columns 
        sizer.Add(self.button1, pos=(1, 0), span=(1, 2))
        sizer.Add(self.display, pos=(2, 0), span=(1, 2))
        
        # use boxsizer to add border around sizer
        border = wx.BoxSizer()
        border.Add(sizer, 0, wx.ALL, 30)
        panel.SetSizerAndFit(border)
        self.Fit()
        
    def onCmd(self, event):
        """process data and show result"""
        # get the data from the input widget
        string1 = self.entry1.GetValue()
        # do the processing ...
        proc_data = string1 * 3   
        # show the result ...
        self.display.SetValue(proc_data) 
        

app = wx.App(redirect=False)  # stdout/stderr to console
frame = MyFrame(None, -1, "Title ...")
# optional info, size seems to adjust to sizers demand
print frame.GetSize()  # (336, 250)
frame.Show()
app.MainLoop()
Comments
nicely orchestrated
Attachments HIVirus1.jpg 12.79 KB

The wxPython GUI toolkit has some interesting widgets, one of them is a complete analog clock widget. Here is an example:

# looking at the wxPython analog clock widget

import wx
from wx.lib import analogclock as ac

class MyFrame(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title)

        clock = ac.AnalogClockWindow(self)
        clock.SetBackgroundColour('gray')
        clock.SetHandColours('black')
        clock.SetTickColours('WHITE')
        # set hour and minute ticks
        clock.SetTickSizes(h=15, m=5)
        # set hour style
        clock.SetTickStyles(ac.TICKS_ROMAN)
        self.SetSize((400,350))


app = wx.App()
frame = MyFrame(None, wx.ID_ANY, "analogclock")
frame.Show()
app.MainLoop()

This code sample shows you how to add an about message box to your wxPython program:

# wxPython Frame with menu, statusbar, and about dialog

import wx

class MyFrame(wx.Frame):
    """
    create a frame, with menu, statusbar and about dialog
    inherits wx.Frame
    """
    def __init__(self):
        # create a frame/window, no parent, default to wxID_ANY
        wx.Frame.__init__(self, None, wx.ID_ANY, 'About (click File)',
            pos=(300, 150), size=(300, 350))

        # create a status bar at the bottom
        self.CreateStatusBar()
        self.SetStatusText("This is the statusbar")

        menu = wx.Menu()
        # the optional & allows you to use alt/a
        # the last string argument shows in the status bar on mouse_over
        menu_about = menu.Append(wx.ID_ANY, "&About", "About message")
        menu.AppendSeparator()
        # the optional & allows you to use alt/x
        menu_exit = menu.Append(wx.ID_ANY, "E&xit", "Quit the program")

        # create a menu bar at the top
        menuBar = wx.MenuBar()
        # the & allows you to use alt/f
        menuBar.Append(menu, "&File")
        self.SetMenuBar(menuBar)

        # bind the menu events to an action/function/method
        self.Bind(wx.EVT_MENU, self.onMenuAbout, menu_about)
        self.Bind(wx.EVT_MENU, self.onMenuExit, menu_exit)

    def onMenuAbout(self, event):
        dlg = wx.MessageDialog(self,
            "a simple application using wxFrame, wxMenu\n"
            "a statusbar, and this about message.  snee",
            "About", wx.OK | wx.ICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()

    def onMenuExit(self, event):
        # via wx.EVT_CLOSE event
        self.Close(True)

app = wx.App(0)
# create class instance
window = MyFrame()
window.Show(True)
# start the event loop
app.MainLoop()

The wxPython GUI toolkit has a number of ways to specify colors, or should I say colours. Here is a simple example:

# show different ways to specify colours with wxPython

import wx

class ColourTest(wx.Dialog):
    def __init__(self, parent, id, title):
        """use a dialog box as a simple window/frame"""
        wx.Dialog.__init__(self, parent, id, title, size=(300, 300))

        self.pnl1 = wx.Panel(self, -1)
        self.pnl2 = wx.Panel(self, -1)
        self.pnl3 = wx.Panel(self, -1)
        self.pnl4 = wx.Panel(self, -1)
        self.pnl5 = wx.Panel(self, -1)
        self.pnl6 = wx.Panel(self, -1)
        self.pnl7 = wx.Panel(self, -1)
        self.pnl8 = wx.Panel(self, -1)

        # use a wx.GridSizer(rows, cols, vgap, hgap) for layout
        gs = wx.GridSizer(4,2,3,3)
        # for this dialog window wx.EXPAND is not needed
        gs.AddMany([ (self.pnl1, 0 ,wx.EXPAND),
            (self.pnl2, 0, wx.EXPAND),
            (self.pnl3, 0, wx.EXPAND),
            (self.pnl4, 0, wx.EXPAND),
            (self.pnl5, 0, wx.EXPAND),
            (self.pnl6, 0, wx.EXPAND),
            (self.pnl7, 0, wx.EXPAND),
            (self.pnl8, 0, wx.EXPAND) ])

        self.SetSizer(gs)
        self.SetColors()
        self.Centre()
        self.ShowModal()
        self.Destroy()

    def SetColors(self):
        # create a number of colorful panels
        # using different ways to specify colours
        self.pnl1.SetBackgroundColour(wx.BLACK)
        # wx.Colour() uses a (r, g, b) tuple
        self.pnl2.SetBackgroundColour(wx.Colour(139,105,20))
        self.pnl3.SetBackgroundColour(wx.RED)
        # specify as #RRGGBB hex string
        self.pnl4.SetBackgroundColour('#0000FF')
        self.pnl5.SetBackgroundColour('dark green')
        self.pnl6.SetBackgroundColour('midnight blue')
        self.pnl7.SetBackgroundColour(wx.LIGHT_GREY)
        self.pnl8.SetBackgroundColour('plum')


app = wx.App()
ColourTest(None, wx.ID_ANY, 'wxPython colours')
app.MainLoop()

Trapping a key event:

# bind keyevent to key down and display the key value

import wx

class KeyEvent(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(400, 70))
        self.SetBackgroundColour("yellow")
        # create a label
        self.label = wx.StaticText(self, wx.ID_ANY, label="  ", pos=(20, 30))

        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)

        self.Show(True)

    def OnKeyDown(self, event):
        keycode = event.GetKeyCode()
        s = "Key value = " + str(keycode)
        self.label.SetLabel(s)
        if keycode == wx.WXK_ESCAPE:
            choice = wx.MessageBox('Are you sure you want to quit? ',
                'Question', wx.YES_NO|wx.CENTRE|wx.NO_DEFAULT, self)
            if choice == wx.YES:
                self.Close()
        event.Skip()


app = wx.App()
KeyEvent(None, wx.ID_ANY, 'press any key (press escape to exit)')
app.MainLoop()

Sneekula left one small starter editor somewhere around DaniWeb. So I took it and modified it to use the wxPython toolbar with icon images rather than Snee's menu bar. The images come from wxPython's builtin art library:

# the start of one small text editor with toolbar and image icons
# notice that the wx.TextCtrl() surface has already some advanced
# features: select text, right click to cut, copy and paste etc.

import os
import wx

class MyFrame(wx.Frame):
    def __init__(self, title):
        wx.Frame.__init__(self, None, wx.ID_ANY, title, size=(500, 300))
        self.control = wx.TextCtrl(self, 1, style=wx.TE_MULTILINE)

        # statusBar at the bottom of the window
        self.CreateStatusBar()
        self.SetStatusText(" Click on the icon")

        # ToolBar at the top of the window
        toolbar = wx.ToolBar(self, -1, style=wx.TB_HORIZONTAL|wx.NO_BORDER)
        toolbar.SetToolBitmapSize(size=(24,24))
        toolbar.AddSimpleTool(wx.ID_OPEN, self.getBMP(wx.ART_FILE_OPEN),
            "Load", " Load a text file")
        toolbar.AddSimpleTool(wx.ID_SAVE, self.getBMP(wx.ART_FILE_SAVE),
            "Save", " Save the text file")
        toolbar.AddSimpleTool(wx.ID_ABOUT, self.getBMP(wx.ART_INFORMATION),
            "About"," About message")
        toolbar.AddSeparator()
        toolbar.AddSimpleTool(wx.ID_EXIT, self.getBMP(wx.ART_QUIT),
            "Exit"," Exit the program")
        toolbar.Realize()
        self.SetToolBar(toolbar)

        # bind the various toolbar icon click events to some action
        self.Bind(wx.EVT_TOOL, self.onLoad, id=wx.ID_OPEN)
        self.Bind(wx.EVT_TOOL, self.onSave, id=wx.ID_SAVE)
        self.Bind(wx.EVT_TOOL, self.onAbout, id=wx.ID_ABOUT)
        self.Bind(wx.EVT_TOOL, self.onExit, id=wx.ID_EXIT)

    def getBMP(self, pic_id):
        """get the bitmap image from the wxPython art provider"""
        return wx.ArtProvider.GetBitmap(pic_id, wx.ART_TOOLBAR, wx.Size(24, 24))

    def onAbout(self, e):
        """ the about box """
        about = wx.MessageDialog( self, " A very simple text editor \n"
            " using the wxPython GUI toolkit", "About Simple Editor", wx.OK)
        about.ShowModal()
        about.Destroy()

    def onLoad(self, e):
        """ open text file"""
        self.dirname = ''
        mask = "Text (.txt)|*.txt|All (.*)|*.*"
        dlg = wx.FileDialog(self, "Choose a file to load",
            self.dirname, "", mask, wx.OPEN)
        if dlg.ShowModal() == wx.ID_OK:
            self.filename = dlg.GetFilename()
            self.dirname = dlg.GetDirectory()
            f = open(os.path.join(self.dirname,self.filename),'r')
            self.control.SetValue(f.read())
            f.close()
        dlg.Destroy()

    def onSave(self, e):
        """ Save text file"""
        self.dirname = ''
        mask = "Text (.txt)|*.txt|All (.*)|*.*"
        dlg = wx.FileDialog(self, "Choose or create a file to save to",
            self.dirname, self.filename, mask, 
            wx.OVERWRITE_PROMPT|wx.OPEN)
        if dlg.ShowModal() == wx.ID_OK:
            self.filename = dlg.GetFilename()
            self.dirname = dlg.GetDirectory()
            f = open(os.path.join(self.dirname,self.filename),'w')
            f.write(self.control.GetValue())
            f.close()
        dlg.Destroy()

    def onExit(self, e):
        self.Close(True)


app = wx.App(0)
# create instance of MyFrame and show it
MyFrame(title="A Simple Editor").Show()
app.MainLoop()

This shows you how to use wxPython's wx.lib.fancytext to show super and subscripted text in a specified font, colour and size. The text coding is XML ...

# wxPython's wx.lib.fancytext can show super and subscripted text
# using XML code

import  wx
import  wx.lib.fancytext as fancytext

class FancyText(wx.Panel):
    """display fancytext on a panel"""
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, wx.ID_ANY)
        self.Bind(wx.EVT_PAINT, self.OnPaint)

    def OnPaint(self, evt):
        """generate the fancytext on a paint dc canvas"""
        dc = wx.PaintDC(self)
        fancytext.RenderToDC(xml_str, dc, 0, 20)

# the XML code string
xml_str = """\
<font family="swiss" color="blue" size="20">
  H<sub>2</sub>O
  x<sup>3</sup> + y<sup>2</sup> - 15 = 0
</font>
"""

app = wx.App(0)
frame = wx.Frame(None, wx.ID_ANY, title='wxPython fancy text',
    pos=(100, 50), size=(500, 250))
FancyText(frame)
frame.Show(True)
app.MainLoop()

Dealing with mouse events ...

# get the position of the mouse when clicked or moved

import wx

class MyFrame(wx.Frame):
    """create a color frame, inherits from wx.Frame"""
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, wx.ID_ANY, "Move or click mouse")
        self.SetBackgroundColour('Goldenrod')
        # give it a fancier cursor
        self.SetCursor(wx.StockCursor(wx.CURSOR_PENCIL))

        # bind some mouse events
        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
        self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
        self.Bind(wx.EVT_MOTION, self.OnMotion)

    def OnLeftDown(self, event):
        """left mouse button is pressed"""
        pt = event.GetPosition()  # position tuple
        self.SetTitle('LeftMouse click at = ' + str(pt))

    def OnRightDown(self, event):
        """right mouse button is pressed"""
        pt = event.GetPosition()
        self.SetTitle('RightMouse click at = ' + str(pt))

    def OnMotion(self, event):
        """mouse in motion"""
        pt = event.GetPosition()
        self.SetTitle('Mouse in motion at = ' + str(pt))


app = wx.App(0)
MyFrame(None).Show()
app.MainLoop()

Applying different fonts to a text display ...

# test wx.Font() and wx.FontDialog on a given text
# wx.Font(pointSize, family, style, weight, underline=false, faceName="",
#     encoding=wx.FONTENCODING_DEFAULT)

import wx

class MyFrame(wx.Frame):
    def __init__(self, parent, title, data):
        wx.Frame.__init__(self, parent, wx.ID_ANY, title, size=(600, 400))
        panel = wx.Panel(self, wx.ID_ANY)
        button = wx.Button(panel, wx.ID_ANY, label='Change Font',
            pos=(3, 3))
        button.Bind(wx.EVT_BUTTON, self.changeFont)

        # family: wx.DEFAULT, wx.DECORATIVE, wx.ROMAN, wx.SCRIPT, 
        # wx.SWISS, wx.MODERN
        # style: wx.NORMAL, wx.SLANT or wx.ITALIC
        # weight: wx.NORMAL, wx.LIGHT or wx.BOLD
        font = wx.Font(16, wx.SCRIPT, wx.NORMAL, wx.LIGHT)
        # use additional fonts this way ...
        #face = u'Comic Sans MS'
        #font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, False, face)

        self.text = wx.StaticText(panel, wx.ID_ANY, data, pos=(10,35))
        self.text.SetFont(font)

    def changeFont(self, event):
        dialog = wx.FontDialog(None, wx.FontData())
        if dialog.ShowModal() == wx.ID_OK:
            data = dialog.GetFontData()
            font = data.GetChosenFont()
            self.text.SetForegroundColour(data.GetColour())
            self.text.SetFont(font)
        dialog.Destroy()


data = """\
Al Gore:  The Wild Years
America's Most Popular Lawyers
Career Opportunities for History Majors
Different Ways to Spell "Bob"
Ethiopian Tips on World Dominance
Everything Men Know About Women
Everything Women Know About Men
Staple Your Way to Success
The Amish Phone Book
The Engineer's Guide to Fashion
Ralph Nader's list of pleasures"""

app = wx.App(0)
# create instance of MyFrame and show
MyFrame(None, "Text and Font", data).Show()
# start the event loop
app.MainLoop()

You can use wxPython's ImageDialog to preview the image to be loaded. Also use a scrolled window to display oversized images ...

# test the wx.lib.imagebrowser.ImageDialog()
# and display the loaded image on a scrolled window

import wx
import os
import wx.lib.imagebrowser

class MyFrame(wx.Frame):
    def __init__(self, parent, mytitle):
        wx.Frame.__init__(self, parent, wx.ID_ANY, mytitle, size=(600,400),
            style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE)

        # create a srolled window to put the image on
        self.scrollw = wx.ScrolledWindow(self, wx.ID_ANY)
        self.scrollw.SetBackgroundColour('green')
        # set EnableScrolling(bool x_scrolling, bool y_scrolling)
        self.scrollw.EnableScrolling(True, True)
        # create the scroll bars, set max width and height
        max_width = 1000
        max_height = 1000
        # SetScrollbars(pixelsPerUnitX, pixelsPerUnitY, noUnitsX, noUnitsY)
        self.scrollw.SetScrollbars(20, 20, max_width/20, max_height/20)

        # create a statusbar at the bottom of the frame
        self.CreateStatusBar()

        # create the menubar at the top of the frame
        menubar = wx.MenuBar()
        # setting up the menu
        filemenu = wx.Menu()
        # alt/o is hotkey, "Open file" shows up in statusbar
        filemenu.Append(wx.ID_OPEN, "&Open","Open image file")
        filemenu.AppendSeparator()
        # alt/x is hotkey
        filemenu.Append(wx.ID_EXIT,"E&xit","Exit program")
        # add the filemenu to the menubar
        menubar.Append(filemenu,"&File")
        # add the finished menubar to the frame/window
        self.SetMenuBar(menubar)

        # bind event to an action
        self.Bind(wx.EVT_MENU, self.onExit, id=wx.ID_EXIT)
        self.Bind(wx.EVT_MENU, self.onOpen, id=wx.ID_OPEN)

    def onExit(self,event):
        """close the frame"""
        self.Close(True)

    def onOpen(self,event):
        """open an image file via wx.lib.imagebrowser.ImageDialog()"""
        dirname = ''
        dialog = wx.lib.imagebrowser.ImageDialog(self, dirname)
        if dialog.ShowModal() == wx.ID_OK:
            filename = dialog.GetFile()
            image = wx.Bitmap(filename)
            self.SetStatusText(filename)
            # bitmap upper left corner is in position (x, y) = (5, 5)
            wx.StaticBitmap(self.scrollw, wx.ID_ANY, image, pos=(5, 5),
                size=(image.GetWidth(), image.GetHeight()))
        dialog.Destroy()


app = wx.App(0)
# create MyFrame instance and show the frame
MyFrame(None, "Test wx.lib.imagebrowser.ImageDialog()").Show()
app.MainLoop()

Ever wanted to know how many named colours wxPython has? Here is a short demo code to find out ...

# test the wx.Choice() widget and wx.lib.colourdb
# wx.Choice(parent, id, pos, size, choices, style)
# has no style options

import wx
import wx.lib.colourdb as colourdb

class MyFrame(wx.Frame):
    def __init__(self, parent, mytitle, mysize):
        wx.Frame.__init__(self, parent, wx.ID_ANY, mytitle, size=mysize)
        # create a panel to show the selected colour
        self.panel = wx.Panel(self, wx.ID_ANY, pos=(0,40), size=(250, 130))

        # create a sorted colour list from the wx colour data base
        colourdb.updateColourDB()
        colour_list = sorted(colourdb.getColourList())
        # create a choice widget
        self.choice = wx.Choice(self, wx.ID_ANY, choices=colour_list)
        # select item 0 (first item) in sorted colour list
        self.choice.SetSelection(0)
        # set the current frame color to the choice
        self.SetBackgroundColour(self.choice.GetStringSelection())
        # bind the checkbox events to an action
        self.choice.Bind(wx.EVT_CHOICE, self.onChoice)

    def onChoice(self, event):
        bgcolour = self.choice.GetStringSelection()
        # change colour of the panel to the selected colour ...
        self.panel.SetBackgroundColour(bgcolour)
        self.panel.Refresh()
        # show the selected color in the frame title
        self.SetTitle(bgcolour.lower())


app = wx.App(0)
# create a MyFrame instance and show
MyFrame(None, 'Select a colour', (250, 170)).Show()
app.MainLoop()

Might as well get used to the British spelling of colour!
I left some US spellings in there for you to find.

The wx.RadioBox() widget is just a convenient way to group a bunch of radio buttons and give them a title. Radio buttons are used if you want only one of the group selected. Here is an example ...

# test the wx.RadioBox() widget
# wx.RadioBox(parent, id, label, pos, size, choices, style)
# combines a wx.StaticBox() with wx.RadioButton()
# only one radiobutton can be selected

import wx

class MyFrame(wx.Frame):
    def __init__(self, parent, mytitle, mysize):
        wx.Frame.__init__(self, parent, wx.ID_ANY, mytitle, size=mysize)
        self.SetBackgroundColour("yellow")

        self.options = ['now', 'later', 'much later', 'never']
        # create an input widget
        self.radiobox = wx.RadioBox(self, wx.ID_ANY, "Select one option",
            pos=(10, 10), choices=self.options, style=wx.VERTICAL)
        # set radio button 1 as selected (first button is 0)
        self.radiobox.SetSelection(1)
        # bind mouse click to an action
        self.radiobox.Bind(wx.EVT_RADIOBOX, self.onAction)
        # create an output widget
        self.label = wx.StaticText(self, wx.ID_ANY, "" , pos=(10, 120))
        # show present selection
        self.onAction(None)

    def onAction(self, event):
        """ some action code"""
        #index = self.radiobox.GetSelection()
        #s = "You selected option " + self.options[index]
        # better ...
        s = "You selected option " + self.radiobox.GetStringSelection()
        self.label.SetLabel(s)


app = wx.App(0)
# create a MyFrame instance and show the frame
MyFrame(None, 'testing wx.RadioBox()', (300, 200)).Show()
app.MainLoop()

The wx.SpinCtrl() is a graphical way to enter integers. You can click on the up or down arrows to change the value, or use the keyboard to type the value directly, once the spinbox has the focus. Just run the code to see what it does ...

# test the wx.SpinCtrl() widget
# wx.SpinCtrl(parent, id, value, pos, size, style, min, max, initial)
# used for integer number input
# style =
# wx.SP_ARROW_KEYS  can use arrow keys to change the value
# wx.SP_WRAP  value wraps at the minimum and maximum.

import wx

class MyFrame(wx.Frame):
    def __init__(self, parent, mytitle, mysize):
        wx.Frame.__init__(self, parent, wx.ID_ANY, mytitle, size=mysize)
        self.SetBackgroundColour("yellow")

        # create a spinctrl widget for inteer input
        self.spin = wx.SpinCtrl( self, wx.ID_ANY, value="",
            pos=(10, 20), size=(80, 25), min=-100, max=1000,
            initial=98, style=wx.SP_ARROW_KEYS)
        # bind mouse click on arrows to an action
        self.spin.Bind(wx.EVT_SPINCTRL, self.onAction)
        # you can edit the value directly
        self.spin.Bind(wx.EVT_TEXT, self.onAction)

        # create an output widget
        s1 = "click on arrows to change the spinbox value \n"
        s2 = "or type the integer value directly"
        self.label = wx.StaticText(self, wx.ID_ANY, s1+s2, pos=(10, 60))

    def onAction(self, event):
        """ some action code"""
        val = self.spin.GetValue()
        f = str(round(val * 9.0/5 + 32, 2))
        c = str(round((val - 32)*5/9.0, 2))
        v = str(val)
        s1 = v + " degree Fahrenheit is " + c + " degree Celcius \n"
        s2 = v + " degree Celcius is " + f + " degree Fahrenheit"
        self.label.SetLabel(s1 + s2)


app = wx.App(0)
# create a MyFrame instance and show the frame
MyFrame(None, 'testing the wx.SpinCtrl()', (300, 150)).Show()
app.MainLoop()

You can input a date the graphical way, let's call it the wxPythonian way, with a simple mouse point and click:

# explore the wx.calendar.CalendarCtrl() control
# allows for point and click date input

import wx
import wx.calendar as cal

class MyCalendar(wx.Dialog):
    """create a simple dialog window with a calendar display"""
    def __init__(self, parent, mytitle):
        wx.Dialog.__init__(self, parent, wx.ID_ANY, mytitle)
        # use a box sizer to position controls vertically
        vbox = wx.BoxSizer(wx.VERTICAL)

        # wx.DateTime_Now() sets calendar to current date
        self.calendar = cal.CalendarCtrl(self, wx.ID_ANY, wx.DateTime_Now())
        vbox.Add(self.calendar, 0, wx.EXPAND|wx.ALL, border=20)
        # click on day
        self.calendar.Bind(cal.EVT_CALENDAR_DAY, self.onCalSelected)
        # change month
        self.calendar.Bind(cal.EVT_CALENDAR_MONTH, self.onCalSelected)
        # change year
        self.calendar.Bind(cal.EVT_CALENDAR_YEAR, self.onCalSelected)

        self.label = wx.StaticText(self, wx.ID_ANY, 'click on a day')
        vbox.Add(self.label, 0, wx.EXPAND|wx.ALL, border=20)

        button = wx.Button(self, wx.ID_ANY, 'Exit')
        vbox.Add(button, 0, wx.ALL|wx.ALIGN_CENTER, border=20)
        self.Bind(wx.EVT_BUTTON, self.onQuit, button)

        self.SetSizerAndFit(vbox)
        self.Show(True)
        self.Centre()

    def onCalSelected(self, event):
        #date = event.GetDate()
        date = self.calendar.GetDate()
        day = date.GetDay()
        # for some strange reason month starts with zero
        month = date.GetMonth() + 1
        # year is yyyy format
        year = date.GetYear()
        s1 = "%02d/%02d/%d \n" % (month, day, year)
        # or just take a slice of the first 8 characters to show mm/dd/yy
        s2 = str(date)[0:8]
        self.label.SetLabel(s1 + s2)

    def onQuit(self, event):
        self.Destroy()


app = wx.App()
MyCalendar(None, 'wx.calendar.CalendarCtrl()')
app.MainLoop()
This question has already been answered. Start a new discussion instead.