Cincinnati Linux Users Group
Making Linux Available to Everyone!

Abstract

This presentation was given by Monty Stein at the January meeting of the Cincinnati Linux Users Group.

History

TCL (Tool Control Language) was developed by John Ousterhout (oh-stir-hout) at the University of California (Berkeley) and who is currently working at Sun Microsystems on further TCL development. TCL was originally meant to be a command interpreter for tools, hence the name. Instead of reinventing the wheel and writing a command interpreter from scratch, TCL would be linked in and the needed tool specific commands added. Because of this you are likely to encounter TCL in the strangest of places.

Comments

All comments begin with the "#" character. The difference between TCL and other languages is that TCL actually uses the "#" as a command that ignores all it's arguments. This means that comments can only go where the parser expects a command to start.

Quoting

A All instances of $variables are expanded when encountered unless some type of quoting is used.
\ singly quoted character follows
"" allows variable expansion, newlines are allowed
{} do not expand variables, when this is used with a control structure, it is reinvoked to provide variable expansion.
[] run contents as command and return the result
Note: expr calls will perform variable expansion twice: once when the expr is encountered and again when expr sees it's arguments.

Variables

Variables are assigned in TCL using set. The set command can also be used to read variables; this is useful when the variable is too complex to represent using the $ notation (such as in certain types of associative arrays). Simple types include integers, floats, strings and lists (counted from 0). Associative arrays are defined by parenthesis.
$ tclsh
% set a 10
10
% set b 4
4
% expr $a+$b
14
% set t("junk") "more junk"
more junk
% puts $t("junk")
more junk
% puts $t("no more junk")
can't read "t("no more junk")": no such element in array
% set l {10 20 30 40 50 "oh, look!"}
10 20 30 40 50 "oh, look!"
% lindex $l 3
40

Any globally scoped variables that are going to be used within a function must be declared as global, otherwise a variable with the same name will be created within the function's scope.

Arithmetic

All arithmetic except for simple increment is done through the expr call. All the standard math functions (sin,cos,sqrt) are supported.
$ tclsh
% set a 3
3
% set b 4
4
% expr sqrt($a*$a+$b*$b)
5.0
% incr b
5
% incr a -1
2
% expr sqrt($a*$a+$b*$b)
5.38516
% 

Control structures

If/Switch statements

if { $a!=1 } {
  #true stuff here
} elseif {$b==0} {
  #other stuff here
} else {
  #yet more stuff
}
Switch is similar to it's counterpart in C except is has a richer set of matches. These are selected by flags that are passed just after the switch command:
-exact Must be an exact match
-glob Shell like expansions (*,? and the like)
-regexp Regular expressions
-- End of flags
% set input "foople"
foople
% switch -regexp -- $input {
  foo* { puts "we got a foo!" }
  bar* { puts "that's in a bar" }
  default { puts "that cannot be found" }
}
we got a foo!
% 

Procedures

% proc distance {a b} {
  set ret [expr sqrt($a*$a+$b*$b)]
  return $ret
}
% set r [distance 1 2]
2.23607

Looping

While

% set a 0
0
% while {$a<3} {
  puts $a
  incr a
}
0
1
2
$

For

% for {set a 0} {$a<3} {incr a} {
  puts $a
}
0
1
2
% 

Foreach (walks a list)

% foreach a {0 1 2} {
  puts $a
}
0
1
2
% 



Tk Overview

Each object on the screen that has some sort of behavior associated with it (even static text) is called a Widget. This behavior is defined through bindings that are connected with various X window events. For example, a button widget that receives a mouse-button-1 down event would change its appearance to look like it was pressed and invoke some command.

Tk is a library originally intended to provide TCL with X11 window interface. It has been linked with a number of other languages to give them the same X abilities. It has a similar look-and-feel to Motif.

The layout of the widgets inside their parent window in Tk is handled by the packer (or placer) that uses a constraint system to handle the fit and size of the various widgets. By using a constraint system, events like the resizing of a window can have all the internal widgets resize themselves automatically in a pleasing fashion.

Since the whole basis of interaction with Tk is through X events, the same mechanism can be used to have events be received at a later time. This way applications that need to poll some resource but still react to user driven events (such as a mailer) can be written simply.


Tk itself

You can do a lot in a few lines of code (here using TCL):
    #!/usr/bin/env wish
    button .b -text "Press to Quit" -command exit
    pack .b
would create:

All the widgets have a default action that is overridden with configuration options that are passed in when the widget is defined or at a later time. In this case the parent window (by default ".") contains a button widget that has an appearance and a command associated with it.

Lazy?

There are GUI builders (SpecTcl for example) out there that can do a lot of the work for you.

How to run with Tk

The Tk libraries have been integrated into many languages such as TCL (the language it was written for), Perl, Python, Scheme, and on and on... Here are the basics for 3 of them:

TCL

To run Tk under TCL use the wish command.
All the examples included in this document use TCL.

Perl

#!/usr/bin/env perl
use Tk;

my($win) = MainWindow->new();
[...]
&Tk::MainLoop;
is all that you need. Then, $win is your toplevel to do with as you please. Tk::MainLoop should be the last thing the program does. That will start Tk up.

Python

Under python, see if: "from Tkinter import *" raises an exception. If not, your copy of python has Tk compiled in. The core functionality is generally inherited from Frame. Each widget group would generally be a class that inherits from Frame and packs itself into it's own window. Here is a minimal program:
#!/usr/bin/env python
from Tkinter import *

class Hello(Frame):
  def __init__(self,parent=None):
    Frame.__init__(self,parent)
    f=Frame()
    Button(f,text="Hello World",anchor=CENTER,command=f.quit).pack()
    f.pack()

hello=Hello()
try:
  hello.mainloop()
except KeyboardInterrupt:
  hello.quit()

Widgets

Common widget functions

Configure

Any widget option can be changed using the configure call. For example:
$ wish
% label .l -text Hello -bg blue
.l
% pack .l
% 


% .l configure -bg red
% 

Bind

The bind command allows a command to be attached to an X event for a particular window or widget. The dot (".") window is the parent. Bindings take the form of:
     < MODIFIERs - TYPE - DETAIL >
Where MODIFIER is zero or more of:
Alt Button1 or B1 Button2 or B2 Button3 or B3 Button4 or B4 Button5 or B5
Control Double Lock Meta or M Mod1, M1 Mod2 or M2
Mod3 or M3 Mod4 or M4 Mod5 or M5 Shift Triple  
Where TYPE is one of:
ButtonPress or Button ButtonRelease Circulate Colormap Configure Destroy
Enter Expose FocusIn FocusOut Gravity KeyPress or Key
KeyRelease Leave Map Motion Property Reparent
Unmap Visibility        
DETAIL identifies a particular button or key for the binding.
This example reports the coordinates that the pointer is hovering over:
canvas .c -width 200 -height 200 
label .coord -text "Move the mouse" -relief raised
pack .c .coord -fill x

bind . < Key-q> exit
bind .c < Motion> ".coord configure -text \"(%x %y)\""

Packer

The packer is what actually places the widgets on the screen. It uses a constraint system to simplify the definition of the layout. When the packer starts to pack inside of a window, all of the window's area is considered to be "unpacked". Any new widgets are placed inside of that unpacked area using the flags that are supplied when pack is called. By default the widget is packed in the top of the space and the remaining unpacked space is left at the bottom of the packed widget. Any further packs will take place there. In normal usage, the packer will handle events like window resizing with all the widgets centering and expanding themselves automatically. The more common flags that can be supplied when calling pack are:
-side left,right,top,bottom
-fill x,y,both,none
-in window
-expand 0,1
An example that will handle window resizing gracefully:
#!/usr/bin/env wish
text .t -width 40 -height 10 -wrap word -yscrollcommand ".s set"
scrollbar .s -orient vertical -command ".t yview"
pack .t -side left 
pack .s -side left -fill y

and after stretching the window:

Not quite right, the text area doesn't know that it can fill the space, it is restricted to the size passed in when it was created. Changing it's packing should fix it:
pack .t -side left -fill both

Still not right. The text area is restricted on how big it can grow by the presence of the scrollbar. We can tell the packer to have it fill ALL the available space when it is packed so it will push the scrollbar over.

pack .t -side left -fill both -expand 1

Specific Widgets

Frame

Frames act as containers for other widgets (and through bind can have their own behavior). There is a special flag for pack to force a wiget to be packed inside of a frame.
#!/usr/bin/env wish
frame .f -relief raised -borderwidth 4
button .f.b1 -text "Inside"
button .f.b2 -text "Frame"
pack .f.b1 .f.b2 -in .f -side left
pack .f
button .b -text "Outside Frame"
pack .b

Label

A label can display text or bitmap images. Multiple lines of text can be displayed if the text string contains newlines. There is no intended interaction with the widget.
#!/usr/bin/env wish
set tick_value 0

proc tick {} {
  global tick_value
  set tick_value [ incr tick_value ]
  .ticker configure -text $tick_value
  # wait 1 second to reinvoke
  after 1000 tick  
}


label .l1 -text "This" -relief raised
label .l2 -text "That" -relief sunken
label .l3 -bitmap @/usr/include/X11/bitmaps/calculator
label .ticker 
pack .l1 -side left
pack .l2 -side right
pack .l3 .ticker

# start the ball rolling
tick  

bind . < Key-q> exit

Entry

Entry widgets are the main way that a user would interact with an application using text fields. Each one contains one line of text and by default allows scrolling, insertion/deletion of characters and cut/paste.
set name "Your name"
set phone "x1234"

label .namelabel -text "Name"
entry .name -width 30 -textvariable name
label .phonelabel -text "Phone"
entry .phone -width 9 -textvariable phone
pack .namelabel .name .phonelabel .phone -side left

Button

Buttons are the simplest interactive widget. They display some text or a bitmap and can invoke a command when pressed. If needed a button can be disabled and then cannot be pressed and will display a grayed appearance.
#!/usr/bin/env wish

label .m -text "The Universe has crashed..."
pack .m
button .ok -text Ok -state disabled
button .cancel -text Cancel -command exit
pack .ok -side left
pack .cancel -side right 



Listbox

Listboxes show a list of choices (like used in a file browser) and indicate which choice that the pointer is over by highlighting it. To actually select a choice, a binding must be set up to the function that will handle it.
#!/usr/bin/env wish

proc show_selection {y} {
  puts [.l get [.l nearest $y]]
}
 
listbox .l 
.l insert end 1 2 3 4 5 6 7 8 9 10
.l insert 0 "0"
pack .l

bind .l  "show_selection %y"



Scrollbar

Scrollbars can be easily attached to any widget that supports scrolling such as Listboxes and Canvases. The set command is the main interaction into the scrollbar. See the manual pages for details on the arguments that can be passed.
#!/usr/bin/env wish
proc show_selection {y} {
  puts [.l get [.l nearest $y]]
}
 
listbox .l -height 5
.l insert end 1 2 3 4 5 6 7 8 9 10
.l insert 0 "0"
scrollbar .s -command ".l yview" 
.l configure -yscrollcommand ".s set"
pack .l -side left
pack .s -side right -fill y

bind .l  "show_selection %y"

Radiobutton

Radiobuttons act like the buttons on your car's radio (duh!). All the button selections are mutually exclusive: selecting one will deselect all the others that are associated with it.
#!/usr/bin/env wish

proc change {} {
  global color
  .current configure -text $color -bg $color
}

frame .f
set color "blue"
foreach i {"blue" "red" "green"} {
  radiobutton .$i -text $i -value $i -variable color -command change -anchor w
  pack .$i -anchor w -side left -in .f
}
pack .f
label .current -text $color -bg $color
pack .current -fill x 




Text

A text area is a free format text entry area. The example for the packer shows the basic structure. It is that simple.

Canvas

Canvas is a free form drawing area for line graphics. Because of the complexity, I'm not going to cover it here. See the manual pages for all the gory details.

Putting it all together

Examples

File Browser


Source in: TCL(htmlified) Python(htmlified)

Interactive Spline Drawer


Source in: TCL(htmlified) Perl(htmlified)

Application Laucher Used for Demos

Source in: Python(htmlified)

Grail browser

Home page:http://grail.cnri.reston.va.us/grail/

All the examples in one place

In TCL:

  • bind_example.tcl
  • browser.tcl
  • button_example.tcl
  • entry_example.tcl
  • frame_example.tcl
  • intro_example.tcl
  • label_example.tcl
  • listbox_example.tcl
  • packer_example1.tcl
  • packer_example2.tcl
  • packer_example3.tcl
  • radiobutton_example.tcl
  • scrollbar_example.tcl
  • spline.tcl
  • viewer.tcl

    In Python:

  • browser.py
  • launcher.py

    In Perl:

  • spline.pl

    CLUG HOME | Events | Directions | Members | Mailing Lists (archives, FAQ)
    Resources | Search | Library | Presentations | Contributions | Bylaws | Board Minutes


    Linux Power
    CLUG Contact: Jeff Gilton (jeff@jsgis.com)
    Web Page Contact: webmaster@clug.org
    Last Modified: