Welcome to Changhai Lu's Homepage

I know nothing except the fact of my ignorance.

- Socrates

 
INFO
 
 
 
Email Me
All English Contents
 
STATS
 
 
 
Article Views:
13,394
Site Views:
16,486,272

Selected Topics in Tcl/Tk

- by Changhai Lu -

Unless otherwisely stated, this article makes use of Tcl/Tk 8.3.2. The basic features of Tcl/Tk are fairly stable, therefore changes made by later versions should have little impact on what we covered here. The syntax highlight of the code is generated by Tcl2Html.

0. Contents

  1. Default Arguments - Not Just for Convenience
  2. Tk Option Database - Look & Feel Goes Global
  3. Images on Control Elements
  4. Behind the Curtain - Make Use of the Tk Internal Code

1. Default Arguments - Not Just for Convenience

Most C++ programmers are familiar with the concept of default argument, it is a convenient way to declare a function such that it can be used with less number of arguments (the compiler will insert default values for the missing arguments). For instance a function that calculates the total price of an order may take the unit price of the merchandise and the quantity as arguments. If most customers only purchase a single unit, you may want to set a default value ("1" in this case) for the quantity so you don't have to pass it explicitly to the function most of the time. The technique of doing this is called default argument (even though we are really talking about the default value assigned for the argument).

In C++, default argument is nothing but a convenient way to let you call a function with omitted arguments. If you want, you can always define multiple functions with same name but different signatures because C++ supports function overloading. In Tcl, however, there is no function overloading, so if you write multiple functions with one name, the last function will overwrite all the previous ones. Therefore in Tcl, default argument is the only way to achieve the goal.

The following code demonstrates the use of the default argument in Tcl:

proc f {s1 {s2 "world"}} {
    puts "$s1 $s2"
}

This procedure (the terminologies in programming world is a little messy, function in C/C++, method in Java and procedure in Tcl are different names for the exact same concept) - named f - takes s1 and s2 as arguments and simply prints them out. The default value of the 2nd argument is set to the string "world". So f "hello" - with the 2nd argument missing - will output "hello world", but f "hello" "Tcl" - with both arguments passed - will output "hello Tcl".

A function can have more than one default arguments. The general rule is: put each pair of the argument and default value in a separate curly bracket.

2. Tk Option Database - Look & Feel Goes Global

Suppose you have an application that runs fine, but you want to change the background color of ALL the buttons to a new value, say yellow. How would you do that?

One way to do it is of course to perform the change for each and every button, which - depending on the scale of your application - could become quite tedious. Not only that it may become tedious, what if you want to try several configuration options before making a decision? Brute force is certainly not the best way to go. Tcl/Tk stores default values for widget configuration options in something called the option database and provides you with commands to modify those options. By modifying the option database for a widget type, one can change the look & feel for all the widgets in that type. For instance, to change the background color of all the buttons to yellow, one can use the following line:

option add *Button.background yellow startupFile

The option add is the command to modify the option database, the argument *Button.background means we want to modify the background option for all the buttons. Notice that the first character of the widget name - Button - is capitalized, which means the modification applies to the whole type of widget. A non-capitalized name, on the other hand, only applies to a specific widget with that specific name. For instance *button.background only applies to the background option of a widget named .button (which may not even be a button). The next argument - yellow - in the code specifies the value for that option. Finally, the argument startupFile assigns a priority to the option setting. Now if we create two buttons:

button .b1 -text "Button 1"
button .b2 -text "Button 2"
pack .b1 .b2

Both buttons will have yellow background. For any specific widget you can overwrite this option database value by specifying an explicit value (say a red background) for that widget (similar to the use of a local stylesheet to overwrite a document-level stylesheet in web design).

3. Images on Control Elements

There are several ways to put images on control elements such as buttons, labels, etc. We use buttons as an example to describe two most useful methods. Both methods can be easily applied to other control elements.

The image we use is a mailbox icon with a transparent background. Both methods we introduce will preserve the transparency.

The first method makes use of the image file directly, suppose the file is named icon_email.gif and placed in the same directory as the Tcl file itself, the following is the code to create a button with that image on it.

image create photo img -format gif -file "icon_email.gif"
button .b -image img
pack .b

Here the first line creates an image based on the file icon_email.gif, the name of the image is img. This name is then used in the second line together with the -image option for the button to draw the image on the button. If you run this code, a button will be created. If we add a background option -background yellow to the button, due to the transparency of the image background, the new background color (yellow) will surround the little mailbox seamlessly: .

Direct use of external image files is not always convenient. Especially if the application is to run across the network, you may not want to transfer image files separately. In this case we can use the so-called Base64 encoding to convert images into character strings and use those strings instead of the external images. I will not explain Base64 encoding here, do a search on the web and you will find many introductory materials as well as free utilities to do the encoding. The following code creates the same button as in the previous example, but makes use of the Base64 encoded image:

set imgdata "
    R0lGODlhIwAjAKL/AP////8AAL+/v4CAgICAAAAAAMDAwAAAACH5BAEAAAYALA
    AAAAAjACMAQAPkaLrc/nAVASgFONcq+hhFESmTEAalAIbFYLpuAaxjUxB4uL5h
    7ZOt30hm4XhoQkapgGp1NkWTyhQU/UKdrNYJm1qvOIKul3T0juSyWaOxZD9pX+
    pZZGNeU1BkGWiyXEVcJUgQZ1pagFx5Jkk3YnN4MHFgOS2TakphY1+YnZ0sl54G
    LUZonJgybVCmakR2d6sqhENQtWxRWHp7HTpZthmKMbsmfX++FhMxcLpmvMUqoC
    4VydAzoaMi0JA7Ul1jctmHW4uSjVji1DDYjZrn3euomiAr1+w586eijmL4og/3
    lvwJbJAAADs=
"
image create photo img -data $imgdata
button .b -image img
pack .b

Here the weired magic string imgdata carries the Base64 encoded information for the mailbox image. The rest part of the code is very similar to the previous method, except that the image create photo now uses the data string $imgdata (together with the -data option) instead of the image file.

4. Behind the Curtain - Make Use of the Tk Internal Code

As your programming experience grows, chances are that you will gradually become "a person who enjoys exploring the details of programmable systems and how to stretch their capabilities" (According to «The Jargon Dictionary», such a person is called a hacker). In that case, you may want to go beyond what ordinary Tcl/Tk commands let you do. For instance you may want to create a menubutton, but instead of using the left button (Button-1) to bring up the menu, you may want to use the right button (Button-3). This apparently goes beyond the capability of an ordinary menubutton. We will use this as an example to demonstrate how to probe and use Tk internal code.

To change the default menu binding from Button-1 to Button-3, you need first find out the Tk internal code for the Button-1 binding. Open wish, enter the command bind Menubutton (notice the first character of widget type name must be capitalized). This gives you all the bindings available for a menubutton widget: <Key-F10> <Alt-Key> <Key-space> <ButtonRelease-1> <B1-Motion> <Motion> <Button-1> <Leave> <Enter>. Obviously <Button-1> and <ButtonRelease-1> are the relevant ones here. To find out the Tk internal code for <Button-1> binding, use the command bind Menubutton <Button-1>, the output is:

    if {[string compare $tkPriv(inMenubutton) ""]} {
        tkMbPost $tkPriv(inMenubutton) %X %Y
    }

Similarly the command bind Menubutton <ButtonRelease-1> gives the internal code for <ButtonRelease-1> binding:

    tkMbButtonUp %W

Now we paste the code to the Button-3 bindings. The following code creates a "File" menubutton with a single dummy menu entry "New". The menu can only be brought up using the right button (you can keep the Button-1 binding if you don't disable it).

menubutton .mb -text "File" -menu .mb.m
menu .mb.m
.mb.m add command -label "New"
# disable the default Button-1 binding
bind .mb <Button-1> { break }
bind .mb <ButtonRelease-1> { break }
# re-produce internal Button-1 binding on Button-3
bind .mb <Button-3> {
    if {[string compare $tkPriv(inMenubutton) ""]} {
        tkMbPost $tkPriv(inMenubutton) %X %Y
    }
}
bind .mb <ButtonRelease-3> {
    tkMbButtonUp %W
}
pack .mb

We can even simplify those Tk internal code for our example. Look at the <Button-1> binding, the if-statement basically checks whether the menubutton is under the mouse cursor. Since we are talking about binding on the menubutton, the whole binding code will not be invoked at all if the mouse cursor is not on the menubutton. Therefore we can remove the if-statement. Next we use the command info args tkMbPost to query the signature of the tkMbPost procedure, the result is w x y which means it takes the path name of the event widget and the coordinates of the event as arguments. Similar to the use of %X and %Y to substitute the x and y coordinates of the event, we can use %W to substitute the path name of the event widget. So the Button-3 binding can be simplied as:

bind .mb <Button-3> {
    tkMbPost %W %X %Y
}

Finally, for those who want to probe even deeper into the internal code, the command info body tkMbPost can be used to query the full content of the tkMbPost procedure. Other procedures can be queried similarly.

The regular comment period of this article is over
Please come back in the first 7 days of any month to leave comment