Home Contact

TkDocs

Information you need to build high-quality Tk user interfaces.

This tutorial will quickly get you up and running with the latest Tk from Tcl, Ruby, Perl or Python (coming soon) on Mac, Windows or Linux. It provides all the essentials about core Tk concepts, the various widgets, layout, events and more that you need for your application.

 

Introduction

This tutorial is designed to help people get up to speed quickly with building mainstream desktop graphical user interfaces with Tk, and in particular Tk 8.5, which is an incredibly significant milestone release.

The downside is that unless you know one or two particular things, it's actually not that significant a release; For backwards compatibility reasons, unless existing programs make a few simple changes, they won't look all that much different. So while this tutorial will certainly benefit newcomers to Tk, it will also help existing Tk developers bring their knowledge right up to date. It's a cliche, but I can't believe how much I've learned in writing this tutorial, and I've been using Tk for over fifteen years.

The general state of Tk documentation (outside the Tcl-oriented reference documentation, which is excellent) is unfortunately not at a high point these days. This is particularly true for developers using Tk from languages other than Tcl, and developers working on multiple platforms.

So this tutorial will, as much as possible, target developers on the three main platforms (Windows, Mac, Linux), and also be language-neutral. Initially, the tutorial will cover Tcl, Ruby and Perl, with Python soon to follow. Over time, additional languages will be added. Even if your own language isn't included, the chances are you'll still benefit; since all the languages use the same underlying Tk library, there's obviously a lot of overlap.

This is also not a reference guide, it's not going to cover everything, just the essentials you need in 95% of applications. The rest you can find in reference documentation.

 

Who this Tutorial is for

This tutorial is designed for developers building tools and applications in Tk. It's also concerned with fairly mainstream graphical user interfaces, with buttons, lists, checkboxes, richtext editing, 2D graphics and so on. So if you're either looking to hack on Tk's internal C code, or build the next great 3D immersive game interface, this is probably not the material for you.

This tutorial also doesn't teach you the underlying programming language (Tcl, Ruby, Perl, etc.), so you should have a basic grasp on that already. Similarly, you should have a basic familiarity with desktop applications in general, and while you don't have to be a user interface designer, some appreciation of GUI design is always helpful.

 

Modern Best Practices

This tutorial is all about building modern Tk user interfaces using the current tools Tk has to offer. It's all about the best practices you need to know to do this.

For most tools, you wouldn't think you'd have to say something like that, but for Tk that's not the case. Tk has had a very long evolution (see Tk Backgrounder), and any evolution tends to leave you with a bit of cruft; couple that with how much graphical user interface platforms and standards have evolved in that time, and you can see where keeping something as large and complex as a GUI library up to date as well as backwards compatible may be challenging.

Tk has, in recent years, gotten a bad rap, to put it mildly. Some of this has been well deserved, most of it not so much. Like any GUI tool, it can be used to create absolutely terrible looking and outdated user interfaces, but with the proper care and attention, it can also be used to create spectacularly good ones as well. Most people know about the crappy ones; most of the good ones people don't even know are done in Tk. In this tutorial, we're going to focus on what you need to build good user interfaces, which isn't nearly as hard as it used to be before Tk 8.5.

So modern desktop graphical user interfaces, using modern conventions and design sense, using the modern tools provided by Tk 8.5.

Tk Extensions

When it comes to modern best practices, Tk extensions deserve a special word of note. Over the years, a number of groups have provided all kinds of add-ons to Tk, for example adding new widgets not available in the core (or at least not at the time). Some well-known and quite popular Tk extensions include BLT, Tix, iWidgets, BWidgets; there are many, many others.

Many of these extensions were created years ago. Because core Tk has always been highly backwards compatible, these extensions generally continue to work with newer versions. However, many have not been updated, or not been significantly updated, in a long time. They may not reflect current platform conventions or styles, and so while they "work", they can make your application appear extremely dated or out of place.

If you do decide to use Tk extensions, it's highly recommended that you investigate and review your choices carefully.

 

The Better Way Forward

Tk also gives you a lot of choices. There are at least six different ways to layout widgets on the screen, often multiple different widgets that could accomplish the same thing, especially if you count the huge assortment of Tk extensions like Tix, BLT, BWidgets, Itk and others. Most of these also are older, most not updated and therefore crappy looking, and in many cases, the facilities they provide have been obsoleted by newer and more modern facilities recently built into Tk itself. But for backwards compatibility reasons, most of these old ways of doing things still keep working, year after year. That doesn't necessarily mean people should still be using some of them.

So there are a lot of choices in Tk, but frankly, all that choice gets in the way. If you want to learn and use Tk, you don't need all the choices, you need the right choice, so you don't have to do all the research and make that choice yourself. That's what this tutorial will give you. Think of it as the documentation equivalent of opinionated software. So we'll often use different ways of doing things than in other documentation or examples; often, it's because when those were written, the better ways didn't even exist yet. Later on, once you're an expert, and you're encountering some wacky situation where the normal choice doesn't fit, you can go hunt around for alternatives.

 

How to Use

While the tutorial is designed to be used linearly, feel free to jump around as you see fit. We'll often provide links where you can go for more information, whether links to other documentation on this site, such as our "widget roundup" providing usage info on each Tk widget, or to external documentation, such as the full reference for a particular command.

The tutorial also lets you select what language (Tcl, Ruby, or Perl) to show. You can change this by the "Show:" popup menu which is located in the sidebar, near the top right of each page in the tutorial. But it also lets you see how Tk is used by all the different languages, which can itself be quite interesting and useful.

Conventions

As is typically done, code listings, interpreter or shell commands and responses will be indicated with a fixed-width font. When showing an interactive session with the interpreter, the parts that you enter will additionally be in bold fixed-width.

When describing procedure or method calls, the literal parts (e.g. the method name) will be in plain fixed-width font, parameters where you should fill in the actual value will add italics, and optional parameters will be surrounded by '?', e.g. "set variable ?value?".

A number of icons appearing to the left of text are used, as follows:

This paragraph consists of material that is specific to the Tcl binding to Tk.

This paragraph consists of material that is specific to the Ruby binding to Tk.

This paragraph consists of material that is specific to the Perl binding to Tk.

This paragraph will help point out common mistakes that people make, or suggest useful but not necessarily obvious solutions related to the topic.

This indicates a new way of doing things in Tk 8.5 that is very different from the way things would have been done previously. People familiar with older versions of Tk, or working on programs developed with older versions of Tk, should pay close attention.

This paragraph provides some additional background information, not strictly necessary to understanding the topic at hand, but that might help you understand a bit more about how or why things are done the way they are.

This indicates some error in the tutorial itself which hasn't yet been corrected, or a section that has been deleted but not yet replaced.

This indicates an area in Tk that could most charitably be described as a "rough edge". It may indicate a bad or missing API requiring you to use a workaround in your code. Because these things tend to get fixed up over time, it's worth marking them in your code with a "TODO" so you can remember to go back later and see if a newer API resolves the problem cleanly.

Installing Tk

In this chapter, you'll get Tk installed on your machine, verify it works, and then see a quick example of what a Tk program looks like.

Though pretty much all Mac OS X and Linux machines come with Tk installed already, it's usually an older version (typically 8.4.x). You want to make sure you've got at least version 8.5 to use the new widget set, so if that's not already there, you'll want to install the newer version.

Though there are lots of ways to install Tk, the easiest is to download and install one of the versions provided by ActiveState (www.activestate.com).

ActiveState is a company that sells professional developer tools for dynamic languages. They also provide (for free) quality-controlled distributions of some of these languages, and happen to employ a number of core developers of these languages.

 

Installing Tk on Mac OS X

On Mac OS X, the easiest way to get Tk is to install the "ActiveTcl" distribution from ActiveState, which includes Tcl, Tk, plus a number of other extension libraries.

In your web browser, go to www.activestate.com, and follow along the links to download the standard version of ActiveTcl, available as a universal binary. Make sure you're downloading an 8.5.x version, not an older 8.4.x version.

Run the installer to get everything loaded onto your machine. When you're done, you'll find a shiny new application called "Wish 8.5" inside the Utilities folder of your Applications folder. This is the "wish" shell, an application that includes both Tcl and Tk.

If you launch that application, you'll see two windows popup (see below), one titled "Wish" which will contain your application, and the second titled "Console" which is where you can type in Tcl/Tk commands.


The Wish application running on Mac OS X.

For convenient use from the Unix command line, you'll also find a script installed as /usr/local/bin/wish8.5 which will launch the same application.

Installing Tk for Ruby on Mac OS X is one of those "good news, bad news" situations. The good news is that both are actually already installed in the base operating system (in Tiger and Leopard). The bad news is that it's an older version of Tk (8.4.7), whereas we really want an 8.5 version.

To check to see exactly what version of Tk your copy of Ruby is using, from 'irb' run:

require 'tk'
Tk::TK_PATCHLEVEL
In an ideal world, this should return 8.5.0 or newer. But it will probably tell you it's an older version.

The new themed widgets set in Tk 8.5 is derived from a Tk extension called Tile. Tile has been available for Tk 8.4.x, and has been bundled with Ruby's Tk bindings. Despite this, let me emphasize again that you really want a current 8.5.x version of Tk.

RubyTk is a binding that links against an existing but separate Tk library. So, to get the latest version of Tk for Ruby, we're going to have to do two things, first download the latest 8.5.x Tcl/Tk version from ActiveState, and then compile (or re-compile) Ruby to use it.

Install ActiveTcl

The "ActiveTcl" distribution from ActiveState contains the latest Tk, as well as the latest version of Tcl (which Ruby's Tk bindings use internally to talk to Tk). In your web browser, go to www.activestate.com, and follow along the links to download the standard version of ActiveTcl, available as a universal binary. Again, make sure you're downloading an 8.5.x version, not an older 8.4.x version.

Run the installer and everything will be loaded onto your machine.

Recompile Ruby

Make sure you've got Apple's developer tools (i.e. Xcode, which includes gcc and friends). Then if you haven't already got it, go to www.ruby-lang.org to download the latest stable (1.8.x, not 1.9.x) version of Ruby.

Unpack it, and then from the Unix command line, run (note that the "configure" command should all be entered on one line, the <install-dir> should be replaced with the location you'd like your version of Ruby installed:

% ./configure --prefix=<install-dir>
		  --with-tcltk-framework=/Library/Frameworks
		  --enable-pthread
		  --enable-shared
% make && make install

The "/Library/Frameworks" directory is where ActiveTcl installs its libraries, as opposed to "/System/Library/Frameworks" which is used by the version of Tcl/Tk that comes with OS X.

To verify that everything worked, start up your newly compiled copy of 'irb', and type:

% require 'tk'
% Tk::TK_PATCHLEVEL

The first line should load RubyTk; typically if there was a problem with compiling it would show up here. The second line will return the version of Tk that you're running, which should be something like "8.5.2".

Verified steps using ActiveTcl 8.5.2, and Ruby 1.8.7.

For modern Tk programming using Perl, the "Tkx" module is highly recommended, and we'll be using that here. The easiest way to get set up is to use the "ActiveTcl" and "ActivePerl" distributions from www.activestate.com. This involves two easy steps (installing each of ActiveTcl and ActivePerl) and one slightly finicky one (making them talk to each other). Don't skip this last step!

Install ActiveTcl 8.5.x

The "ActiveTcl" distribution from ActiveState contains the latest Tk, as well as the latest version of Tcl (which Tkx uses internally to talk to Tk). In your web browser, go to www.activestate.com, and follow along the links to download the standard version of ActiveTcl, available as a universal binary. Make sure you're downloading an 8.5.x version, not an older 8.4.x version.

Run the installer and everything will be loaded onto your machine.

Install ActivePerl

Also from ActiveState, download and install the latest ActivePerl distribution.

ActivePerl, Meet ActiveTcl 8.5.x

ActivePerl actually bundles a version of Tk, as well as several Tk extensions like Tile (the precursor to the themed Tk widgets in Tk 8.5). Unfortunately, many versions of ActivePerl bundle 8.4.x, and not 8.5.x. We really want to use 8.5.x.

There are definite incompatibilities between the versions of Tk 8.4.x and Tile bundled with ActivePerl and 8.5.x, so again, please use 8.5. If you do insist on ignoring this advice, you're on your own, but you might at least want to know that you'll need to include "Tkx::package_require('tile')" in your scripts.

To find out what version of Tk Perl and Tkx are using, run this from the command line:

% perl -MTkx -e 'print Tkx::info("patchlevel");'

We want this to be returning something like "8.5.2"; if it returns something like "8.4.17" you've got more work to do. To change the version of Tk that Tkx uses, we'll need to set the PERL_TCL_DL_PATH environment variable to point to the version of Tcl installed by ActiveTcl. Typically you'll set this up in your .profile, .cshrc or other startup script. You might use something like:

% setenv PERL_TCL_DL_PATH /Library/Frameworks/Tcl.framework/Versions/Current/Tcl

Then, when you run the 'Tkx::info("patchlevel")' command again, it should return an 8.5.x version of Tcl/Tk.

 

Installing Tk on Windows

On Windows, the easiest way to get Tcl/Tk onto your machine is to install the "ActiveTcl" distribution from ActiveState, which includes Tcl, Tk, plus a number of other extension libraries.

In your web browser, go to www.activestate.com, and follow along the links to download the standard version of ActiveTcl for Windows. Make sure you're downloading an 8.5.x version, not an older 8.4.x version.

Run the installer, and follow along. You'll end up with a fresh install of ActiveTcl, usually located in C:\Tcl. From a DOS command prompt, or the Start Menu's "Run..." command, you should then be able to run a Tcl/Tk 8.5 shell via:

% C:\Tcl\bin\wish85

This should pop up a small window titled "wish85", which will contain your application. A second, larger window titled "Console" is where you can type in Tcl/Tk commands. Type "exit" in the console window to exit. You may also want to add C:\Tcl\bin to your PATH environment variable.

RubyTk is a binding that links against an existing but separate Tk library. So, to get the latest version of Tk for Ruby, we're going to have to do two things, first download the latest 8.5.x Tcl/Tk version from ActiveState, and then compile Ruby to use it.

Install ActiveTcl

The "ActiveTcl" distribution from ActiveState contains the latest Tk, as well as the latest version of Tcl (which Ruby's Tk bindings use internally to talk to Tk). In your web browser, go to www.activestate.com, and follow along the links to download the standard version of ActiveTcl for Windows. Again, make sure you're downloading an 8.5.x version, not an older 8.4.x version.

Run the installer and ActiveTcl will be loaded onto your machine, in "C:\Tcl".

Compile Ruby

In this section, we'll describe how to compile Ruby using Visual C++ (usually available as part of Visual Studio).

Yes, it can be done using tools like cygwin, but I still recommend doing it with Visual C++. This shouldn't be an issue even for the rabid "free software" types since they wouldn't touch a Windows machine anyway...

Compiling Ruby so that it finds the correct Tk distribution and so on is incredibly finicky, and depends on environment variables that have to be defined before compiling. If everything isn't just right, it will compile okay, but doing "require 'tk'" will fail.

Install Visual C++

Make sure you have a version of Visual C++ installed on your machine, including the command line tools (e.g. nmake). This can be done for example by installing Visual Studio.

Even fairly old versions (but at least 5.0) of Visual C++ should work. As well, Microsoft does offer an "Express" edition of Visual C++ for free, which also works just fine.

Make sure the Visual C++ environment variables are set, and that the Visual C++ tools are in your PATH; this can normally be done by running the "vcvars32.bat" script that is installed as part of Visual C++ (depending on your system, you may need to use the "vcvarsall.bat" script instead).

Download Ruby Source

Go to www.ruby-lang.org and download the latest stable (1.8.x, not 1.9.x) source code version of Ruby.

Create a directory that will contain both the source and compiled version, e.g. "C:\ruby". Unpack the source into a subdirectory of that e.g. "C:\ruby\ruby187".

The download will be in the form of a ".tar.gz" or ".tar.bz2" file; Winzip can unpack this for you.

Set Environment Variables

Open a command line, and execute the following DOS commands. Again, this is so that when you compile Ruby it will find the appropriate version of Tcl/Tk on your system. We add these items to the beginning of each environment variable to ensure they are found before anything else that might already by on our paths.

set INCLUDE=c:\tcl\include;%INCLUDE%
set LIB=c:\ruby\lib;c:\tcl\lib;%LIB%
set LIBPATH=c:\ruby\lib;c:\tcl\bin;c:\tcl\lib;%LIBPATH%
set PATH=c:\ruby\bin;c:\tcl\bin;%PATH%
set RUBYPATH=c:\ruby

You and your users will probably want most of these environment variables set to run your program, even after you've compiled Ruby. If you later get errors, particularly when trying to do a "require 'tk'", checking these environment variables is a good first step at troubleshooting.

Compile and Install

In the same command line window, type the following:

cd \ruby\ruby187
win32\configure.bat --prefix=c:\ruby
nmake
nmake DESTDIR=c:\ruby install

To verify that everything worked, start up your newly compiled copy of 'irb' (which would have been installed in "c:\ruby\bin" which is on your PATH), and type:

% require 'tk'
% Tk::TK_PATCHLEVEL

The first line should load RubyTk; typically if there was a problem with compiling it would show up here. The second line will return the version of Tk that you're running, which should be something like "8.5.2".

For modern Tk programming using Perl, the "Tkx" module is highly recommended, and we'll be using that here. The easiest way to get set up is to use the "ActiveTcl" and "ActivePerl" distributions from www.activestate.com. This involves two easy steps (installing each of ActiveTcl and ActivePerl) and one slightly finicky one (making them talk to each other). Don't skip this last step!

Install ActiveTcl 8.5.x

The "ActiveTcl" distribution from ActiveState contains the latest Tk, as well as the latest version of Tcl (which Tkx uses internally to talk to Tk). In your web browser, go to www.activestate.com, and follow along the links to download the standard version of ActiveTcl. Make sure you're downloading an 8.5.x version, not an older 8.4.x version.

Run the installer and everything will be loaded onto your machine.

Install ActivePerl

Also from ActiveState, download and install the latest ActivePerl distribution.

ActivePerl, Meet ActiveTcl 8.5.x

ActivePerl actually bundles a version of Tk, as well as several Tk extensions like Tile (the precursor to the themed Tk widgets in Tk 8.5). Unfortunately, many versions of ActivePerl bundle 8.4.x, and not 8.5.x. We really want to use 8.5.x.

There are definite incompatibilities between the versions of Tk 8.4.x and Tile bundled with ActivePerl and 8.5.x, so again, please use 8.5. If you do insist on ignoring this advice, you're on your own, but you might at least want to know that you'll need to include "Tkx::package_require('tile')" in your scripts.

To find out what version of Tk Perl and Tkx are using, run this from the command line:

% perl -MTkx -e "print Tkx::info('patchlevel');"

We want this to be returning something like "8.5.2"; if it returns something like "8.4.17" you've got more work to do. To change the version of Tk that Tkx uses, we'll need to set the PERL_TCL_DL_PATH environment variable to point to the version of Tcl installed by ActiveTcl. Typically you'll set this up in the 'System' control panel ('advanced' tab) but you can also just enter it on a command line before starting Perl.

% set PERL_TCL_DL_PATH=C:\Tcl\bin\tcl85.dll

Then, when you run the 'Tkx::info("patchlevel")' command again, it should return an 8.5.x version of Tcl/Tk.

 

Installing Tk on Linux

While Linux distributions pretty much all come with Tcl/Tk installed, most include Tk 8.4.x, and we want to make sure to get an 8.5.x version. The easiest way to do this is to install the "ActiveTcl" distribution from ActiveState, which includes Tcl, Tk, plus a number of other extension libraries.

In your web browser, go to www.activestate.com, and follow along the links to download the standard version of ActiveTcl for Linux. Make sure you're downloading an 8.5.x version, not an older 8.4.x version.

Unpack it, and run the installer (install.sh), and follow along. You'll end up with a fresh install of ActiveTcl, located in /opt/ActiveTcl-8.5. You should then be able to run a Tcl/Tk 8.5 shell via:

% /opt/ActiveTcl-8.5/bin/wish8.5

This should pop up a window titled "wish8.5". Type a control-D at the prompt in the terminal window to exit. You may also want to add /opt/ActiveTcl-8.5/bin to your Unix path.

While Linux distributions pretty much all come with Tk installed, and most with Ruby as well, we want to make sure to get the latest version, especially of Tk.

RubyTk is a binding that links against an existing but separate Tk library. So, to get the latest version of Tk for Ruby, we're going to have to do two things, first download the latest 8.5.x Tcl/Tk version from ActiveState, and then compile (or re-compile) Ruby to use it.

Install ActiveTcl

The "ActiveTcl" distribution from ActiveState contains the latest Tk, as well as the latest version of Tcl (which Ruby's Tk bindings use internally to talk to Tk). In your web browser, go to www.activestate.com, and follow along the links to download the standard version of ActiveTcl for Linux. Again, make sure you're downloading an 8.5.x version, not an older 8.4.x version.

Unpack it, and run the installer (install.sh), and follow along. You'll end up with a fresh install of ActiveTcl, located in /opt/ActiveTcl-8.5.

Recompile Ruby

Then if you haven't already got it, go to www.ruby-lang.org to download the latest stable (1.8.x, not 1.9.x) version of Ruby.

Unpack it, and then from the Unix command line, run (note that the "configure" command should all be entered on one line, the <install-dir> should be replaced with the location you'd like your version of Ruby installed, and note also the underscore in "--enable-tcltk_stubs"):

% ./configure --prefix=<install-dir>
	  --with-tcl-dir=/opt/ActiveTcl-8.5
	  --with-tk-dir=/opt/ActiveTcl-8.5
	  --with-tcllib=tclstub8.5 
	  --with-tklib=tkstub8.5
	  --enable-tcltk_stubs
	  --enable-tcl-thread
	  --enable-pthread
% make && make install

To verify that everything worked, start up your newly compiled copy of 'irb', and type:

% require 'tk'
% Tk::TK_PATCHLEVEL

The first line should load RubyTk; typically if there was a problem with compiling it would show up here. The second line will return the version of Tk that you're running, which should be something like "8.5.2".

For modern Tk programming using Perl, the "Tkx" module is highly recommended, and we'll be using that here. The easiest way to get set up is to use the "ActiveTcl" and "ActivePerl" distributions from www.activestate.com. This involves two easy steps (installing each of ActiveTcl and ActivePerl) and one slightly finicky one (making them talk to each other). Don't skip this last step!

Install ActiveTcl 8.5.x

The "ActiveTcl" distribution from ActiveState contains the latest Tk, as well as the latest version of Tcl (which Tkx uses internally to talk to Tk). In your web browser, go to www.activestate.com, and follow along the links to download the standard version of ActiveTcl for Linux. Make sure you're downloading an 8.5.x version, not an older 8.4.x version.

Run the installer and everything will be loaded onto your machine.

Install ActivePerl

Also from ActiveState, download and install the latest ActivePerl distribution.

ActivePerl, Meet ActiveTcl 8.5.x

ActivePerl actually bundles a version of Tk, as well as several Tk extensions like Tile (the precursor to the themed Tk widgets in Tk 8.5). Unfortunately, many versions of ActivePerl bundle 8.4.x, and not 8.5.x. We really want to use 8.5.x.

There are definite incompatibilities between the versions of Tk 8.4.x and Tile bundled with ActivePerl and 8.5.x, so again, please use 8.5. If you do insist on ignoring this advice, you're on your own, but you might at least want to know that you'll need to include "Tkx::package_require('tile')" in your scripts.

To find out what version of Tk Perl and Tkx are using, run this from the command line:

% perl -MTkx -e 'print Tkx::info("patchlevel");'

We want this to be returning something like "8.5.2"; if it returns something like "8.4.17" you've got more work to do. To change the version of Tk that Tkx uses, we'll need to set the PERL_TCL_DL_PATH environment variable to point to the version of Tcl installed by ActiveTcl. Typically you'll set this up in your .profile, .cshrc or other startup script. You might use something like:

% setenv PERL_TCL_DL_PATH /opt/ActiveTcl-8.5/lib/libtcl8.5.so

Then, when you run the 'Tkx::info("patchlevel")' command again, it should return an 8.5.x version of Tcl/Tk.

 

The Obligatory First Program

To make sure that everything actually did work, let's try to run a "Hello World" program in Tk. While for something this short you could just type it in directly to the interpreter, instead use your favorite text editor to put it in a file.

package require Tk
grid [ttk::button .b -text "Hello World"] 

Save this to a file named 'hello.tcl'. From the wish shell, type:

% source hello.tcl

Couldn't find hello.tcl? You might be looking in the wrong directory. You can either give the full path to hello.tcl, or use Tcl's "pwd" and "cd" commands to see what directory you're in, and change to a different one.

require 'tk'
require 'tkextlib/tile'
root = TkRoot.new() 
button = Tk::Tile::TButton.new(root) {text "Hello World"}.grid
Tk.mainloop()

Save this to a file named 'hello.rb'. Start up 'irb', and from the command prompt, type:

% source "hello.rb"

Couldn't find hello.rb? You might be looking in the wrong directory. You can either give the full path to hello.rb, or use Ruby's "Dir.pwd" and "Dir.chdir" commands to see what directory you're in, and change to a different one.

use Tkx;
Tkx::grid( Tkx::ttk__button(".b", -text => "Hello, world" ) );
Tkx::MainLoop();

Note that there are two underscores between "ttk" and "button".

Save this to a file named 'hello.pl'. From a command prompt, type:

% perl hello.pl

Couldn't find hello.pl? You might be looking in the wrong directory. Try providing the full path to hello.pl.

Not working? Are you sure you're using an 8.5.x version of Tcl/Tk? See the install chapter...


Our First Program. Some work left to do before the IPO.

A First (Real) Example

With that out of the way, let's try a slightly more useful example, which will give you an initial feel for what the code behind a Tk program looks like.

 

Design

The example we'll use is a simple GUI tool that will convert a number of feet to the equivalent number of meters. If we were to sketch this out, it might look something like this:


A sketch of our feet to meters conversion program.

So it looks like we have a short text entry widget that will let us type in the number of feet, and a 'Calculate' button that will get the value out of that entry, perform the calculation, and then put the resulting number of meters on the screen just below where the entry is. We've also got three static labels ("feet", "is equivalent to", and "meters") which help our user figure out how to use the interface.

In terms of layout, things seem to naturally divide into three columns and three rows:


The layout of our user interface, which follows a 3 x 3 grid.

 

Code

Now here is the Tcl/Tk code to create this program.

package require Tk

wm title . "Feet to Meters"
grid [ttk::frame .c -padding "3 3 12 12"] -column 0 -row 0 -sticky nwes
grid columnconfigure . 0 -weight 1; grid rowconfigure . 0 -weight 1

grid [ttk::entry .c.feet -width 7 -textvariable feet] -column 2 -row 1 -sticky we
grid [ttk::label .c.meters -textvariable meters] -column 2 -row 2 -sticky we
grid [ttk::button .c.calc -text "Calculate" -command calculate] -column 3 -row 3 -sticky w

grid [ttk::label .c.flbl -text "feet"] -column 3 -row 1 -sticky w
grid [ttk::label .c.islbl -text "is equivalent to"] -column 1 -row 2 -sticky e
grid [ttk::label .c.mlbl -text "meters"] -column 3 -row 2 -sticky w

foreach w [winfo children .c] {grid configure $w -padx 5 -pady 5}
focus .c.feet
bind . <Return> {calculate}

proc calculate {} {  
   if {[catch {
       set ::meters [expr {round($::feet*0.3048*10000.0)/10000.0}]
   }]!=0} {
       set ::meters ""
   }
}

Now here is the RubyTk code to create this program.

require 'tk'
require 'tkextlib/tile'

root = TkRoot.new {title "Feet to Meters"}
content = Tk::Tile::Frame.new(root) {padding "3 3 12 12"}.grid( :sticky => 'nsew')
TkGrid.columnconfigure root, 0, :weight => 1; TkGrid.rowconfigure root, 0, :weight => 1

$feet = TkVariable.new; $meters = TkVariable.new
f = Tk::Tile::Entry.new(content) {width 7; textvariable $feet}.grid( :column => 2, :row => 1, :sticky => 'we' )
Tk::Tile::Label.new(content) {textvariable $meters}.grid( :column => 2, :row => 2, :sticky => 'we');
Tk::Tile::Button.new(content) {text 'Calculate'; command {calculate}}.grid( :column => 3, :row => 3, :sticky => 'w')

Tk::Tile::Label.new(content) {text 'feet'}.grid( :column => 3, :row => 1, :sticky => 'w')
Tk::Tile::Label.new(content) {text 'is equivalent to'}.grid( :column => 1, :row => 2, :sticky => 'e')
Tk::Tile::Label.new(content) {text 'meters'}.grid( :column => 3, :row => 2, :sticky => 'w')

TkWinfo.children(content).each {|w| TkGrid.configure w, :padx => 5, :pady => 5}
f.focus
root.bind("Return") {calculate}

def calculate
  begin
     $meters.value = (0.3048*$feet*10000.0).round()/10000.0
  rescue
     $meters.value = ''
  end
end

Tk.mainloop

Now here is the Perl code to create this program.

use Tkx;

Tkx::wm_title(".", "Feet to Meters");
Tkx::ttk__frame(".c",  -padding => "3 3 12 12");
Tkx::grid( ".c", -column => 0, -row => 0, -sticky => "nwes");
Tkx::grid_columnconfigure( ".", 0, -weight => 1); 
Tkx::grid_rowconfigure(".", 0, -weight => 1);

Tkx::ttk__entry(".c.feet", -width => 7, -textvariable => \$feet);
Tkx::grid(".c.feet", -column => 2, -row => 1, -sticky => "we");
Tkx::ttk__label(".c.meters", -textvariable => \$meters);
Tkx::grid(".c.meters", -column => 2, -row => 2, -sticky => "we");
Tkx::ttk__button(".c.calc", -text => "Calculate", -command => sub {calculate();});
Tkx::grid(".c.calc", -column => 3, -row => 3, -sticky => "w");

Tkx::grid( Tkx::ttk__label(".c.flbl", -text => "feet"), -column => 3, -row => 1, -sticky => "w");
Tkx::grid( Tkx::ttk__label(".c.islbl", -text => "is equivalent to"), -column => 1, -row => 2, -sticky => "e");
Tkx::grid( Tkx::ttk__label(".c.mlbl", -text => "meters"), -column => 3, -row => 2, -sticky => "w");

foreach (Tkx::SplitList(Tkx::winfo_children(".c"))) {
    Tkx::grid_configure($_, -padx => 5, -pady => 5);
}
Tkx::focus(".c.feet");
Tkx::bind(".", "<Return>", sub {calculate();});

sub calculate {
   $meters = int(0.3048*$feet*10000.0+.5)/10000.0 || '';
}

Tkx::MainLoop();

As we'll see in the next chapter, there's another, more object-oriented way to do exactly the same thing. Are we surprised?

And the resulting user interface:




Screenshot of our completed feet to meters user interface (on Mac OS X, Windows and Linux).

 

Step-by-Step Walkthrough

Let's take a closer look at that code, piece by piece. For now, all we're trying to do is get a basic understanding of the types of things we need to do to create a user interface in Tk, and roughly what those things look like. We'll go into details later.

package require Tk	

First thing we do is tell Tcl that our program needs Tk. Though not strictly necessary, it's considered good form to include this line. It can also be used to specify exactly what version of Tk is needed.

require 'tk'
require 'tkextlib/tile'	

These two lines tell Ruby that our program needs two packages. The first, "tk", is the Ruby binding to Tk, which when loaded also causes the existing Tk library on your system to be loaded. The second, "tkextlib/tile", is Ruby Tk's binding to the newer "themed widgets" that were added to Tk in 8.5.

The themed widget set evolved out of an earlier Tk add-on called Tile, hence the nomenclature. Despite that, the Tk::Tile::* calls you'll see in the programs are actually using the proper ttk versions in 8.5. Expect this to get better rationalized in the future.

use Tkx;

The first thing that we need to do is tell Perl to load the "Tkx" module, which provides the Perl interface to Tk that we are using.

As mentioned here, there are other Perl bindings to Tk. However, we very strongly recommend using Tkx for development, and that will be the only binding we will be describing here. Tkx has the advantage of being a very thin layer above Tk's native Tcl API, which means that in almost all cases it automatically tracks the latest changes to Tk (something which became a considerable issue with Perl/Tk, which was extremely popular in earlier years, but has not been recently updated). As well, by avoiding introducing another layer of code, API errors are reduced, and we can also take advantage of available reference documentation for Tk (which is usually Tcl oriented).

wm title . "Feet to Meters"
grid [ttk::frame .c -padding "3 3 12 12"] -column 0 -row 0 -sticky nwes
grid columnconfigure . 0 -weight 1; grid rowconfigure . 0 -weight 1
root = TkRoot.new {title "Feet to Meters"}
content = Tk::Tile::Frame.new(root) {padding "3 3 12 12"}.grid(:sticky => 'nsew')
TkGrid.columnconfigure root, 0, :weight => 1; TkGrid.rowconfigure root, 0, :weight => 1
Tkx::wm_title(".", "Feet to Meters");
Tkx::ttk__frame(".c",  -padding => "3 3 12 12");
Tkx::grid( ".c", -column => 0, -row => 0, -sticky => "nwes");
Tkx::grid_columnconfigure( ".", 0, -weight => 1); 
Tkx::grid_rowconfigure(".", 0, -weight => 1);

Next, the above lines set up the main window, giving it the title "Feet to Meters". Next, we create a frame widget, which will hold all the content of our user interface, and place that in our main window. The "grid" bits just tell Tk that if the main window is resized, the frame should expand to take up the extra space.

Strictly speaking, we could just put the other parts of our interface directly into the main root window, without the intervening content frame. However, the main window isn't itself part of the "themed" widgets, so its background color wouldn't match the themed widgets we will put inside it. Using a "themed" frame widget to hold the content ensures that the background is correct.

grid [ttk::entry .c.feet -width 7 -textvariable feet] -column 2 -row 1 -sticky we
grid [ttk::label .c.meters -textvariable meters] -column 2 -row 2 -sticky we
grid [ttk::button .c.calc -text "Calculate" -command calculate] -column 3 -row 3 -sticky w
$feet = TkVariable.new; $meters = TkVariable.new
f = Tk::Tile::Entry.new(content) {width 7; textvariable $feet}.grid( :column => 2, :row => 1, :sticky => 'we' )
Tk::Tile::Label.new(content) {textvariable $meters}.grid( :column => 2, :row => 2, :sticky => 'we');
Tk::Tile::Button.new(content) {text 'Calculate'; command {calculate}}.grid( :column => 3, :row => 3, :sticky => 'w')
Tkx::ttk__entry(".c.feet", -width => 7, -textvariable => \$feet);
Tkx::grid(".c.feet", -column => 2, -row => 1, -sticky => "we");
Tkx::ttk__label(".c.meters", -textvariable => \$meters);
Tkx::grid(".c.meters", -column => 2, -row => 2, -sticky => "we");
Tkx::ttk__button(".c.calc", -text => "Calculate", -command => sub {calculate();});
Tkx::grid(".c.calc", -column => 3, -row => 3, -sticky => "w");

The preceding lines create the three main widgets in our program: the entry where we type the number of feet in, a label where we put the resulting number of meters, and the calculate button that we press to perform the calculation.

For each of the three widgets, we need to do two things: create the widget itself, and then place it onscreen. All three widgets, which are 'children' of our content window are created as instances of one of Tk's themed widget classes. At the same time as we create them, we give them certain options, such as how wide the entry is, the text to put inside the Button, etc. The entry and label each are assigned a mysterious "textvariable"; we'll see what that does shortly.

If the widgets are just created, they won't automatically show up on screen, because Tk doesn't know how you want them to be placed relative to other widgets. That's what the "grid" part does. Remembering the layout grid for our application, we place each widget in the appropriate column (1, 2 or 3), and row (also 1, 2 or 3). The "sticky" option says how the widget would line up within the grid cell, using compass directions. So "w" (west) means anchor the widget to the left side of the cell, "we" (west-east) means anchor it to both the left and right sides, and so on.

grid [ttk::label .c.flbl -text "feet"] -column 3 -row 1 -sticky w
grid [ttk::label .c.islbl -text "is equivalent to"] -column 1 -row 2 -sticky e
grid [ttk::label .c.mlbl -text "meters"] -column 3 -row 2 -sticky w
Tk::Tile::Label.new(content) {text 'feet'}.grid( :column => 3, :row => 1, :sticky => 'w')
Tk::Tile::Label.new(content) {text 'is equivalent to'}.grid( :column => 1, :row => 2, :sticky => 'e')
Tk::Tile::Label.new(content) {text 'meters'}.grid( :column => 3, :row => 2, :sticky => 'w')
Tkx::grid( Tkx::ttk__label(".c.flbl", -text => "feet"), -column => 3, -row => 1, -sticky => "w");
Tkx::grid( Tkx::ttk__label(".c.islbl", -text => "is equivalent to"), -column => 1, -row => 2, -sticky => "e");
Tkx::grid( Tkx::ttk__label(".c.mlbl", -text => "meters"), -column => 3, -row => 2, -sticky => "w");

The above three lines do exactly the same thing for the three static text labels in our user interface; create each one, and place it onscreen in the appropriate cell in the grid.

foreach w [winfo children .c] {grid configure $w -padx 5 -pady 5}
focus .c.feet
bind . <Return> {calculate}
TkWinfo.children(content).each {|w| TkGrid.configure w, :padx => 5, :pady => 5}
f.focus
root.bind("Return") {calculate}
foreach (Tkx::SplitList(Tkx::winfo_children(".c"))) { Tkx::grid_configure($_, -padx => 5, -pady => 5); }
Tkx::focus(".c.feet");
Tkx::bind(".", "", sub {calculate();});

The preceeding three lines help put some nice finishing touches on our user interface.

The first line walks through all of the widgets that are children of our content frame, and adds a little bit of padding around each, so they aren't so scrunched together. We could have added these options to each "grid" call when we first put the widgets onscreen, but this is a nice shortcut.

The second line tells Tk to put the focus on our entry widget. That way the cursor will start in that field, so the user doesn't have to click in it before starting to type.

The third line tells Tk that if the user presses the Return key (Enter on Windows) anywhere within the root window, that it should call our calculate routine, the same as if the user pressed the Calculate button.

proc calculate {} {  
   if {[catch {
       set ::meters [expr {round($::feet*0.3048*10000.0)/10000.0}]
   }]!=0} {
       set ::meters ""
   }
}
def calculate
  begin
     $meters.value = (0.3048*$feet*10000.0).round()/10000.0
  rescue
     $meters.value = ''
  end
end
sub calculate {
   $meters = int(0.3048*$feet*10000.0+.5)/10000.0 || '';
}

Here we define our calculate procedure, which is called either when the user presses the Calculate button, or hits the Return key. It performs the feet to meters calculation, taking the number of feet from our entry widget, and placing the result in our label widget.

Say what? It doesn't look like we're doing anything with those widgets! Here's where the magic "textvariable" options we specified when creating the widgets come into play. We specified the global variable "feet" as the textvariable for the entry, which means that anytime the entry changes, Tk will automatically update the global variable feet. Similarly, if we explicitly change the value of a textvariable associated with a widget (as we're doing for "meters" which is attached to our label), the widget will automatically be updated with the current contents of the variable. Slick.

Tk.mainloop

This final line tells Tk to enter its event loop, which is needed to make everything run.

Tkx::MainLoop();

This final line tells Tk to enter its event loop, which is needed to make everything run.

 

What's Missing

It's also worth examing what we didn't have to include in our Tk program to make it work. For example:

  • we didn't have to worry about redrawing the screen as things changed
  • we didn't have to worry about parsing and dispatching events, hit detection, or handling events on each widget
  • we didn't have to provide a lot of options when we created widgets; the defaults seemed to take care of most things, and so we only had to change things like the text the button displayed
  • we didn't have to write complex code to get and set the values of simple widgets; we just attached them to variables
  • we didn't have to worry about what happens when the user closes the window or resizes it
  • we didn't need to write extra code to get this all to work cross-platform

Tk Concepts

With a first example behind you, you should have a basic idea of what a Tk program might look like and the types of tasks it needs to accomplish. We'll step back and look at three broad concepts that you need to know to understand Tk: widgets, geometry management, and event handling.

 

Widgets

Widgets are all the things that you see onscreen. In our example, we had a button, an entry, a few labels, and a frame. Others are things like checkboxes, tree views, scrollbars, text areas, and so on. Widgets are what are often referred to as "controls"; you'll also often see them referred to as "windows", particularly in Tk's documentation, a holdover from its X11 roots (so under that terminology, both a toplevel window and things like a button would be called windows).

Here is an example showing some of Tk's widgets, which we'll cover individually shortly.


Widget Classes

Widgets are objects, instances of classes that represent buttons, frames, and so on. So the first thing you'll need to do is identify the specific class of the widget you'd like to instantiate. This tutorial and the widget roundup will help with that.

Window Hierarchy

The other thing you'll need to know is the parent of the widget instance you'd like to create. In Tk, all widgets are part of a window hierarchy, with a single root at the top of the hierarchy. This hierarchy can be arbitrarily deep; so you might have a button in a frame in another frame within the root window. Even a new toplevel window is part of that same hierarchy, with it and all its contents forming a subtree of the overall window hierarchy.

In our metric conversion example, we had a single frame that was created as a child of the root window, and that frame had all the other controls as children. The root window was a container for the frame, and was therefore the frame's parent. The complete hierarchy for the example looked like this:


The window hierarchy of the metric conversion example.

Creating and Using Widgets

In Tcl, each widget is given an explicit pathname, which both differentiates it from other widgets, and also indicates its place in the window hierarchy. The root of the hierarchy, the toplevel widget that Tk automatically creates, is named simply "." (dot).

The frame, which was a child of the root, was named ".c". We could have put pretty much anything in place of the "c", naming it for example ".content". This name is purely for use by your program, so it's best to choose something meaningful. The controls that were children of the frame were given names like ".c.feet", ".c.meters", ".c.flbl", and so on. If there were any widgets at a deeper level of the hierarchy, we'd add another "." and then a unique identifier.

So to create a widget, we need to provide the widget class, and the pathname. The pathname is used to indicate the widget's parent (which must of course exist also), and hence its position in the window hierarchy. For example:

ttk::button .b
ttk::frame .f
ttk::entry .f.entry

This also creates a new object command with the same name as the widget's pathname, which will let us communicate with the widget. So the above code would produce new Tcl commands named ".b", ".f", ".f.entry", and so on. You can then use that object command to communicate further with the widget, calling e.g. ".b invoke", or ".f.entry state disabled". Because of the obvious parallels with many object-oriented systems, we'll often refer to the object commands as objects, and calls on those objects (like the "invoke") as method calls. For example, you'll see below the use of the "configure" and "cget" methods.

Each separate widget is a Ruby object. When creating a widget, you must pass its parent as a parameter to the widget class' "new" method. The only exception is the "root" window, which is the toplevel window that will contain everything else. That is instantiated from the TkRoot class, and it does not have a parent. For example:

root = TkRoot.new 
content = Tk::Tile::Frame.new(root)
Tk::Tile::Button.new(content)

Whether or not you save the widget object in a variable is entirely up to you, and depends of course whether you'll need to refer to it later. Because the object is inserted into the widget hierarchy, it won't be garbage collected even if you don't keep your own reference to it.

If you snuck a peak at how Tcl manages widgets, you'll see each widget has a specific pathname; you'll also see this pathname referred to in Tk reference documentation. RubyTk chooses and manages all these pathnames for you behind the scenes, so you should never have to worry about them. If you do though, you can get the pathname from a widget with its "path" method.

In Perl, each widget is given an explicit pathname, which both differentiates it from other widgets, and also indicates its place in the window hierarchy. The root of the hierarchy, the toplevel widget that Tk automatically creates, is named simply "." (dot).

The frame, which was a child of the root, was named ".c". We could have put pretty much anything in place of the "c", naming it for example ".content". This name is purely for use by your program, so it's best to choose something meaningful. The controls that were children of the frame were given names like ".c.feet", ".c.meters", ".c.flbl", and so on. If there were any widgets at a deeper level of the hierarchy, we'd add another "." and then a unique identifier.

So to create a widget, we need to provide the widget class, and the pathname. The pathname is used to indicate the widget's parent (which must of course exist also), and hence its position in the window hierarchy. For example:

Tkx::ttk__button(".b", -text => "hello");
Tkx::ttk__frame(".f");
Tkx::ttk__entry(".f.entry");

Widget Objects

Many widgets have operations that you can call on them, such as to disable a widget, invoke a button's command, and so on. If we were doing this in Tcl, we'd write something like ".b invoke" or ".f.entry state disabled". Essentially you'd think of ".b" or ".f.entry" as objects, and "invoke" and "state" as methods, the latter having a single parameter "disabled". In Perl, you can call the exact Tcl command like this:

Tkx::i::call(".b", "invoke");
Tkx::i::call(".f.entry", "state", "disabled");

Admittedly, that's pretty lame. Ideally, we'd want to have these widgets behave just like Perl objects. Luckily, Tkx lets us do exactly that.

The first thing we need to do is get a reference to an object for these widgets. We can do that like this, passing the widget path name to "Tkx::widget->new":

my $b = Tkx::widget->new(".b");
my $e = Tkx::widget->new(".f.entry");

Then we can invoke methods on these objects just how you'd expect:

$b->invoke;
$e->state("disabled");

Creating each widget and then getting a reference to it as a separate step is a bit much. So there's another way to create widgets. If you have an object reference to the parent widget in the widget hierarchy, you can ask it to create a child.

my $mw = Tkx::widget->new(".");
my $b = $mw->new_ttk__button(-text => "hello");
my $f = $mw->new_ttk__frame;
my $e = $f->new_ttk__entry;

In this case, Tkx will choose the widget pathname for you (it'll look something like ".b", ".f.e", ".f.e2", etc.).

You can get this path by calling the object's "_mpath" method (e.g. "$b->_mpath"). If you stringify the object itself (e.g. trying to print it out), it will also be the widget pathname.

You also saw in our earlier example that some Tkx commands (like "grid") take the widget pathname as their first argument. There's a way to do that using the widget object instead. We invoke a method which is the name of the command, prefixed by "g_", e.g. "g_grid". So this:

Tkx::ttk__entry(".c.feet", -width => 7, -textvariable => \$feet);
Tkx::grid(".c.feet", -column => 2, -row => 1, -sticky => "we");

becomes this:

my $mw = Tkx::widget->new(".");
my $ft = $mw->new_ttk__entry(-width => 7, -textvariable => \$feet);
$ft->g_grid(-column => 2, -row => 1, -sticky => "we");

Translation Rules

What's somewhat scary about the Tkx module is that it implements all of this on a purely syntactic level. That is, it has no clue about buttons and entries and grid. It just knows that if it sees a method "new_something" it's creating a widget using the Tcl command "something", or if you call a method "g_otherthing", that it's going to invoke a Tcl command "otherthing", and pass the widget pathname associated with the object as a first parameter (followed by any other parameters passed to the method).

It's this pure syntactic mapping which gives Tkx its power and amazing brevity (check out the code in Tkx.pm!). It also explains why it can automatically track any changes in Tk. Any new commands and options don't need to be explicitly implemented in Tkx code; they are automatically available just by using the right syntax.

Now let's talk about those underscores in the Tkx commands and method names. If you've glanced at the Tcl version of the code in this tutorial, you know that they can take several forms, i.e.

  • a single toplevel command, like "grid"
  • a multi-word command (called an ensemble in Tcl), e.g. "wm title"
  • a command in a namespace, e.g. "ttk::button"
  • a single toplevel command with underscores, e.g. "tk_messageBox"

No translations are needed for simple commands like "grid", but the others need to be tweaked. To translate these into Perl, Tkx uses underscores in the following way:

  • a single underscore is replaced with a space, e.g. "wm_title" in Perl becomes "wm title" in Tcl
  • two underscores is replaced with the namespace qualifier "::", e.g. "ttk__button" becomes "ttk::button"
  • three underscores is replaced with a single underscore, e.g. "tk___messageBox" becomes "tk_messageBox"

Given that this purely syntactic translation is all that Tkx does, let's state the obvious: whether or not you use the pure command form, which we used in the "feet to meters" example, or the object oriented form, they both do exactly the same thing (i.e. invoke the same underlying Tcl command).

Because you can get an object's widget pathname, or get an object reference from a widget pathname, you can also freely intermix them. Most programs of any length will probably be a bit simpler if they predominately use the object oriented form, but this is almost entirely a stylistic issue.

Feet to Meters, Object Style

Here's the earlier "feet to meters" example, this time rewritten in the object oriented style. The programs do exactly the same thing.

use Tkx;

my $mw = Tkx::widget->new(".");
$mw->g_wm_title("Feet to Meters");
my $frm = $mw->new_ttk__frame(-padding => "3 3 12 12");
$frm->g_grid(-column => 0, -row => 0, -sticky => "nwes");
$mw->g_grid_columnconfigure(0, -weight => 1);
$mw->g_grid_rowconfigure(0, -weight => 1);

my $ef = $frm->new_ttk__entry(-width => 7, -textvariable => \$feet);
$ef->g_grid(-column => 2, -row => 1, -sticky => "we");
my $em = $frm->new_ttk__label(-textvariable => \$meters);
$em->g_grid(-column => 2, -row => 2, -sticky => "we");
my $cb = $frm->new_ttk__button(-text => "Calculate", -command => sub {calculate();});
$cb->g_grid(-column => 3, -row => 3, -sticky => "w");

$frm->new_ttk__label(-text => "feet")->g_grid(-column => 3, -row => 1, -sticky => "w");
$frm->new_ttk__label(-text => "is equivalent to")->g_grid(-column => 1, -row => 2, -sticky => "e");
$frm->new_ttk__label(-text => "meters")->g_grid(-column => 3, -row => 2, -sticky => "w");

foreach (Tkx::SplitList($frm->g_winfo_children)) {
    Tkx::grid_configure($_, -padx => 5, -pady => 5);
}
$ef->g_focus;
$mw->g_bind("<Return>", sub {calculate();});

sub calculate {
   $meters = int(0.3048*$feet*10000.0+.5)/10000.0 || '';
}

Tkx::MainLoop();

Configuration Options

All widgets also have a number of different configuration options, which generally control how they are displayed or how they behave.

The options that are available depend upon the widget class of course. There is a lot of consistency between different widget classes, so options that do pretty much the same thing tend to be named the same. So both a button and a label have a "text" option to adjust the text the widget displays, while a scrollbar for example would not have a "text" option since it's not needed. In the same way, the button has a "command" option telling it what to do when pushed, while a label, which holds just static text, does not.

Configuration options can be set when the widget is first created, by passing along the names and values of the options as optional parameters. You can later check what the value of those options are, and with a very small number of exceptions, change them at any time. If you're not sure what all the different options are for a widget, you can ask the widget to provide it. This gives you a long list of all the options, and for each option, you can see the name of the option and its current value (along with three other attributes which you won't normally need to worry about).

This is all best illustrated with the following interactive dialog with the interpreter.

% wish8.5
create a button, passing two options:
% grid [ttk::button .b -text "Hello" -command {button_pressed}]
check the current value of the text option:
% .b cget -text
Hello
check the current value of the command option:
% .b cget -command
button_pressed
change the value of the text option:
% .b configure -text Goodbye
check the current value of the text option:
% .b cget -text
Goodbye
get all information about the text option:
% .b configure -text
-text text Text {} Goodbye
get information on all options for this widget:
% .b configure
{-takefocus takeFocus TakeFocus ttk::takefocus ttk::takefocus} 
{-command command Command {} button_pressed} {-default default Default normal normal} 
{-text text Text {} Goodbye} {-textvariable textVariable Variable {} {}} 
{-underline underline Underline -1 -1} {-width width Width {} {}} {-image image Image {} {}} 
{-compound compound Compound none none} {-padding padding Pad {} {}} 
{-state state State normal normal} {-takefocus takeFocus TakeFocus {} ttk::takefocus} 
{-cursor cursor Cursor {} {}} {-style style Style {} {}} {-class {} {} {} {}}
% irb
irb(main):001:0> require 'tk'
=> true
irb(main):002:0> require 'tkextlib/tile'
=> true
create a button, passing two options:
irb(main):003:0> root = TkRoot.new
=> #<TkRoot:0xb7c8c9d8 @path=".">
irb(main):004:0> button = Tk::Tile::Button.new(root) {text "Hello"; command "button_pressed"}.grid
=> #<Tk::Tile::TButton:0xb7c86ab0 @cmdtbl=["c00001"], @path=".w00000">
check the current value of the text option:
irb(main):005:0> button['text']
=> "Hello"
check the current value of the command option:
irb(main):006:0> button['command']
=> #<cb_entry:fdbe42342>
change the value of the text option:
irb(main):007:0> button['text'] = 'goodbye'
=> "goodbye"
check the current value of the text option:
irb(main):008:0> button['text']
=> "goodbye"
get all information about the text option:
irb(main):009:0> button.configinfo 'text'
=> ["text", "text", "Text", "", "goodbye"]
get information on all options for this widget:
irb(main):010:0> button.configinfo
=> [["takefocus", "takeFocus", "TakeFocus", true, true], ["command", "command", "Command", "", #], 
["default", "default", "Default", "normal", "normal"], ["text", "text", "Text", "", "goodbye"],
 ["textvariable", "textVariable", "Variable", nil, nil], 
["underline", "underline", "Underline", -1, -1], ["width", "width", "Width", "", ""], 
["image", "image", "Image", "", ""], ["compound", "compound", "Compound", "none", "none"], 
["padding", "padding", "Pad", "", ""], ["state", "state", "State", "normal", "normal"], 
["takefocus", "takeFocus", "TakeFocus", nil, true], ["cursor", "cursor", "Cursor", "", ""], 
["style", "style", "Style", [], []], ["class", "", "", "", ""]]
run Perl interactively using the program "psh":
% psh
Perl> $mw = Tkx::widget->new(".")
.
create a button, passing two options:
Perl> ($b = $mw->new_ttk__button(-text => "Hello", -command => sub {button_pressed();}))->g_grid
.b
check the current value of the text option:
  (note that "m_<foo>" is another one of those magic Tkx translations 
   like "g_<foo>"; in this case it means call the <foo> method on the object)
Perl> $b->m_cget(-text)
Hello
conveniently, the "m_" part is almost always optional:
Perl> $b->cget(-text)
Hello
check the current value of the command option:
Perl> $b->cget(-command)
::perl::CODE(0x839020)
change the value of the text option:
Perl> $b->configure(-text => "Goodbye")

check the current value of the text option:
Perl> $b->cget(-text)
Goodbye
get all information about the text option:
Perl> $b->configure(-text)
-text text Text {} Goodbye
convert the Tcl list this returns into a Perl list:
Perl> Tkx::SplitList($b->configure(-text))
-text, text, Text, , Goodbye
get information on all options for this widget:
Perl> $b->configure
{-takefocus takeFocus TakeFocus ttk::takefocus ttk::takefocus} 
{-command command Command {} ::perl::CODE(0x839020)} {-default default Default normal normal} 
{-text text Text {} Goodbye} {-textvariable textVariable Variable {} {}} 
{-underline underline Underline -1 -1} {-width width Width {} {}} {-image image Image {} {}} 
{-compound compound Compound none none} {-padding padding Pad {} {}} 
{-state state State normal normal} {-takefocus takeFocus TakeFocus {} ttk::takefocus} 
{-cursor cursor Cursor {} {}} {-style style Style {} {}} {-class {} {} {} {}}

 

Geometry Management

If you've been playing around creating widgets, you've probably noticed that just by creating them they didn't end up showing up onscreen. Having things actually put in the onscreen window, and precisely where in the window they show up is a separate step called geometry management.

In our example, this positioning was accomplished by the "grid" command, where we passed along the column and row we wanted each widget to go in, how things were to be aligned within the grid, and so on. Grid is an example of a geometry manager (of which there are several in Tk, grid being the most useful). We'll talk about grid in detail in a later chapter, but for now we'll look at geometry management in general.

A geometry manager's job is to figure out exactly where those widgets are going to be put. This turns out to be very difficult optimization problem, and a good geometry manager relies on quite complex algorithms. A good geometry manager provides the flexibility, power and ease of use that makes programmers happy, and Tk's "grid" is without a doubt one of the absolute best. A poor geometry manager... well, all the Java programmers who have suffered through "GridBagLayout" please raise their hands.

The Problem

The problem for a geometry manager is to take all the different widgets the program creates, plus the instructions for where in the window the program would like things to go (explicitly, or more often, relative to other widgets), and then actually put them in the window.

In doing so, the geometry manager has to balance a number of different constraints:

  • The widgets may have a "natural" size (e.g. the natural width of a label would normally be determined by the text and font in it), but the toplevel all these different widgets are trying to fit into isn't big enough to accommodate them; the geometry manager must decide which widgets to shrink to fit, by how much, etc.
  • If the toplevel window is bigger than the natural size of all the widgets, how is the extra space used? Is it just used for extra space between widgets, and if so, how is that space distributed? Is it used to make certain widgets bigger than they normally want to be?
  • If the toplevel window is resized, how does the size and position of the widgets in it change? Will certain areas (e.g. a text entry area) expand or shrink, while other parts stay the same size, or is the area distributed differently? Do certain widgets have a minimum (or maximum) size that you want to avoid going under (over)?
  • How can widgets in different parts of the user interface be aligned with each other, to present a clean layout and match platform guidelines to do with inter-widget spacing?
  • For a complex user interface, which may have many frames nested in other frames nested in the window (etc.), how can all the above be accomplished, trading off the conflicting demands of different parts of the entire user interface?

How it Works

Geometry management in Tk relies on the concept of master and slave widgets. A master is a widget, typically a toplevel window or a frame, which will contain other widgets, which are called slaves. You can think of a geometry manager as taking control of the master widget, and deciding what will be displayed within.

The geometry manager will ask each slave widget for its natural size, or how large it would ideally like to be displayed. It then takes that information and combines it with any parameters provided by the program when it asks the geometry manager to manage that particular slave widget. In our example, we passed grid a "column" and "row" number for each widget, which indicated the relative position of the widget with respect to others, and also a "sticky" parameter to suggest how the widget should be aligned or possibly stretched. We also used "columnconfigure" and "rowconfigure" to indicate the columns and rows we'd like to have expand if there is extra space available in the window. Of course, all these parameters are specific to grid; other geometry managers would use different ones.

The geometry manager takes all the information about the slaves, as well as the information about how large the master is, and uses its internal algorithms to determine the area each slave will be allocated (if any!). The slave is then responsible for drawing etc. within that particular rectangle. And of course, any time the size of the master changes (e.g. because the toplevel window was resized), the natural size of a slave changes (e.g. because we've changed the text in a label), or any of the geometry manager parameters change (e.g. like "row", "column", or "sticky") we repeat the whole thing.

This all works recursively as well. In our example, we had a content frame inside the toplevel window, and then a number of other controls in the content frame. We therefore had a geometry manager working on two different masters. At the outer level, the toplevel window was the master, and the content frame was the slave. At the inner level, the content frame was the master, with each of the other widgets being slaves. So the same widget can be both a master and a slave. This hierarchy can of course also be nested much more deeply.

While each master can have only one geometry manager (e.g. grid), it's entirely possible for different masters to have different geometry managers; while grid is generally used, others may make sense for a particular layout used in one part of your user interface. Also, we've been making the assumption that slave widgets are the immediate children of their master in the widget hierarchy. While this is usually the case, and mostly there's no good reason to do it any other way, it's also possible (with some restrictions) to get around this.

 

Event Handling

In Tk, as in most other user interface toolkits, there is an event loop which receives events from the operating system. These are things like button presses, keystrokes, mouse movement, window resizing, and so on.

Generally, Tk takes care of managing this event loop for you. It will figure out what widget the event applies to (did the user click on this button? if a key was pressed, which textbox had the focus?), and dispatch it accordingly. Individual widgets know how to respond to events, so for example a button might change color when the mouse moves over it, and revert back when the mouse leaves.

Command Callbacks

Often though you want your program to handle particular events, for example doing something when a button is pushed. For those events that are pretty much essential to customize (what good is a button without something happening when you press it?), the widget will provide a callback as a widget configuration option. We saw this in the example with the "command" option of the button.

Callbacks in Tk tend to be simpler than in toolkits used with compiled languages (where a callback must generally be directed at a procedure with a certain set of parameters or an object method with a certain signature). Instead, the callback is just a normal bit of code that the interpreter evaluates. While it can be as complex as you want to make it, most times though you'll just want your callback to call some other procedure.

Event Bindings

For events that don't have a command callback associated with them, you can use Tk's "bind" to capture any event, and then (like with callbacks) execute an arbitrary piece of code.

Here is a (silly) example that shows how a label can have bindings set up for it to respond to different events, which it does so by just changing what is displayed in the label.

package require Tk
grid [ttk::label .l -text "Starting..."] 
bind .l <Enter> {.l configure -text "Moved mouse inside"}
bind .l <Leave> {.l configure -text "Moved mouse outside"}
bind .l <1> {.l configure -text "Clicked left mouse button"}
bind .l <Double-1> {.l configure -text "Double clicked"}
bind .l <B3-Motion> {.l configure -text "right button drag to %x %y"}

Note that the bind command lives in the global namespace; there is not a tk::bind command.

require 'tk'
require 'tkextlib/tile'
root = TkRoot.new
l = Tk::Tile::Label.new(root) {text "Starting..."}
l.bind("Enter") {l['text'] = "Moved mouse inside"}
l.bind("Leave") {l['text'] = "Moved mouse outside"}
l.bind("1") {l['text'] = "Clicked left mouse button"}
l.bind("Double-1") {l['text'] = "Double clicked"}
l.bind("B3-Motion", proc{|x,y| l['text'] = "right button drag to #{x} #{y}"}, "%x %y")
Tk.mainloop	
use Tkx;
my $mw = Tkx::widget->new(".");
(my $l = $mw->new_ttk__label(-text => "Starting..."))->g_grid;
$l->g_bind("<Enter>",     sub {$l->configure(-text => "Moved mouse inside")});
$l->g_bind("<Leave>",     sub {$l->configure(-text => "Moved mouse outside")});
$l->g_bind("<1>",         sub {$l->configure(-text => "Clicked left mouse button")});
$l->g_bind("<Double-1>",  sub {$l->configure(-text => "Double clicked")});
$l->g_bind("<B3-Motion>", [sub { my($x,$y) = @_;
                                 $l->configure(-text => "right button drag to $x $y")
	                   }, Tkx::Ev("%x", "%y")]);
Tkx::MainLoop();

The first three event bindings are pretty straightforward, just looking at simple events. The double click binding introduces the idea of an event modifier; in this case we want to trigger the event on a left mouse click (the "1"), but only when it's a double click (the "Double-").

The last binding also uses a modifier: capture mouse movement ("Motion"), but only when the right mouse button ("B3") is held down. This binding also shows an example of how to use event parameters, through the use of percent substitutions. Many events, such as mouse clicks or movement have as parameters additional information like the current position of the mouse. These percent substitutions let you capture them so they can be used in your script.

Tkx lets us provide command callbacks as just a Perl function (the first four), or as a two element array (the last case). The first element is the Perl code to be called, while the second array element specifies parameters to pass to that code. The function "Tkx::Ev()" will expand its parameter ("%x %y" in this case) when the callback is invoked, which will perform the percent substitutions. These then are passed as parameters to our function.

For a complete description of all the different event names, modifiers, and the different event parameters that are available with each, the best place to look is the "bind" command reference.

Virtual Events

Beyond the low-level operating system events like mouse clicks and window resizes, many widgets generate higher level events called virtual events. For example, a listbox widget will generate a "ListboxSelect" virtual event anytime the selection changes, regardless of whether that was because the user clicked on an item, moved to it with the arrow keys, or whatever. This avoids the problem of setting up multiple, possibly platform-specific event bindings to capture the change. Virtual events for a widget, if any, will be listed in the widget's documentation.

Multiple Bindings

Widgets can actually have a number of different event bindings trigger for a single event. Normally, events can be set up for: the individual widget itself, all widgets of a certain class (e.g. buttons), the toplevel window containing the widget, and all widgets in the application. Each of these will fire in sequence.

We saw this in our example when we set up a binding for the Return key on the toplevel window, and that applied to every widget within that window.

The default behavior of each widget class in Tk is itself defined with script-level event bindings, and so can be introspected and modified to alter the behavior of all widgets of a certain class. You can even completely modify the handling of this multiple sequence of events for each widget; see the "bindtags" command reference if you're curious.

Basic Widgets

This chapter introduces you to the basic Tk widgets that you'll find in just about any user interface: frames, labels, buttons, checkbuttons, radiobuttons, entries and comboboxes. By the end, you'll know how to use all the widgets you'd ever need for a typical fill-in form type of user interface.

This chapter (and those following that discuss more widgets) are meant to be read in order. Because there is so much commonality between many widgets, we'll introduce certain concepts in an earlier widget that will also apply to a later one. Rather than going over the same ground multiple times, we'll just refer back to when the concept was first introduced.

At the same time, each widget will also refer to the widget roundup page for the specific widget, as well as the reference manual page, so feel free to jump around a bit too.

 

Frame

A frame is a widget that displays just as a simple rectangle. Frames are primarily used as a container for other widgets, which are under the control of a geometry manager such as grid.


Frame Widgets

Frames are created using the ttk::frame command:

ttk::frame .frame

Frames are created using the Tk::Tile::Frame class:

frame = Tk::Tile::Frame.new(parent)

Frames are created using the new_ttk__frame method, a.k.a. Tkx::ttk__frame():

$frame = $parent->new_ttk__frame;

Frames can take several different configuration options which can alter how they are displayed.

Requested Size

Like any other widget, after creation it is added to the user interface via a (parent) geometry manager. Normally, the size that the frame will request from the geometry manager will be determined by the size and layout of any widgets that are contained in it (which are under the control of the geometry manager that manages the contents of the frame itself).

If for some reason you want an empty frame that does not contain other widgets, you should instead explicitly set the size that the frame will request from its parent geometry manager using the "width" and/or "height" configuration options (otherwise you'll end up with a very small frame indeed).

Normally, distances such as width and height are specified just as a number of pixels on the screen. You can also specify them via one of a number of suffixes. For example, "350" means 350 pixels, "350c" means 350 centimeters, "350i" means 350 inches, and "350p" means 350 printer's points (1/72 inch).

Padding

The "padding" configuration option is used to request extra space around the inside of the widget; this way if you're putting other widgets inside the frame, there will be a bit of a margin all the way around. A single number specifies the same padding all the way around, a list of two numbers lets you specify the horizontal then the vertical padding, and a list of four numbers lets you specify the left, top, right and bottom padding, in that order.

.frame configure -padding "5 10"
frame['padding'] = '5 10'
$frame->configure(-padding => "5 10")

Borders

You can display a border around the frame widget; you see this a lot where you might have a part of the user interface looking "sunken" or "raised" in relation to its surroundings. To do this, you need to set the "borderwidth" configuration option (which defaults to 0, so no border), as well as the "relief" option, which specifies the visual appearance of the border: "flat" (default), "raised", "sunken", "solid", "ridge", or "groove".

.frame configure -borderwidth 2 -relief sunken
frame['borderwidth'] = 2
frame['relief'] = 'sunken'
$frame->configure(-borderwidth => 2, -relief => "sunken")

Changing Styles

There is also a "style" configuration option, which is common to all of the themed widgets, which can let you control just about any aspect of their appearance or behavior. This is a bit more advanced, so we won't go into it right now.

Styles mark a sharp departure from the way most aspects of a widget's visual appearance are changed in the "classic" Tk widgets. While in classic Tk you could provide a wide range of options to finely control every aspect of behavior (foreground color, background color, font, highlight thickness, selected foreground color, padding, etc.), in the new themed widgets these changes are done by changing styles, not adding options to each widget.
 
As such, many of the options you may be familiar with in certain widgets are not present in their themed version. Given that overuse of such options was a key factor undermining the appearance of Tk applications, especially when moved across platforms, transitioning to themed widgets provides an opportune time to review and refine if and how such appearance changes are made.

 

Label

A label is a widget that displays text or images, typically that the user will just view but not otherwise interact with. Labels are used for such things as identifying controls or other parts of the user interface, providing textual feedback or results, etc.


Label Widgets

Labels are created using the ttk::label command, and typically their contents are set up at the same time:

ttk::label .label -text {Full name:}

Labels are created using the Tk::Tile::Label class, and typically their contents are set up at the same time:

label = Tk::Tile::Label.new(parent) {text 'Full name:'}

Labels are created using the new_ttk__label method, a.k.a. Tkx::ttk__label(), and typically their contents are set up at the same time:

$label = $parent->new_ttk__label(-text => "Full name:");

Like frames, labels can take several different configuration options which can alter how they are displayed.

Displaying Text

The "text" configuration option shown above when creating the label is the most commonly used, particularly when the label is purely decorative or explanatory. You can of course change this option at any time, not only when first creating the label.

You can also have the widget monitor a variable in your script, so that anytime the variable changes, the label will display the new value of the variable; this is done with the "textvariable" option:

.label configure -textvariable resultContents
set resultContents "New value to display"

Variables must be global, or the fully qualified name given for those within a namespace.

$resultsVar = TkVariable.new
label['textvariable'] = $resultsVar
$resultsVar.value = 'New value to display'

Ruby's Tk binding only allows you to attach to an instance of the "TkVariable" class, which contains all the logic to watch for changes, communicate them back and forth between the variable and Tk, and so on. You need to read or write the current value using the "value" accessor, as shown.

$label->configure(-textvariable => \$resultContents;
$resultContents = "New value to display";

Variables must be global (package), not lexically scoped (e.g. "my" variables).

Displaying Images

You can also display an image in a label instead of text; if you just want an image sitting in your interface, this is normally the way to do it. We'll go into images in more detail in a later chapter, but for now, let's assume you want to display a GIF image that is sitting in a file on disk. This is a two-step process, first creating an image "object", and then telling the label to use that object via its "image" configuration option:
image create photo imgobj -file "myimage.gif"
.label configure -image imgobj
image = TkPhotoImage.new(:file => "myimage.gif")
label['image'] = image
Tkx::image_create_photo( "imgobj", -file => "myimage.gif");
$label->configure(-image => "imgobj");

You can use both an image and text, as you'll often see in toolbar buttons, via the "compound" configuration option. The default value is "none", meaning display only the image if present, otherwise the text specified by the "text" or "textvariable" options. Other options are "text" (text only), "image" (image only), "center" (text in center of image), "top" (text above image), "left", "bottom", and "right".

Layout

While the overall layout of the label (i.e. where it is positioned within the user interface, and how large it is) is determined by the geometry manager, there are several options that can help you control how the label will be displayed within the box the geometry manager gives it.

If the box given to the label is larger than the label requires for its contents, you can use the "anchor" option to specify what edge or corner the label should be attached to, which would leave any empty space in the opposite edge or corner. Possible values are specified as compass directions: "n" (north, or top edge), "ne", (north-east, or top right corner), "e", "se", "s", "sw", "w", "nw" or "center".

Labels can be used to display more than one line of text. This can be done by embedding carriage returns ("\n") in the "text"/"textvariable" string. You can also let the label wrap the string into multiple lines that are no longer than a given length (with the size specified as pixels, centimeters, etc.), by using the "wraplength" option.

Multi-line labels are a replacement for the older "message" widgets in classic Tk.

You can also control how the text is justified, by using the "justify" option, which can have the values "left", "center" or "right". If you only have a single line of text, this is pretty much the same as just using the "anchor" option, but is more useful with multiple lines of text.

Fonts, Colors and More

Like with frames, normally you don't want to touch things like the font and colors directly, but if you need to change them (e.g. to create a special type of label), this would be done via creating a new style, which is then used by the widget with the "style" option.

Unliked most themed widgets, the label widget also provides explicit widget-specific options as an alternative; again, you'd use this only in special one-off cases, when using a style didn't necessarily make sense.

You can specify the font used to display the label's text using the "font" configuration option. While we'll go into fonts in more detail in a later chapter, here are the names of some predefined fonts you can use:

TkDefaultFontThe default for all GUI items not otherwise specified.
TkTextFontUsed for entry widgets, listboxes, etc.
TkFixedFontA standard fixed-width font.
TkMenuFontThe font used for menu items.
TkHeadingFontThe font typically used for column headings in lists and tables.
TkCaptionFontA font for window and dialog caption bars.
TkSmallCaptionFontA smaller caption font for subwindows or tool dialogs
TkIconFontA font for icon captions.
TkTooltipFontA font for tooltips.

Because the choice of fonts is so platform specific, be careful of hardcoding them (font families, sizes, etc.); this is something else you'll see in a lot of older Tk programs that can make them look ugly.

The foreground (text) and background color can also be changed via the "foreground" and "background" options. Colors are covered in detail later, but you can specify these as either color names (e.g. "red") or hex RGB codes (e.g. "#ff340a").

Labels also accept the "relief" option that was discussed for frames.

 

Button

A button, unlike a frame or label, is very much designed for the user to interact with, and in particular, press to perform some action. Like labels, they can display text or images, but also have a whole range of new options used to control their behavior.


Button Widgets

Buttons are created using the ttk::button command, and typically their contents and command callback are set up at the same time:

ttk::button .button -text "Okay" -command "submitForm"

Buttons are created using the Tk::Tile::Button class, and typically their contents and command callback are set up at the same time:

button = Tk::Tile::Button.new(parent) {text 'Okay'; command 'submitForm'}

Buttons are created using the new_ttk__button method, a.k.a. Tkx::ttk__button(), and typically their contents and command callbackare set up at the same time:

$button = $parent->new_ttk__button(-text => "Okay", -command => sub {submitForm();});

As with other widgets, buttons can take several different configuration options which can alter their appearance and behavior.

Text or Image

Buttons take the same "text", "textvariable" (rarely used), "image" and "compound" configuration options as labels, which control whether the button displays text and/or an image.

Buttons have a "default" option, which tells Tk that the button is the default button in the user interface (i.e. the one that will be invoked if the user hits Enter or Return). Some platforms and styles will draw this with a different border or highlight. Set the option to "active" to specify this is a default button; the regular state is "normal". Note that setting this option doesn't create an event binding that will make the Return or Enter key activate the button; that you have to do yourself.

The Command Callback

The "command" option is used to provide an interface between the button's action and your application. When the user clicks the button, the script provided by the option is evaluated by the interpreter.

You can also ask the button to invoke the command callback from your application. This is useful so that you don't need to repeat the command to be invoked several times in your program; so you know if you change the option on the button, you don't need to change it elsewhere too.

.button invoke
button invoke
$button->invoke

Button State

Buttons and many other widgets can be in a normal state where they can be pressed, but can also be put into a disabled state, where the button is greyed out and cannot be pressed. This is done when the button's command is not applicable at a given point in time.

All themed widgets carry with them an internal state, which is a series of binary flags. You can set or clear these different flags, as well as check the current setting using the "state" and "instate" methods. Buttons make use of the "disabled" flag to control whether or not the user can press the button. For example:

.button state disabled            ;# set the disabled flag, disabling the button
.button state !disabled           ;# clear the disabled flag
.button instate disabled          ;# return 1 if the button is disabled, else 0
.button instate !disabled         ;# return 1 if the button is not disabled, else 0
.button instate !disabled {mycmd} ;# execute 'mycmd' if the button is not disabled
button.state('disabled')            ;# set the disabled flag, disabling the button
button.state('!disabled')           ;# clear the disabled flag
button.instate('disabled')          ;# return true if the button is disabled, else false
button.instate('!disabled')         ;# return true if the button is not disabled, else false
button.instate('!disabled', 'cmd')  ;# execute 'cmd' if the button is not disabled
$button->state("disabled")                  ;# set the disabled flag, disabling the button
$button->state("!disabled")                 ;# clear the disabled flag
$button->instate("disabled")                ;# return 1 if the button is disabled, else 0
$button->instate("!disabled")               ;# return 1 if the button is not disabled, else 0
$button->instate("!disabled", sub {mycmd})  ;# execute 'mycmd' if the button is not disabled

Using "state"/"instate" replaces the older "state" configuration option (which took the values "normal" or "disabled"). This configuration option is actually still available in Tk 8.5, but "write-only", which means that changing the option calls the appropriate "state" command, but other changes made using the "state" command are not reflected in the option. This is only for compatibility reasons; you should change your code to use the new state vector.

The full list of state flags available to themed widgets is: "active", "disabled", "focus", "pressed", "selected", "background", "readonly", "alternate", and "invalid". These are described in the themed widget reference; not all states are meaningful for all widgets. It's also possible to get fancy in the "state" and "instate" methods and specify multiple state flags at the same time.

 

Checkbutton

A checkbutton is like a regular button, except that not only can the user press it, which will invoke a command callback, but it also holds a binary value of some kind (i.e. a toggle). Checkbuttons are used all the time when a user is asked to choose between e.g. two different values for an option.


Checkbutton Widgets

Checkbuttons are created using the ttk::checkbutton command, and typically set up at the same time:

ttk::checkbutton .check -text "Use Metric" -command "metricChanged" 
	    -variable measuresystem -onvalue metric -offvalue imperial

Checkbuttons are created using the Tk::Tile::CheckButton class, and typically set up at the same time:

$measureSystem = TkVariable.new
check = Tk::Tile::CheckButton.new(parent) {text 'Use Metric'; 
	    command 'metricChanged'; variable $measureSystem; 
	    onvalue 'metric'; offvalue 'imperial'}

Checkbuttons are created using the new_ttk__checkbutton method, a.k.a. Tkx::ttk__checkbutton, and typically set up at the same time:

$check = $parent->new_ttk__checkbutton(-text => "Use Metric", -command => sub {metricChanged},
	    -variable => \$measuresystem, -onvalue => "metric", -offvalue => "imperial"

Checkbuttons use many of the same options as regular buttons, but add a few more. The "text", "textvariable", "image", and "compound" options control the display of the label (next to the check box itself), and the "state" and "instate" methods allow you to manipulate the "disabled" state flag to enable or disable the checkbutton. Similarly, the "command" option lets you specify a script to be called everytime the user toggles the checkbutton, and the "invoke" method will also execute the same callback.

Widget Value

Unlike buttons, checkbuttons also hold a value. We've seen before how the "textvariable" option can be used to tie the label of a widget to a variable in your program; the "variable" option for checkbuttons behaves similarly, except it is used to read or change the current value of the widget, and updates whenever the widget is toggled. By default, checkbuttons use a value of "1" when the widget is checked, and "0" when not checked, but these can be changed to just about anything using the "onvalue" and "offvalue" options.

What happens when the linked variable contains neither the on value or the off value (or even doesn't exist)? In that case, the checkbutton is put into a special "tristate" or indeterminate mode; you'll sometimes see this in user interfaces where the checkbox holds a single dash rather than being empty or holding a check mark. When in this state, the state flag "alternate" is set, so you can check for it with the "instate" method:

.check instate alternate
check.instate('alternate')
$check->instate("alternate")

Because the checkbutton won't automatically set (or create) the linked variable, your program needs to make sure it sets the variable to the appropriate starting value.

 

Radiobutton

A radiobutton lets you choose between one of a number of mutually exclusive choices; unlike a checkbutton, it is not limited to just two choices. Radiobuttons are always used together in a set, and are good when the number of choices is fairly small, e.g. 3-5.


Radiobutton Widgets

Radiobuttons are created using the ttk::radiobutton command, typically as a set:

ttk::radiobutton .home -text "Home" -variable phone -value home
ttk::radiobutton .office -text "Office" -variable phone -value office
ttk::radiobutton .cell -text "Mobile" -variable phone -value cell

Radiobuttons are created using the Tk::Tile::RadioButton class, and typically as a set:

$phone = TkVariable.new
home = Tk::Tile::RadioButton.new(parent) {text 'Home'; variable $phone; value 'home'}
office = Tk::Tile::RadioButton.new(parent) {text 'Office'; variable $phone; value 'office'}
cell = Tk::Tile::RadioButton.new(parent) {text 'Mobile'; variable $phone; value 'cell'}

Radiobuttons are created using the new_ttk__radiobutton method, a.k.a. Tkx::ttk__radiobutton, typically as a set:

$home = $parent->new_ttk__radiobutton(-text => "Home", -variable => \$phone, -value => "home");
$office = $parent->new_ttk__radiobutton(-text => "Office", -variable => \$phone, -value => "office");
$cell = $parent->new_ttk__radiobutton(-text => "Mobile", -variable => \$phone, -value => "cell");

Radiobuttons share most of the same configuration options as checkbuttons. One exception is that the "onvalue" and "offvalue" options are replaced with a single "value" option. Each of the radiobuttons of the set will have the same linked variable, but a different value; when the variable has the given value, the radiobutton will be selected, otherwise unselected. When the linked variable does not exist, radiobuttons also display a "tristate" or indeterminate, which can be checked via the "alternate" state flag.

 

Entry

An entry presents the user with a single line text field that they can use to type in a value. These can be just about anything: their name, a city, a password, social security number, and so on.


Entry Widgets

Entries are created using the ttk::entry command:

ttk::entry .name -textvariable username

Entries are created using the Tk::Tile::Entry class:

$username = TkVariable.new
name = Tk::Tile::Entry.new(parent) { textvariable $username }

Entries are created using the new_ttk__entry method, a.k.a. Tkx::ttk__entry:

$name = $parent->new_ttk__entry(-textvariable => \$username)

A "width" configuration option may be specified to provide the number of characters wide the entry should be, allowing you for example to provide a shorter entry for a zip or postal code.

We've seen how checkbutton and radiobutton widgets have a value associated with them. Entries do as well, and that value is normally accessed through a linked variable specified by the "textvariable" configuration option. Note that unlike the various buttons, entries don't have a separate text or image beside them to identify them; use a separate label widget for that.

You can also get or change the value of the entry widget directly, without going through the linked variable. The "get" method returns the current value, and the "delete" and "insert" methods let you change the contents, e.g.

puts "current value is [.name get]"
.name delete 0 end           ; # delete between two indices, 0-based
.name insert 0 "your name"   ; # insert new text at a given index
puts ("current value is #{name.get}")
name.delete(0, end)          ; # delete between two indices, 0-based
name.insert(0, 'your name')  ; # insert new text at a given index
print "current value is " . $name->get
$name->delete(0, "end")         ; # delete between two indices, 0-based
$name->insert(0, "your name")   ; # insert new text at a given index

Note that entry widgets do not have a "command" option which will invoke a callback whenever the entry is changed. To watch for changes, you should watch for changes on the linked variable. See also "Validation", below.

Passwords

Entries can be used for passwords, where the actual contents are displayed as a bullet or other symbol. To do this, set the "show" configuration option to the character you'd like to display, e.g. "*".

Widget States

Like the various buttons, entries can also be put into a disabled state via the "state" command (and queried with "instate"). Entries can also use the state flag "readonly"; if set, users cannot change the entry, though they can still select the text in it (and copy it to the clipboard). There is also an "invalid" state, set if the entry widget fails validation, which leads us to...

Validation

validate (controls overall validation behavior) - none (default), key (on each keystroke, runs before - prevalidation), focus/focusin/focusout (runs after.. revalidation), all
* validatecommand script (script must return 1 or 0)
* invalidcommand script (runs when validate command returns 0)
- various substitutions in scripts.. most useful %P (new value of entry), %s (value of entry prior to editing)
- the callbacks can also modify the entry using insert/delete, or modify -textvariable, which means the in progress edit is rejected in any case (since it would overwrite what we just set)
* .e validate to force validation now

 

Combobox

A combobox combines an entry with a list of choices available to the user. This lets them either choose from a set of values you've provided (e.g. typical settings), but also put in their own value (e.g. for less common cases you don't want to include in the list).


Combobox Widgets

Comboboxes are created using the ttk::combobox command:

ttk::combobox .country -textvariable country

Comboboxes are created using the Tk::Tile::Combobox class:

$countryvar = TkVariable.new
country = Tk::Tile::Combobox.new(parent) { textvariable $countryvar }

Comboboxes are created using the new_ttk__combobox method, a.k.a. Tkx::ttk__combobox:

$country = $parent->new_ttk__combobox(-textvariable => \$countryvar)

Like entries, the "textvariable" option links a variable in your program to the current value of the combobox. As with other widgets, the linked variable will not be set automatically. You can also get the current value using the "get" method, and change the current value using the "set" method (which takes a single argument, the new value).

A combobox will generate a "<ComboboxSelected>" virtual event that you can bind to whenever its value changes.

bind .country <<ComboboxSelected>> { script }
country.bind("<ComboboxSelected>") { script }
$country->bind("<<ComboboxSelected>>", sub { script })

Predefined Values

You can provide a list of values the user can choose from using the "values" configuration option:

.country configure -values [list USA Canada Australia]
country['values'] = [ 'USA', 'Canada', 'Australia'] 
$country->configure(-values => "USA Canada Australia")

If set, the "readonly" state flag will restrict the user to making choices only from the list of predefined values, but not be able to enter their own (though if the current value of the combobox is not in the list, it won't be changed).

As a complement to the "get" and "set" methods, you can also use the "current" method to determine which item in the predefined values list is selected (call "current" with no arguments, it will return a 0-based index into the list, or -1 if the current value is not in the list), or select one of the items in the list (call "current" with a single 0-based index argument).

Want to associate some other value with each item in the list, so that your program can refer to some actual meaningful value, but it gets displayed in the combobox as something else? You'll want to have a look at the section entitled "Keeping Extra Item Data" when we get to the discussion of listboxes in a couple of chapters from now.

The Grid Geometry Manager

We'll take a bit of a break from talking about different widgets (what to put onscreen), and focus instead on geometry management (where to put it). We introduced the general idea of geometry management in the "Tk Concepts" chapter; here, we focus on one specific geometry manager: grid.

As you've seen, grid lets you layout widgets in columns and rows. If you're familiar with using HTML tables to do layout, you'll feel right at home here. This chapter describes the various ways you can tweak grid to give you all the control you need for your user interface.

Grid is one of several geometry managers, but it's mix of power, flexibility and ease of use, along with its natural fit with today's layouts (that rely on alignment of widgets) make it the best choice for general use. There are other geometry managers: "pack" is also quite powerful, but harder to use and understand; "place" gives you complete control of positioning each element; we'll see even widgets like paned windows, notebooks, canvas and text can act as geometry managers.

Grid was first introduced to Tk in 1996, several years after Tk became popular, and took a while to catch on. Before that, developers had always used "pack" to do constraint-based geometry management. When grid came out, many developers kept using pack, and you'll still find it used in many Tk programs and documentation. While there's nothing technically wrong with it, the algorithm's behavior is often hard to understand. More importantly, because the order that widgets are packed is significant in determining layout, modifying existing layouts can be more difficult.
 
Grid has all the power of pack, generally produces nicer layouts (because it makes it easy to align widgets both horizontally and vertically), and is easier to learn and use. Because of that, we think grid is the right choice for most developers most of the time. Start your new programs using grid, and switch old ones to grid as you're making changes to an existing user interface.

The reference documentation for grid provides an exhaustive description of grid, its behaviors and all options.

 

Columns and Rows

Using grid, widgets are assigned a "column" number and a "row" number, which indicates their relative position to each other. All widgets in the same column will therefore be above or below each other, while those in the same row will be to the left or right of each other.

Column and row numbers must be integers, with the first column and row starting at 0. You can leave gaps in column and row numbers (e.g. column 0, 1, 2, 10, 11, 12, 20, 21), which is handy if you plan to add more widgets in the middle of the user interface at a later time.

The width of each column (or height of each row) depends on the width or height of the widgets contained within the column or row. This means when sketching out your user interface, and dividing it into rows and columns, you don't need to worry about each column or row being equal width.

 

Spanning Multiple Cells

Widgets can take up more than a single cell in the grid; to do this, you'll use the "columnspan" and "rowspan" options when gridding the widget. These are analogous to the "colspan" and "rowspan" attribute of HTML tables.

Here is an example of creating a user interface that has multiple widgets, some that take up more than a single cell.


Gridding multiple widgets

ttk::frame .c
ttk::frame .c.f -borderwidth 5 -relief sunken -width 200 -height 100
ttk::label .c.namelbl -text Name
ttk::entry .c.name
ttk::checkbutton .c.one -text One -variable one -onvalue 1; set one 1
ttk::checkbutton .c.two -text Two -variable two -onvalue 1; set two 0
ttk::checkbutton .c.three -text Three -variable three -onvalue 1; set three 1
ttk::button .c.ok -text Okay
ttk::button .c.cancel -text Cancel

grid .c -column 0 -row 0
grid .c.f -column 0 -row 0 -columnspan 3 -rowspan 2
grid .c.namelbl -column 3 -row 0 -columnspan 2
grid .c.name -column 3 -row 1 -columnspan 2
grid .c.one -column 0 -row 3
grid .c.two -column 1 -row 3
grid .c.three -column 2 -row 3
grid .c.ok -column 3 -row 3
grid .c.cancel -column 4 -row 3
require 'tk'
require 'tkextlib/tile'
root = TkRoot.new

content = Tk::Tile::Frame.new(root)
frame = Tk::Tile::Frame.new(content) {borderwidth 5; relief "sunken"; width 200; height 100}
namelbl = Tk::Tile::Label.new(content) {text "Name"}
name = Tk::Tile::Entry.new(content)
$option_one = TkVariable.new( 1 )
one = Tk::Tile::CheckButton.new(content) {text "One"; variable $option_one; onvalue 1}
$option_two = TkVariable.new( 0 )
two = Tk::Tile::CheckButton.new(content) {text "Two"; variable $option_two; onvalue 1}
$option_three = TkVariable.new( 1 ) 
three = Tk::Tile::CheckButton.new(content) {text "Three"; variable $option_three; onvalue 1}
ok = Tk::Tile::Button.new(content) {text "Okay"}
cancel = Tk::Tile::Button.new(content) {text "Cancel"}

content.grid :column => 0, :row => 0 
frame.grid :column => 0, :row => 0, :columnspan => 3, :rowspan => 2
namelbl.grid :column => 3, :row => 0, :columnspan => 2
name.grid :column => 3, :row => 1, :columnspan => 2
one.grid :column => 0, :row => 3
two.grid :column => 1, :row => 3
three.grid :column => 2, :row => 3
ok.grid :column => 3, :row => 3
cancel.grid :column => 4, :row => 3

Tk.mainloop
use Tkx;

my $mw = Tkx::widget->new(".");
my $content = $mw->new_ttk__frame;
my $frame = $content->new_ttk__frame(-borderwidth => 5, -relief => "sunken", -width => 200, -height => 100);
my $namelbl = $content->new_ttk__label(-text => "Name");
my $name = $content->new_ttk__entry;
$option_one = 1; $option_two = 0; $option_three = 1;
my $one = $content->new_ttk__checkbutton(-text => "One", -variable => \$option_one, -onvalue => 1); 
my $two = $content->new_ttk__checkbutton(-text => "Two", -variable => \$option_two, -onvalue => 1);
my $three = $content->new_ttk__checkbutton(-text => "Three", -variable => \$option_three, -onvalue => 1);
my $ok = $content->new_ttk__button(-text => "Okay");
my $cancel = $content->new_ttk__button(-text => "Cancel");

$content->g_grid(-column => 0, -row => 0);
$frame->g_grid(-column => 0, -row => 0, -columnspan => 3, -rowspan => 2);
$namelbl->g_grid(-column => 3, -row => 0, -columnspan => 2);
$name->g_grid(-column => 3, -row => 1, -columnspan => 2);
$one->g_grid(-column => 0, -row => 3);
$two->g_grid(-column => 1, -row => 3);
$three->g_grid(-column => 2, -row => 3);
$ok->g_grid(-column => 3, -row => 3);
$cancel->g_grid(-column => 4, -row => 3);

Tkx::MainLoop;

 

Layout within the Cell

Because the width of a column (and height of a row) depends on all the widgets that have been added to it, the odds are that at least some widgets will have a smaller width or height than has been allocated for the cell its been placed in. So the question becomes, where exactly should it be put within the cell?

By default, if a cell is larger than the widget contained in it, the widget will be centered within it, both horizontally and vertically, with the master's background showing in the empty space around it. The "sticky" option can be used to change this default behavior.

The value of the "sticky" option is a string of 0 or more of the compass directions "nsew", specifying which edges of the cell the widget should be "stuck" to. So for example, a value of "n" (north) will jam the widget up against the top side, with any extra vertical space on the bottom; the widget will still be centered horizontally. A value of "nw" (north-west) means the widget will be stuck to the top left corner, with extra space on the bottom and right.

Specifying two opposite edges, such as "we" (west, east) means that the widget will be stretched, in this case so it is stuck both to the left and right edge. So the widget will then be wider than its "ideal" size. Most widgets have options that can control how they are displayed if they are larger than needed. For example, a label width has an "anchor" option which controls where the text of the label will be positioned.

If you want the widget to expand to fill up the entire cell, grid it with a sticky value of "nsew" (north, south, east, west) meaning it will stick to every side.

 

Handling Resize

If you've taken a peek below and added the extra "sticky" options to our example, when you try it out you'll notice things still don't look quite right (the entry is lower on the screen then we'd want), and things are even worse if you try to resize the window — nothing moves at all!

It looks like "sticky" may tell Tk how to react if the cell or column do resize, but doesn't actua