Hello, Widgets! (a rock-bottom basic tutorial in Tcl and Tk)


A rock-bottom basic tutorial in Tcl and Tk, by David McClamrock

Lesson 1: A button that will really do something

(Topics: buttons, labels, and entry widgets; variables and values; "bind," "configure," "eval," "exec" commands; "-command" option

If you're anything like me, you want to write computer programs that do things; you don't want anything like a button that says "Hello, World" and then disappears when you click it. So, let's pass up that classic ultra-simple example of Tcl/Tk programming and go right to something a bit more useful. How about a button that will run a program of your choice when you click it, and a couple of entry widgets (the familiar little boxes you can type one line of text into) to tell the button what the program of your choice is?

Start the WISH Interpreter in interactive mode. (On Linux and other Unix-type systems, you do this by typing wish at the command line in a console window.) On the next line, you'll see the Tcl prompt (a percent sign). Somewhere outside the console, you'll see a blank box entitled "wish." By typing commands at the prompt, you can make widgets appear in the box and give them the power to do things for you. (You don't have to type each command at the prompt every time, of course. We'll get to that pretty soon.)

The first widget you'll want is a clickable button. Easy: type the following command at the prompt and hit the Enter key (it's called <Key-Return> in Tcl).

button .prog -text "Program Name Goes Here"

Nothing happens in the blank box. But now enter this command and see what happens:

pack .prog -fill both

The box isn't blank any more; it has a button in it. Here's what's going on. The command "button" creates a button. The next word, ".prog," gives the button a name. Then comes an option, "-text" (options usually start with hyphens). The quotation marks around "Program Name Goes Here" are needed to group the words between the quotation marks--to tell the interpreter to take them all together as a single unit. (If you didn't have the quotation marks, you'd get an error message telling you that "Name" is an unknown option!) Finally, the "pack" command makes the button visible. (Other commands, "grid" and "place," will make widgets visible too; we'll get to them.) The "-fill both" option means that the widget should fill all the space available to it, in both height and width.

You can give widgets almost any names you want; just keep track so you don't give two of them the same name. Every widget's name has to begin with a dot. The name of the blank box, the "parent" of all the other widgets (which are its "children"), is a single dot. If you decide you don't want any widgets after all, you can get rid of them all at once by entering this command:

destroy .

which will do the same thing as this one:


(This will exit the WISH Interpreter and you'll have to restart it. If you don't want to do that, just take my word for what will happen if you enter "destroy .".)

If you've got a button and you haven't destroyed it, now you can make it do something--with the help of a couple of entry widgets. Enter these commands:

label .disptitle -text "Display Name:"

entry .dispname -bg white

label .offtitle -text "Official Name:"

entry .offname -bg white

pack .prog .disptitle .dispname .offtitle .offname -fill both

These lines of code have a couple of features that the "button" lines didn't. First, the "-bg" option sets the background color of the entry widgets. Second, the "pack" command is now applied to four widgets, not just one. To the interpreter, the spaces between the widget names here mean "and"--until the interpreter gets to the hyphen at "-fill," which signals the end of the list of widgets to pack and the beginning of the options.

The label widgets don't do anything except to display text (or images, if you insist). The entry widgets can be used to change the text displayed on the button, and to make the button do things. Here's how.

First, use the "bind" command to get text from the entry widgets when <Key-Return> is pressed, to hold this text in "variables" in the computer's memory, and to change the text displayed in the button:

bind .offname <Key-Return> {
    set dispname [.dispname get]
    set offname [.offname get]
    .prog configure -text $dispname
    .dispname delete 0 end
    .offname delete 0 end
    focus .dispname

This tells the interpreter that, whenever <Key-Return> is pressed while the cursor is in the ".offname" entry widget, the text from ".dispname" should be stored in a variable called "dispname"; the text from ".offname" should be stored in a variable called "offname"; and the button (.prog) should show the text contained in the variable "dispname"--the current value of that variable. (You get the value of a variable by putting a dollar sign in front of the variable name. Easy--value, dollars, get it? After you've gotten a few hundred error messages for forgetting to put dollar signs in front of variable names, it will become almost second nature to put them in.) Then the text should be deleted from the beginning (position "0") to the end (position "end") of each entry widget, and the cursor "focus" should go back to ".dispname."

Type the "display name" of a program on your system (e.g., FileRunner) into the top entry widget, and the "official name" (the name you would use to run the program from the command line, e.g., fr for FileRunner) into the bottom entry widget. Hit <Key-Return> with the cursor in the bottom entry widget. The text from ".dispname" will appear in the button; the text from ".offname" will vanish, but it will still be stored in a variable in the computer's memory.

Now configure the button (.prog) to run the program when the button is clicked:

.prog configure -command {
    eval exec $offname &

[One reader wonders: why not just exec $offname & ? In fact, won't the latter be more successful if $offname embeds a blank, for example? AMG: See the [exec] examples on the {*} page for the explanation.]

This tells the interpreter that, whenever ".prog" is clicked, it should run ("eval exec"--more on this later) the program named in the current value of the variable "offname" ($offname), and then release the button to be available for more commands (&). (If you didn't include the "&", the button would get stuck until you exited the program that the button told the interpreter to run.)

Click the button and, lo and behold, you'll see FileRunner opening up--if you have it installed on your system and in your "path" (a list of directories that are checked automatically for programs). If you don't, pick a program that you do have installed and that you know how to run from the command line.

The Tcl/Tk code in this lesson is a simple example of some things you'll do again and again (unless you quit trying to learn Tcl):

  1. Set up some widgets.
  2. Allow the user (in this case, you) to give your program some input through widgets.
  3. Use the "bind" command, the "configure" command, and/or the "-command" option to give widgets the power to do things by executing Tcl code.

There's one little fatal drawback to running your programs by using the WISH Interpreter in interactive mode: it's less efficient than just running the programs from the command line! To get greater efficiency, you need to store a lot of code in a program file and run it all at once, in "batch" (non-interactive) mode. You also need to store a list of programs in a data file and to be able to use the list at will, so you don't have to keep typing the program names over and over. We'll get to these highly beneficial actions in the next lesson.

11-01-2012 : Nice tutorial. This is exactly what I am looking for. A complete and simple example for everything needed to generate a GUI. Thanks for writing it. Looking forward to the next lesson. --JeRoen.

Cody - 2016-11-22 09:14:24

How would you go about executing a .tcl file instead of an .exe file with this example? Is that even possible on Windows?

MG Sure, as long as you have the file association set up.

  exec {*}[auto_execok start] $path_to_tcl_script &

Or you can run it with a specific Tcl interpreter (like the one your current script is using) rather than the app associated with .tcl in Windows:

  exec [info nameofexecutable] $path_to_tcl_script &