TkLua: Basic Concepts

We have mapped all Tk widgets to Lua. One can create a widget describing its attributes, using Lua table constructors. The general form to create any Tk widget is:

variable = widget_name{attribute1 = value1,
                       attribute2 = value2,
                       ...
                       attributeN = valueN
                      }

where widget_name corresponds to the Tk widget name preceeded by the letters tk, and attributei corresponds to the Tk widget attribute name. Thus, we can create a hello message using a label with green foreground writing:

hello = tklabel{text = "Hello world!",
                foreground = "green"
               }

To simplify the instantiation code, for each widget, we have chosen a main attribute that can be specified without writing its name. To use this simplified format, we list the main attribute value first finishing it with a semi-colon, and then list the other attributes in the conventional way:

variable = widget_name{value1;
                       attribute2 = value2,
                       ...
                       attributeN = valueN
                      }

You may be asking yourself: what is it for? Well, it is just a shortcut. You will soon find out that it is very useful. For instance, we can create a single label writing:

hello = label{"Hello world!"}

since text is considered to be the main attribute of a label (a complete list relating each widget with its main attribute is shown bellow).

Manipulating instantiated widgets

The last statement above creates a label and stores it in the variable hello, i.e., hello is now an object that represents the label. After such a definition, we can use straight Lua semantics to manipulate the object. Hence, the assignment:

hello.text = "Welcome to Lua world!"

changes the label's text, and updates its image if it is already displayed on the screen.

Accordingly, we can define an edition field widget to capture the user name with:

name = entry{width = 20}

If this widget is mapped on a displayed window, the statement:

print(name.current)

prints the current value assigned by the user to the widget. Note that we use the current attribute to store the widget value, instead of using global variables like Tcl/Tk does. Thus, all widgets that have an associated value will accept the attribute current.

Mapping widgets on the screen

Widgets are not automatically mapped on a window. Unlike the Tcl/Tk environment, there isn't the concept of current window in TkLua. You must create a window, which can be the main window or a toplevel widget, to hold the other widgets, and then explicitly map the window onto the screen. In this way, the users can freely describe their dialogs, even cross-referencing widgets, mapping them when necessary.

When creating the main window or a toplevel widget (it also applies to frame, see bellow), we specify the attribute objects listing the objects to be placed inside the window.

A complete "Hello world!'' program might be:

hello = tklabel{"Hello world!"}
message = tkmain{objects={hello}}
message:show()

But as objects is considered to be the main attribute of main and toplevel widgets, we can rewrite the code above as:

hello = tklabel{"Hello world!"}
message = tkmain{hello}
message:show()

Writing callbacks

Many widgets are active objects, i.e., they execute a command to respond users' actions. For instance, we can create a label and then a button that would change the label text under user's action. The command callback is written as a method, named command, of the object that holds the created button:

hello = tklabel{text="Hello world!"}
change = tkbutton{text="To Lua"}

function change:command (arg)
 hello.text = "Welcome to Lua world!"
end

message = tkmain{hello,tolua}
message:show()

All callback functions have a single table as their argument. Each individual argument passed by Tk is accessed indexing that table: arg[i] access the i-th argument passed to the callback. Also, arg.n reports the numbers or parameters being passed. In addition to that, as callbacks are methods, the self variable is always available to access the object instance itself.

The need of writing a method for each active object has two weak points: first, in general, the action is so short that it could be written in a more simplified manner; second, and more important, the user could not store active objects in local variables, because we cannot define methods to local variable using the format showed above.

For that reason, TkLua also accepts the command being given by a string. If the command is a string, TkLua assumes it to be the body of the method. Thus, the code above can be rewritten:

hello = tklabel{"Hello world!"}
tolua = tkbutton{"To Lua"; command = 'hello.text = "Welcome to Lua world!"'}

message = tkmain{hello,tolua}
message:show()

Note that Lua provides different ways to enclose a string ('...', "...", or [[...]]). So we can choose one of them to avoid conflict. It is important to know that the self and the arg table remain available, even inside a string command.

A more sophisticated button callback could be:

function change:command ()
 hello.text = "Welcome to Lua world!"  -- change label text
 self.text = "Exit"                    -- change button label
 self.fg = "red"                       -- change button foreground color
 self.command = "tkexit()"             -- change button command method!
end

Widget commands

Besides widget attributes, Tk defines a set of widget commands for each widget class. All Tk widget commands are implemented as class methods in TkLua. Their parameters and functionality were preserved. So, if lb represents a listbox widget, then lb:insert("end","New item") inserts a new item to the end of the list, since there is a Tk insert command for listboxes.

Instead of passing several arguments to the widgets commands, we can pass a single table that contains the individual values. So that, instead of writing lb:insert("end","New item"), we could write:

t = {"end","New item"}
lb:insert(t)

with the same effect. This is specially useful for writing callbacks that pass their arguments to other methods.

As a simple example of using widget commands, we could change our hello program above requesting the button to be destroyed after it has performed its action:

function tolua:command ()
 hello.text = "Welcome to Lua world!"
 self:destroy()
end

Notice, however, that the most used Tk widget command, configure, is no longer needed, because its effect is now obtained with simple assignments. The main and toplevel widgets, that represent outermost containers for panels and dialog boxes, inherit the methods from the window manager. So, if w represents such a window, then w:iconify() has its natural effect.

Geometry manager

Tcl/Tk places widgets into another widget explicitly calling a geometry manager (pack, grid, or place). As we think it is more natural to describe a layout in a descriptive manner, we have eliminated the need of placing widgets in a procedural manner. Thus, the window (main and toplevel) and the frame widgets are used as containers that automatically place their contents inside themselves.

Thus, to horizontally place the label and button of the hello program above, we simply do (assuming the pack geometry manager):

message = tkmain{hello, tolua; side = "left"}

i.e., besides the objects attribute (implicitly set in the example above), main, toplevel, and frame accept as attribute any geometry manager attribute. The widgets are placed accordingly to the current active geometry manager. As default, it is assumed the pack geometry manager. One can change the current geometry manager by invoking the utility function tksetgeoman specifying the new geometry manager name. For instance, tksetgeoman("grid") would change the current geometry manager to grid. The geometry manager is invoked when showing the window. So, be sure the desired geometry manager is active when calling the show method. If one need to mix differents geometry manager in one dialog, it is possible to overwrite the current geometry manager by assigning a local geoman attribute per container (main, toplevel, or frame).

Sometimes, we also need to specify different geometry manager attributes to the widgets in a container. In that case, we can intercalate the list of widgets with tables specifying such attributes. This is specially useful when using the grid geometry manager. As an example we can write:

panel = tkframe{widget1, widget2,{colunmspan = 2},
                widget3, widget4, widget5, widget6;
                geoman = "grid  -- overwrite current active geometry manager
               }

Each table in the objects list specifies attributes applied to the widgets that preceede it (each table also implies in a new row when using grid geometry manager).

The relative placement facility provided by Tk is also available. The code above could be written as:

panel = tkframe{widget1, "-", widget2, "-", "/",
                widget3, widget4, widget5, widget6;
                geoman = "grid" 
               }

Note that we extended the Tk relative placement to include "/", meaning a new row (having the same effect provided by an empty table).

As another shortcut, any string different from those used for relative placement can also be listed as an object. In that case, they are automatically converted to label widgets. Then, the simplest hello program is:

hello = tkmain{"Hello world!"}
hello:show()

where the "Hello world!" is converted to a label widget, because it is listed as an object of the main window. If we need to access the label widget created, we can only do that through the container widget. In the example showed above, if we wanted to change the "Hello world!" foreground color, it would be done by:

hello.objects[1].foreground = "green"

since the "Hello world!" is the first object listed.

Main attributes

As previously mentioned, we have chosen a main attribute for each widget. The main attribute can be specified, followed by a semi-colon, without writing their names. The chosen main attribute for each widget is:


Topics


Last update: November 1998 by W. Celes.