Introduction

The completed interface built during this tutorialThis is a simple Python ttk tutorial which will introduce some of the basic features of ttk in Python 3. It is based on the first real example from the tutorial on tkdocs.com. This version has been wrapped in a class and has had a simple feature or two added. I will use the product from this tutorial as the starting point for a future one.

If you don’t like copying and pasting lots of little bits of code, or indeed typing, you can download the code for this tutorial from the links section near the end.

The screen shot shows the user interface that the tutorial will produce. The features are simple.

  • Convert a numeric value entered from feet to metres.
  • Convert a numeric value entered from metres to feet if the option is selected.
  • Update the user interface text to show the proper conversion.
  • Completely ignore invalid data.

On with the tutorial code

As shown in line number 1, this is a Python 3 project. Lines 2 and 3 import the tkinter module and the themed tool kit. Readers who are used to Python 2 should note that the module names are in lower case.

#!/usr/bin/env python3
from tkinter import *
from tkinter import ttk

This is nothing out of the ordinary. It is simply a Python dictionary with keys for each piece of interface text added.

INTERFACE_TEXT = {}
INTERFACE_TEXT['window_title'] = 'Convert-O-Matic'
INTERFACE_TEXT['calculate'] = 'Calculate'
INTERFACE_TEXT['feet']= 'feet'
INTERFACE_TEXT['metres'] = 'metres'
INTERFACE_TEXT['equivalent'] = 'is equivalent to'
INTERFACE_TEXT['metres_to_feet'] = 'Convert metres to feet'

This is a decently named class and it derives directly from object. The parent could be omitted in such a case, but it is usually better to be explicit about such things.

class ConvertOMatic(object):

This is the function that calculates how many metres in n feet. The main point of interest in this method are the parameters, self is a reference to the current instance. This argument is not passed by your calling code, rather python passes it for you. The args parameter is a non-keyworded, variable-length argument list which is a fancy way of saying a list of optional parameters. Also of note are the from_value and to_value members, although these will be discussed a little later.

def __feetToMetres(self, *args):
    try:
        feet = float(self.from_value.get())
        if feet == 0:
            self.to_value.set(0)
        else:
            metres = (0.3408 * feet * 10000.0 + 0.5) / 10000.0
            self.to_value.set(metres)
    except ValueError:
        pass

This method should look very familiar.

def __metresToFeet(self, *args):
    try:
        metres = float(self.from_value.get())
        if metres == 0:
            self.to_value.set(0)
        else:
            feet = (3.2808 * metres * 10000.0 + 0.5) / 10000.0
            self.to_value.set(feet)
    except ValueError:
        pass

This function simply decides which of our calculations to call, and then calls it. The try / except block handles the edge case where the metres_to_feet member may not exist.

def __calculate(self, *args):
    try:
        value = int(self.metres_to_feet.get())
    except ValueError:
        value = 0

    if value == 1:
        self.__metresToFeet(self, args)
    else:
        self.__feetToMetres(self, args)

This method sets the proper text labels in the interface using the dictionary we defined earlier. There is nothing else new in this method.

def __setTextLabels(self):
    try:
        value = int(self.metres_to_feet.get())
    except ValueError:
        value = 0

    if value == 1:
        self.from_label.set(INTERFACE_TEXT['metres'])
        self.to_label.set(INTERFACE_TEXT['feet'])
    else:
        self.from_label.set(INTERFACE_TEXT['feet'])
        self.to_label.set(INTERFACE_TEXT['metres'])

This is the beginning of the constructor. kwargs is like the args parameter discussed earlier, with the exception that it receives a list of named arguments.

The member variables here are of the StringVar type. These will be bound to TTK widgets in such a way that reading the member’s value will return the widget’s value and updating it will automatically update the widget. This does feel a little strange at first, but it has the benefit of keeping your code easy to read.

def __init__(self, master, **kwargs):
    self.from_value = StringVar()
    self.to_value = StringVar()
    self.from_label = StringVar()
    self.to_label = StringVar()
    self.metres_to_feet = StringVar()

This code simply creates and positions the widgets in the window. To be more precise, in a frame which is in the window. The main window is not themed which means that placing widgets directly on the window would look odd as the background colours would not match. At the same time we create a grid to arrange further widgets.

Each widget is created and assigned a position within the grid. This uses a row and column co-ordinate system which starts counting from one. The sticky argument defines how the widget aligns in the grid, using a compass system. West or W is aligned to the left.

The StringVar members created earlier are bound to the widgets using the textvariable argument.

master.title(INTERFACE_TEXT['window_title'])

mainframe = ttk.Frame(master, padding="3 3 12 12")
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)

from_entry = ttk.Entry(mainframe, width=7, textvariable=self.from_value)
from_entry.grid(column=2, row=1, sticky=(W, E))

ttk.Checkbutton(mainframe, text=INTERFACE_TEXT['metres_to_feet'], command=self.__setTextLabels, variable=self.metres_to_feet).grid(column=1, row=3, sticky=W)
ttk.Label(mainframe, textvariable=self.to_value).grid(column=2, row=2, sticky=(W, E))
ttk.Button(mainframe, text=INTERFACE_TEXT['calculate'], command=self.__calculate).grid(column=3, row=3, sticky=W)

ttk.Label(mainframe, textvariable=self.from_label).grid(column=3, row=1, sticky=W)
ttk.Label(mainframe, textvariable=self.to_label).grid(column=3, row=2, sticky=W)
ttk.Label(mainframe, text=INTERFACE_TEXT['equivalent']).grid(column=1, row=2, sticky=E)

self.__setTextLabels()

for child in mainframe.winfo_children():
    child.grid_configure(padx=5, pady=5)

Finally, the text box widget is given focus and both the return and keypad enter keys are bound to the same method as the calculate button.

from_entry.focus()
root.bind('<KP_Return>', self.__calculate)
root.bind('<KP_Enter>', self.__calculate)

The window is created, the class is instantiated and away it goes!

root = Tk()
app = ConvertOMatic(root)
root.mainloop()

This is the complete working program. The code is flawed in many ways, including being difficult to support and extend as well as being almost impossible to automatically test. A future tutorial or two will look at re-factoring this program to fix many of these flaws.

Links

The complete source code.

Leave a Reply

Your email address will not be published. Required fields are marked *