ButtonPad: A Simple, Limited Python GUI Toolkit Built on Tkinter
Posted by Al Sweigart in misc
GUI programming is tedious and you don't always need all the options and UI widgets they give you. I've created ButtonPad, a simple, intentionally-limited Python GUI framework for creating a grid of buttons, labels, and text boxes. I modelled them after the design of programmable stream deck or drum machine hardware. The source is on GitHub. It's built on top of tkinter, so it is lightweight and doesn't need additional GUI packages. Widget layout is done with a single comma-separated multiline string. No widget subclassing, no designer files, no sprawling boilerplate; just a layout string and some basic options. Still, I've made a surprising variety of simple apps with it which can all be run from the launcher program: python -m buttonpad
ButtonPad simplifies menubar creation and uses PyMsgBox for JavaScript-like dialog boxes (with alert()
, confirm()
, prompt()
, and password()
functions). You can set on_click, on_enter, and on_exit callback functions as well as tool tips. The ButtonPad window supports a status bar. You can set foreground and background color, button size, spacing, and font face/size. Other than that, it's strictly for simple GUI app creation, suited for:
- Beginners who want to learn GUI programming without wrestling with verbose frameworks.
- Experienced developers who want to crank out prototypes, internal tools, game ideas, or teaching demos fast.
To install, run pip install ButtonPad
. To run the launcher program to view the included examples, run python -m buttonpad
. These examples include:
- Pocket calculator app
- Connect Four game
- Conway's Game of Life simulation
- Dodger Race game (an original creation)
- Emoji Copy Pad (a one-click way to copy emoji to the clipboard; uses pyperclip)
- Fish tank simulation
- Flood It clone game
- Gomoku board game
- Lights Out clone game
- Magic 8 ball fortune teller
- Memory puzzle game
- Othello puzzle game (two-player and vs computer)
- Peg solitaire board game
- SameGame clone game
- Simon memory testing game
- 15-tile slide puzzle
- Stopwatch app
- Tic Tac Toe (two-player and vs computer)
- 2048 clone game
TODO screenshots
Layout Configuration and Available Widgets
You can configure the buttons with a comma-separated multiline string. For example, here's a simple ButtonPad program that creates a phone keypad:
import buttonpad
bp = buttonpad.ButtonPad(
"""1,2,3
4,5,6
7,8,9
*,0,#""",
title="Telephone Keypad Demo",
)
bp.run() # Start the GUI event loop.
When you run this program, it looks like this:
TODO SCREENSHOT
By default, the on_click action for a button prints the text on the button. The buttons have default text and background colors, and they automatically resize as the window changes its size.
ButtonPad offers button widgets, but also text boxes, labels, and images.
If you want a text box, enclose the layout text in [square brackets]. If you just want a text label instead of a button, enclose the layout text in 'single quotes' or "double quotes". If you want an image, use IMG_
followed by an absolute or relative file path to the image file. For example, here's a ButtonPad app that has a button, two text labels, a text box, and an image:
import buttonpad
bp = buttonpad.ButtonPad(
"""Button, 'Label 1', "Label 2", [Text Box], IMG_~/monalisa.png""",
title="Widget Demo",
)
bp.run() # Start the GUI event loop.
When you run this program, it looks like this:
TODO SCREENSHOT
If Pillow isn't installed, then the image is shown in its grid cell at its normal size. If Pillow is installed, then ButtonPad automatically resizes it to fit proportionately in its cell. (You can also have it stretch to always fill the cell, which is discussed later.)
You can merge cells in the grid together by having identically named widgets in the layout string:
import buttonpad
bp = buttonpad.ButtonPad(
"""
Hello, Hello, A, B
Hello, Hello, C, D
Are all your pets named Eric?,Are all your pets named Eric?,Are all your pets named Eric?,Are all your pets named Eric?
""",
title="Merge Demo",
)
bp.run() # Start the GUI event loop.
There is one "Hello" button that occupies a 2 x 2 area and a "Are all your pets named Eric?" button that spans across a 4 x 1 area at the bottom. When you run this program, it looks like this:
TODO SCREENSHOT
If you want to use the same text for a widget but don't want them merged, begin the label with a ` backtick character:
import buttonpad
bp = buttonpad.ButtonPad(
"""
`Hello, `Hello, `Hello, `Hello
`Hello, `Hello, `Hello, `Hello
`Hello, `Hello, `Hello, `Hello
`Hello, `Hello, `Hello, `Hello
""",
title="No-Merge Demo",
)
bp.run() # Start the GUI event loop.
This program creates sixteen separate "Hello" buttons in a 4 x 4 grid. When you run this program, it looks like this:
TODO SCREENSHOT
Features
ButtonPad offers six kinds of UI widgets:
- Buttons, which have a text label and slight animation when clicked.
- Labels, which are just text.
- Text boxes, which are editable, multiline text fields.
- Images, containing an image stretched to fill the cell.
- Status bar, displaying text at the bottom of the window.
- Menu bar, offering menus and submenus.
Buttons, labels, text boxes, and images can all have callback functions for mouse clicks, mouse enter/exits. Their click callback function can be associated with a hotkey. They can have tooltips that appear when the mouse hovers over them.
Widgets all have a text_color
and background_color
setting that can be an HTML-style RGB color value like '#000000'
, '#ff00ff'
, or '#FF00FF'
. They can also be a
ButtonPad Customization
You can make general customizations to the ButtonPad by passing it different arguments. Here's the def
statement of the ButtonPad
class's __init__()
method with its parameters and default arguments:
class ButtonPad:
def __init__(
self,
layout: str, # """Button, 'Label 1', "Label 2", [Text Box], IMG_~/monalisa.png"""
cell_width: Union[int, Sequence[int]] = 60, # width of each grid cell in pixels; int for all cells or list of ints for per-column widths
cell_height: Union[int, Sequence[int]] = 60, # height of each grid cell in pixels; int for all cells or list of ints for per-row heights
h_gap: int = 0, # horizontal gap between cells in pixels
v_gap: int = 0, # vertical gap between cells in pixels
window_color: str = '#f0f0f0', # background color of the window
default_bg_color: str = '#f0f0f0', # default background color for widgets
default_text_color: str = 'black', # default text color for widgets
title: str = 'ButtonPad App', # window title
resizable: bool = True, # whether the window is resizable
border: int = 0, # padding between the grid and the window edge
status_bar: Optional[str] = None, # initial status bar text; None means no status bar
menu: Optional[Dict[str, Any]] = None, # menu definition dict; see menu property for details
):
TODO - can dynamically update layout
Button Customization
TODO - you'll never create the widget objects directly, but you can change them after the ButtonPad object has been amde.
import buttonpad
bp = buttonpad.ButtonPad(
"""Button, 'Label 1', "Label 2", [Text Box], IMG_~/monalisa.png""",
title="Widget Demo",
)
# TODO
bp.run() # Start the GUI event loop.
Label Customization
import buttonpad
bp = buttonpad.ButtonPad(
"""Button, 'Label 1', "Label 2", [Text Box], IMG_~/monalisa.png""",
title="Widget Demo",
)
# TODO
bp.run() # Start the GUI event loop.
Text Box Customization
import buttonpad
bp = buttonpad.ButtonPad(
"""Button, 'Label 1', "Label 2", [Text Box], IMG_~/monalisa.png""",
title="Widget Demo",
)
# TODO
bp.run() # Start the GUI event loop.
Image Customization
import buttonpad
bp = buttonpad.ButtonPad(
"""Button, 'Label 1', "Label 2", [Text Box], IMG_~/monalisa.png""",
title="Widget Demo",
)
# TODO
bp.run() # Start the GUI event loop.