SMARTPACK   

Tools to make packing Tcl widgets a little easier.

by James Davis (c)2000

Download - smartpack.tcl

 

One of the chief problems with designing interfaces for TCL is using the packer. Its fairly convenient to define widgets programmatically. However to arrange them, we need to use frames to lay things out. If you know the exact arrangement beforehand then this is easy. However, when we want to make changes, an extra layer of heirarchy is often required, and because of the way widgets are named relative to their frame, we need to go back and rename all the widgets. This is a real hassle. Smartpack defines some routines that make this a little less painful, and code a little easier to read.

For example, suppose we want to group some widgets:

frame .frame
button .frame.b1 -text b1
button .frame.b2 -text b2
button .frame.b3 -text b3
pack .frame.b1 .frame.b2 .frame.b3
pack .frame

If we decide that we want to insert another layer, we have

frame .frame
button .frame.b1 -text b1
frame .frame.newlevel                    
button .frame.newlevel.b2 -text b2
button .frame.newlevel.b2 -text b3
pack .frame.newlevel.b2 .frame.newlevel.b3
pack .frame.b1 .frame.newlevel
pack .frame

Note that we added two new lines, and changed three of the original six lines. All this for a single conceptual change.

Part of the problem is that when we define widgets we have to explicitly give the widget path, rather than just saying 'Please give me a new widget in the current frame.' If we could define widgets relative to some current state, then when we changed the heirarchy, the widgets would just appear in new current level when they are defined, without actually changing the defining code.

A second problem, is that we have to explicitly pack all the widgets we built. Often we'd like to just 'pack all widgets'

So, we can define a couple new constructs which begin and end levels of the heirarchy, and then declare widgets relative to these.

startframe .frame
button $smartpack.b1 -text b1
button $smartpack.b2 -text b2
button $smartpack.b3 -text b3
packframe
pack .frame

Lets look at whats new here. We use 'startframe' rather than 'frame' to declare a new frame. This sets up a current context. Then we can use $smartpack to reference this notion of current context. When we finish with the frame we use 'packframe' to pack all the widgets currently defined in the frame. Note that we skip explicitly naming the widgets.

Lets look at what we have to do now to add a level of heirarchy.

startframe .frame
button $smartpack.b1 -text b1
startframe newlevel
button $smartpack.b2 -text b2
button $smartpack.b3 -text b3
packframe
packframe
pack .frame

In this case we only had to add two lines, but didn't have to change any of the existing lines of code. We added one line to create a new current context, and one line to pack things in this context. Also note that since this is a sub-frame we only specified a relative frame name 'newlevel', rather than a complete path '.frame.newlevel'.

In some cases we might want the code to more clearly indicate the frame heirarchy, so we can use one more new construct to combine the startframe and packframe commands, like this:

smartframe .frame {
    button $smartpack.b1 -text b1
    button $smartpack.b2 -text b2
    button $smartpack.b3 -text b3
}
pack .frame

Note that we changed from 'startframe' to 'smartframe', and the packframe at the end became implicit. After changing we have:

smartframe .frame {
    button $smartpack.b1 -text b1
    smartframe newlevel {
	button $smartpack.b2 -text b2
	button $smartpack.b3 -text b3
    }
}
pack .frame

We've added one new command, and a level of {} braces in order to change the heirarchy. In addition if we use an editor that auto-indents then the logical widget heirarchy will be reflected in the code  indentation naturally. This can improve the readability of the code quite a bit on complex arrangements.

Here is a more complete sytax example:

smartframe ".frame -relief groove" {
    button $smartpack.b1 -text b1
    smartframe "newlevel -bd 2" {
	smartframe moreLevels {
	    button $smartpack.b2 -text b2
	    button $smartpack.b3 -text b3
	}
    } -anchor w
} -side left
startframe .frame2 
    button $smartpack.b1 -text "A different b1"
packframe
pack .frame .frame2

Finally, here is a reference:

proc startframe {fname args}
#     We define a new frame. If fname starts with a dot (.), then
#     it is an absolute path, otherwise fname defines a relative name
#     for the new frame under the previous current context. Often a top
#     level frame will be absolute, and all the subframes will be relative.
#     The parameter args, are options that pass through to the 'frame'
#     command. In addition we set the global variable $smartpack to
#     be the name of the current context. This variable is declared
#     in the correct scope so that it can just be used. Also the local
#     variable $sp is set as a convenient alias to $smartpack.

proc packframe {args}
#     End the current frame context. Pack all child widgets of the
#     current context, using 'args' as options to the 'pack' command.
#     In addition, pop one level from the global variable $smartpack.
#     Note that $smartpack is not set to what it was _before_ the startframe
#     command, it just pops one level up, so that .frame.newlevel.moreLevels
#     becomes .frame.newlevel after the packframe command.

proc smartframe {fname body args}
#     Define a frame context. fname is the name of the frame using the
#     same absolute and relative rules as for startframe. 'body' defines
#     the set of commands that should run in this current frame context.
#     And 'args' pass through to the final 'pack' commands. Note that
#     options intended for the initial 'frame' command can be included
#     in the fname parameter by using quotation marks. As in
#     smartframe ".frame -bd 2" {}