wxPython: A Tour of Buttons (Part 1 of 2)

Most people don't really think about the widgets they use every day. Instead, they just take them for granted. The button is one of the most commonly used widgets that we use. From the keys on our keyboards to the buttons on door locks, we find them everywhere. They are even more prevalent in software where buttons can be practically any shape or size. Some buttons don't even look like buttons! In this article, we'll look at several buttons that wxPython provides for you and how to use them.

The Common wx.Button Widget

The first button that most wxPython programmers will likely use is wx.Button. It wraps the native button control on the three major platforms and just looks "right" on each of them. Here's a really simple example of how we would use one:

import wx
 
class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Button Tutorial")
        panel = wx.Panel(self, wx.ID_ANY)
        
        button = wx.Button(panel, id=wx.ID_ANY, label="Press Me")
        button.Bind(wx.EVT_BUTTON, self.onButton)
        # self.Bind(wx.EVT_BUTTON, self.onButton, button)
        
    #----------------------------------------------------------------------
    def onButton(self, event):
        """
        This method is fired when its corresponding button is pressed
        """
        print "Button pressed!"
 
# Run the program
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm()
    frame.Show()
    app.MainLoop()

All the code above does is create a simple form with one button. The button is bound to the onButton event handler. Notice that there is another way to bind that is commented out. Robin Dunn explains the difference between the two ways to bind an event on the wiki, so if you don't understand it, go check it out! If you don't feel like getting the full explanation, here's my take:

  • self.Bind will bind the event to the parent (usually a wx.Frame) via the button. This means that the event may have to float up through several event layers before it reaches the parent. If anywhere in those layers of handlers, the developer does not call event.Skip(), then the event stops.
  • widget.Bind for all intents and purposes, binds the event to just that widget. If you want, you can call event.Skip() here to pass the event onwards and upwards, although I can't think of a time when I've needed to do that with a button event.

Binding Multiple Widgets to the Same Handler

It's fairly common practice to need to bind multiple widgets to just one handler and then differentiate between the calling widgets from within the handler. There are several ways to get the required information. We'll look at a couple of the most popular ways to actually get the widget itself:

import wx
 
class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Button Tutorial")
        panel = wx.Panel(self, wx.ID_ANY)
        
        sizer = wx.BoxSizer(wx.VERTICAL)
        buttonOne = wx.Button(panel, id=wx.ID_ANY, label="One", name="one")
        buttonTwo = wx.Button(panel, id=wx.ID_ANY, label="Two", name="two")
        buttonThree = wx.Button(panel, id=wx.ID_ANY, label="Three", name="three")
        buttons = [buttonOne, buttonTwo, buttonThree]
        
        for button in buttons:
            self.buildButtons(button, sizer)
            
        panel.SetSizer(sizer)
        
    #----------------------------------------------------------------------
    def buildButtons(self, btn, sizer):
        """"""
        btn.Bind(wx.EVT_BUTTON, self.onButton)
        sizer.Add(btn, 0, wx.ALL, 5)
        
    #----------------------------------------------------------------------
    def onButton(self, event):
        """
        This method is fired when its corresponding button is pressed
        """
        button = event.GetEventObject()
        print "The button you pressed was labeled: " + button.GetLabel()
        print "The button's name is " + button.GetName()
        
        button_id = event.GetId()
        button_by_id = self.FindWindowById(button_id)
        print "The button you pressed was labeled: " + button_by_id.GetLabel()
        print "The button's name is " + button_by_id.GetName()
 
# Run the program
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm()
    frame.Show()
    app.MainLoop()

Here we have three buttons, each bound to the onButton event handler. There are two ways to get the button object:

  1. event.GetEventObject()
  2. self.FindWindowById(button_id)

The first gets the widget directly in one fell swoop. The second requires us to get the widget ID using event.GetId. Either way, once you have the button, you can call any of its methods. In our example, we get the button's name and current label. We can also change the button's label or other attributes by using the appropriate setter method (i.e. SetLabel). Now let's turn our attention to some of the other buttons available to us.

Toggle Buttons and Bitmap Buttons

Toggle / Bitmap Button Example

The second most popular button is probably a toss-up between Toggle Buttons and Bitmap Buttons. With wxPython, you have two choices for both types of buttons: Use the native versions (where possible) or use one of the generic versions (i.e. buttons written in pure Python rather than SWIGed). Let's look at all four:

import wx
import wx.lib.buttons as buttons
 
class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Button Tutorial")
        panel = wx.Panel(self, wx.ID_ANY)
        
        # create a normal toggle button
        button = wx.ToggleButton(panel, label="Press Me")
        button.Bind(wx.EVT_TOGGLEBUTTON, self.onToggle)
        
        # create a generic toggle button
        gen_toggle_button = buttons.GenToggleButton(panel, -1, "Generic Toggle")
        gen_toggle_button.Bind(wx.EVT_BUTTON, self.onGenericToggle)
        
        # create a normal bitmap button
        bmp = wx.Bitmap("agt_mp3.png", wx.BITMAP_TYPE_ANY)
        bmapBtn = wx.BitmapButton(panel, id=wx.ID_ANY, bitmap=bmp,
                                  size=(bmp.GetWidth()+10, bmp.GetHeight()+10))
        
        # create a generic bitmap button
        genBmapBtn = buttons.GenBitmapButton(panel, bitmap=bmp)
        
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(button, 0, wx.ALL, 5)
        sizer.Add(gen_toggle_button, 0, wx.ALL, 5)
        sizer.Add(bmapBtn, 0, wx.ALL, 5)
        sizer.Add(genBmapBtn, 0, wx.ALL, 5)
        panel.SetSizer(sizer)
        
    #----------------------------------------------------------------------
    def onGenericToggle(self, eventq):
        """"""
        print "Generic toggle button was pressed!"
        
    #----------------------------------------------------------------------
    def onToggle(self, event):
        """
        Print a message when toggled
        """
        print "Button toggled!"
        
     
# Run the program
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm()
    frame.Show()
    app.MainLoop()

This code is pretty similar to what we've already seen. The new piece of information is how to get at the generic buttons. We need to do a special import, like this: import wx.lib.buttons. Now we have access to a bunch of generic buttons including a normal button, a toggle, a bitmap, a bitmap with text and flat buttons. There are even a couple of themed buttons that look almost like the native buttons!

Let's take note of a couple idiosyncrasies in the code above. First, there's the strange sizing we do with the wx.BitmapButton widget. We do it this way to make sure that there's some "white" space around the bitmap. This makes it easier to tell that the widget is a button. The generic version seems to do this automatically. The other odd thing that is that when you toggle the wx.ToggleButton button and keep the mouse pointer above the button, it doesn't look toggled. If you mouse off the button, then it does (I noticed this on Windows XP...it may not be an issue on other platforms).

According to Robin Dunn (creator of wxPython), the 2.9 series of wxPython will have an updated wx.Button control that can display a bitmap along with its text. Now that's something to look forward to!

The PlateButton

The PlateButton is another generic widget. It was created by Cody Precord, author of Editra. There are tons of examples of his button in the wxPython demo. We'll look at just a couple:

import wx
import wx.lib.platebtn as platebtn
 
class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Plate Button Tutorial")
 
        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)

##        btn = platebtn.PlateButton(panel, label="Test", style=platebtn.PB_STYLE_DEFAULT)
        btn = platebtn.PlateButton(panel, label="Gradient", style=platebtn.PB_STYLE_GRADIENT)
 
# Run the program
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm().Show()
    app.MainLoop()

The example above shows how to create a simple PlateButton. Included are two examples that show two different button styles. You'll need to uncomment one and comment out the other to see how they differ. Now let's see how we can add a menu to our PlateButton:

import wx
import wx.lib.platebtn as platebtn
 
class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Plate Button Tutorial")
 
        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
        
        menu = wx.Menu()
        for url in ['http://wxpython.org', 'http://slashdot.org',
                    'http://editra.org', 'http://xkcd.com']:
            menu.Append(wx.NewId(), url, "Open %s in your browser" % url)
                        
        btn = platebtn.PlateButton(panel, label="Menu", size=None, style=platebtn.PB_STYLE_DEFAULT)
        btn.SetMenu(menu)
        
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(btn, 0, wx.ALL, 5)
        panel.SetSizer(sizer)
 
# Run the program
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyForm().Show()
    app.MainLoop()

This sample is basically a stripped down example from the wxPython demo. The idea here is to create a menu and then use the PlateButton's "SetMenu" method to attach it to the button.

Wrapping Up

In this article, we learned about the following buttons: wx.Button, wx.ToggleButton, wx.BitmapButton, a couple generic buttons and the PlateButton. In the second half, we'll look at a couple more core widget buttons and Andrea Gavana's buttons! See you next time!

Note: The examples in this article were tested on Windows XP, wxPython 2.8.10.1 / 2.8.11.0 and Python 2.5

Copyright © 2024 Mouse Vs Python | Powered by Pythonlibrary