BASIC OPERATIONS





BASIC OPERATIONS

The major difference between Perl- and Python-based Tk programming is how we interact with the widgets that we create. Both systems work by creating new widgets which return objects and it's through these objects that you set their properties and create additional widgets, containers, and other interface elements to build up the individual window.

We'll have a look at four different aspects of Tk programming in relation to Perl and Python. We'll start with the basics of creating widgets before moving on to look at how we set their properties and other values. Then we'll look at how to define widget callback before finally looking at the different widget geometry systems (Packer, Placer, and Grid).

Creating widgets

To actually load the Perl interface to Tk we use:

use Tk;

With Python the interface is called Tkinter – you can import this module by name as:

import Tkinter

It's actually more convenient to import the objects in the module into the current namespace, because there are so many object classes defined within the module that always using the explicit name gets tiresome. It's usually enough to use:

from Tkinter import *

Although we use objects in both cases, the interaction between the objects you create and Perl and Python differs slightly. In Perl, to create a window and then to create a button widget within it, we first create our window object and then call the Button method on the new window to create the button, i.e.:

$root = MainWindow–>new();
$button = $root–>Button();

With Python, we do refer to the window that we created, but don't use a method on the window object to create the button, i.e.:

root = Tk()
button = Button(root)

Note that we've also essentially set up the relationship in reverse. In Perl the root window (or other container widget) creates the objects it owns through a series of methods. Python uses classes to create new instances of widgets which in turn accept the object to which they belong as an argument. In short, a Perl/Tk widget creates widgets it owns, and a Python/Tk widget adopts a parent widget when created.

For cross reference, I've included a list of widgets that are supported by both Perl and Python in Figure.

Some of the widgets supported by Tk
Widget class Description
BitmapImage A subclass of the Image widget for displaying bitmap images
Button A simple push-button widget with similar properties to the Label widget
Canvas A drawing area into which you can place circles, lines, text, and other graphic artefacts
Checkbutton A multiple-choice button widget, where each item within the selection can be selected individually
Entry A single-line text entry box
Frame A container for arranging other widgets
Image A simple widget for displaying bitmaps, pixmaps (color bitmaps), and other graphic objects
Label A simple box into which you can place message text (noneditable)
Listbox A multiline list of selection choices
Menu A list of menu selections that can be made up of Label, Message, Button, and other widgets
Menubutton A menu (within a single menu bar) that lists the selections specified in a Menu object
Message A multiline Label object (noneditable)
OptionMenu A special type of Menu widget that provides a pop-up list of items within a selection
PhotoImage A subclass of the Image widget for displaying full-color images
Radiobutton A multiple-choice button widget, where you can choose only one of a multiple of values
Scale A slider that allows you to set a value according to a specific scale
Scrollbar A slider for controlling the contents of another widget, such as Text or Canvas
Text A multiline text widget that supports editable text that can be also be tagged for display in different fonts and colors
Toplevel A window that will be managed and dressed by the parent window

For a simple example of this process in action, here's a Perl script to display a simple "Hello world" button which terminates the application when pressed:

use Tk;

$root = MainWindow->new();
$root->title("Hello world!");
$button = $root->Button(text => 'Hello world',
                       command => \&quit_callback);

$button->pack();
MainLoop();

sub quit_callback
{
    exit(0);
}

The same script in Python looks like this:

import sys
from Tkinter import *

def main():
    root = Tk()
    button = Button(root,
                    text = 'Hello world',
                    command = quit_callback)

    button.pack()
    root.mainloop()

def quit_callback():
    sys.exit(0)

main()

There is only one slight difference between the overall layout of these two scripts. In the Python version we create a main function which actually does all the work of setting up the environment. Using a separate function is not a requirement, just the normal mode of operation for most Python scripts.

The other major difference is how we create a container window for our widgets. In Perl we do this by calling the new() method on the MainWindow class:

$root = MainWindow->new();

In Python, we create a new instance of the Tk class:

root = Tk()

Finally, the two scripts differ in the way we call the main loop function which then services requests and handles the callbacks and events that make up the application. In Perl we call the MainLoop() function. In Python we need to call the mainloop() method for the main window.

Setting properties

Widget properties are set in Perl by supplying the property names and values as list, using hash notation, when the widget was created. For example, to set the text label and callout for a button we'd probably use something like this:

$button = $root->Button(text => 'Hello, world',
                      command => \&quit_callback);

In Python we can more or less do the same – we supply the properties as part of the call when creating a class instance for the widget you are creating. With Python though we use keyword arguments:

button = Button(root, text = 'Hello, world', command =
quit_callback)

For reference, a list of the generic properties supported by most widgets is shown in Figure.

Generic widget properties
Property Description
font The font name in X or Windows format
background, bg The color of the background, specified either by a name or hexadecimal RGB value
foreground, fg The color of the foreground, specified either by a name or hexadecimal RGB value
text The string to be displayed within the widget, using the fore-ground and font values specified
image, bitmap The image or bitmap file to be displayed within the widget
relief The style of the widget's border, which should be one of raised, sunken, flat, ridge, or groove
borderwidth The width of the relief border
height The height of the widget, specified in the number of characters for labels, buttons, and text widgets, and in pixels for all other widgets
width The width of the widget, specified in the number of characters for labels, buttons, and text widgets, and in pixels for all other widgets
textvariable The name of a variable to be used and/or updated when the widget changes
anchor Defines the location of the widget within the window, or the location of the text within the widget; valid values are n, ne, e, se, s, sw, w, nw, and center

Note that some properties in some widgets share the same name as a Python keyword. These will often generate errors if you try to use a keyword argument as a property name as Python will misinterpret the name as the keyword. Most notably the from property used in Scale and Scrollbar widgets is known to cause problems. In this instance an alias normally exists – for example from becomes from_.

Changing existing properties

To change the properties of a widget after it has been created we need to use the configure method in Perl, again supplying a hash as the arguments:

$button->configure(text => 'Not hello world');

With Python each widget object operates like a dictionary, so we can update values directly by setting the corresponding property:

button['text'] = 'Hello, world'

To set multiple properties use the configure() method as in Perl:

Button.configure(text = 'Hello, world',
                command = quit_callback)
Obtaining existing properties

To get a property back under Perl you must use the cget() method:

$buttontext = $button->cget('text');

The same method works in Python:

buttontext = button.cget('text')

However, it's probably easier to get the dictionary element directly:

buttontext = button['text']

Widget variables

When working with widgets that use variables to store their information Python and Tkinter is much more strict about the type of variable that it will work with. In Perl for example when creating a Checkbutton that has an on or off value you simply assign a reference to the variable that will hold the Checkbutton's value:

Checkbutton(variable => \$cbvalue)

The same rule applies to other variables used in conjunction with other widgets – for example, the script below uses two Scale widgets to allow you to convert between measurements in metres and feet, with the variables holding this information being the scalars $feetscale and $metrescale:

use Tk;

my ($feetscale, $metrescale) = (0,0);

$main = MainWindow->new();

$feetframe = $main->Frame()->pack(side => 'left');

$feetframe->Scale(command     => \&update_feet,
                 variable     => \$feetscale,
                 width        => 20,
                 length       => 400,
                 orient       => 'vertical',
                 from         => 0,
                 to           => 328,
                 resolution   => 1,
                 tickinterval => 25,
                 label        => 'Feet'
                 )->pack(side => 'top');

$feetframe->Label(textvariable => \$feetscale)-
>pack(side => 'top',
    pady => 5);

$metreframe = $main->Frame()->pack(side => 'left');

$metreframe->Scale(command     => \&update_metre,
                  variable     => \$metrescale,
                  width        => 20,
                  length       => 400,
                  orient       => 'vertical',
                  from         => 0,
                  to           => 100,
                  resolution   => 1,
                  tickinterval => 10,
                  label        => 'Metres'
                  )->pack(side => 'top');

$metreframe->Label(textvariable => \$metrescale)-
>pack(side => 'top',
    pady => 5);

MainLoop();

sub update_feet
{
    $metrescale = $feetscale/3.280839895;
}

sub update_metre
{
    $feetscale = $metrescale*3.280839895;
}

In Python we need to create a suitable Tk variable to hold the Checkbutton's value – you cannot use standard Python variables:

cbvar = IntVar()
cb = Checkbutton(master, text="Expand", variable=cbvar)

Tkinter actually defines a number of different variable types which you can use, these include IntVar(), FloatVar(), StringVar(), and BooleanVar().

You cannot set or get the values in these variables directly, instead, use the set() and get() methods. You can see this more clearly in the Python version of the metre/feet conversion application shown here:

from Tkinter import *

def main():
    main = Tk();

    global feetscale, metrescale
    feetscale = IntVar()
    metrescale = IntVar()
    feetframe = Frame(main)

    Scale(feetframe,
          command      = update_feet,
          variable     = feetscale,
          width        = 20,
          length       = 400,
          orient       = 'vertical',
          from_        = 0,
          to           = 328,
          resolution   = 1,
          tickinterval = 25,
          label        = 'Feet'
          ).pack(side  = 'top')

    Label(feetframe,
          textvariable = feetscale
          ).pack(side = 'top',
                 pady = 5)

    feetframe.pack(side = 'left')

    metreframe = Frame(main)

    Scale(metreframe,
          command      = update_metre,
          variable     = metrescale,
          width        = 20,
          length       = 400,
          orient       = 'vertical',
          from_        = 0,
          to           = 100,
          resolution   = 1,
          tickinterval = 10,
          label        = 'Metres'
          ).pack(side  = 'top');

    Label(metreframe,
          textvariable = metrescale
          ).pack(side = 'top',
                 pady = 5)

    metreframe.pack(side = 'left');

    main.mainloop()

def update_feet(self):
    global metrescale, feetscale
    metrescale.set(int(feetscale.get()/3.280839895))

def update_metre(self):
    global metrescale, feetscale
    feetscale.set(int(metrescale.get()*3.280839895))

main()

You can see here that the two callbacks which contain the calculations required to convert between the two measurements require the get() and set() methods.

Widget callbacks

Callbacks in both languages are just functions that you define which are called when a particular event occurs. With Perl the function is "dumb" and not provided with information when it is called, although you could conceivably discover this information through closures. This means that to modify the value of a widget you must use global variables to hold the widgets you expect to modify. It also means that we cannot change the properties within the widget that triggered the event. For example, to change our 'Hello world' example so that it changed the message text to 'Quit' and changed the callback method to the real quit_callback() function:

sub change_message
{
    $button->configure(text => 'Quit!',
                     command => \&quit_callback);
}

Under Python, all callback functions are called with a single argument – the widget which invoked it. Using this object we can modify the widget that invoked the callback directly:

def change_message(self)
{
    self.configure(text = 'Quit!',
                 command = quit_callback);
}

Also note that when assigning a callback to a property in Python we do not need to explicitly pass a reference to the callback function – Python automatically supplies a reference to the function.

Widget geometry

Both language's implementations of Tk support three different geometry systems for organizing widget layouts within windows or other container widgets. The three systems are Packer, Grid, and Placer. The basic operation for all three systems is identical –you call the corresponding method (pack(), grid(), or place()) supplying options. In Perl you do this through a hash and in Python through a series of keyword arguments.

For example, to pack a widget aligned to the top of a parent frame widget in Perl we'd probably use something like:

$button->pack(side => 'top');

Whilst in Python we'd use:

button.pack(side = 'top')

Also note that because of the way Tkinter works there are some small differences in the order in which you should pack your objects. In Perl it's popular to both create and pack the widget at the same time, i.e.:

$feetframe = $main->Frame()->pack(side => 'left');

We can then populate the frame with the other widgets without having to redefine the geometry options for the container widget.

In Python we can do the same direct operation:

feetframe = Frame(main).pack(side = 'left')

But we don't physically get the same results. What actually happens is that Packer tries to pack the widget into the frame before being aware of the other objects. In this example, taken from our earlier metre/feet conversion scale application, we end up with two scale widgets, one on top of the other, rather than next to each other, even though in all other respects the formatting and definition of the widgets and calls to Packer are the same.


     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows