TIP 18: Add Labels to Frames

Login
Bounty program for improvements to Tcl and certain Tcl packages.
Tcl 2017 Conference, Houston/TX, US, Oct 16-20
Send your abstracts to tclconference@googlegroups.com
by Aug 21.
Author:         Peter Spjuth <peter.spjuth@space.se>
State:          Final
Type:           Project
Vote:           Done
Created:        12-Dec-2000
Post-History:   
Tcl-Version:    8.4

Abstract

This TIP proposes to add a labelled frame widget to Tk.

Introduction

Labelled frames are a common thing in a GUI and the need for them are rather clear by the fact that practically every widget package implements some version of it.

This proposal wants to add simple labelled frames to Tk. Even though a labelled frame can be built by three frames and label, this requires some skill and a bit work. I believe such a basic thing should be easier and this change would make creating a labelled frame as simple as it deserves to be.

Below is an example of what I mean with a labelled frame.

Example of labelled frame

Specification

A new widget class, labelframe, is added. It works like a frame, with the following changes.

These options are added:

-text: Standard option. Default value "".

-font: Standard option. Default value same as Label widget.

-fg: Standard option. Default value same as Label widget.

-labelwidget: Specify a widget to use as label. Default value "". This option overrides any -text, -font and -fg setting. The widget used must exist before using it as -labelwidget, and if it is not a descendant of the frame it is automatically raised in the stacking order to be visible.

-labelanchor: Sets where to place the label. Takes the values nw, n, ne, en, e, es, se, s, sw, ws, w and wn, listing them clockwise. Default value "nw".

-padx, -pady: Standard options. Adds some "air" between the border and the interior of the frame. Default value 0.

These options changes default values:

-borderwidth, new default value 2. -relief, new default value groove.

-padx and -pady are useful in frames and toplevels too, and since it is easy and cheap to add them at the same time, this TIP proposes to add them there too.

Rationale

My main approach has been to make a simple but still general solution. The most typical usage should be really easy, more advanced usage possible, and more features should be possible to add later if needed.

Trying to mimic all the abilities of a label widget is rather futile. It leads to code duplication and future updates to the label widget would need to be copied too to keep up. Since the most common label is a simple text, the labelframe only mimics options -text, -font and -fg to be able to handle that case in a simple manner. If you want a more advanced label, e.g. with an image or with a checkbutton, you can get it with -labelwidget.

For placement of the label I chose a style I found in IWidget's "labeledframe" widget. It's the most general solution I can see since it allows access to all twelve obvious positions in an easy way.

Options -padx and -pady does not have anything directly to do with labels, but are a generally nice addition to frames that I have missed a lot in the past. Such padding is not possible without part of the changes to geometry management (see Implementing section) that are required for displaying the label.

The thing about raising the -labelwidget in the stacking order comes from this:

With the most simple implementation, using -labelwidget could be done in two ways:

# Way #1
labelframe .f
label .f.l -text Mupp
.f configure -labelwidget .f.l

# Way #2
label .l -text Mupp
labelframe .f -labelwidget .l
raise .l .f

In the first you want the label to be a child but since it has to exist, the -labelwidget can't be used on the labelframe creation line.

In the second you try to circumvent it by creating the label first, but then you have to raise it above the labelframe to be visible.

Even though it's just one extra line of code I find it a bit awkward when it's so easy to do something about. The first can be fixed by not trying to do anything with the label widget until idle time when it has had a chance to be created. This is not a good solution though since it leads to some rather awkward things in implementation. The second can be fixed by automatically raising the label in the stacking order when used as -labelwidget. If this is documented clearly I don't have a problem with it, and that is why I chose it.

Alternatives to this TIP

An alternative way to implement a labelled frame is using mega widget style with a subframe where children are placed. This is how current widget packages do it. I think that is an awkward and unnatural way to handle such a simple thing as a labelled frame. The only reason to do so is that current limitations in geometry management prevents a simpler solution.

I believe that a labelled frame should work like a normal frame. That it displays a label should not matter more than displaying a border or a blue background. A labelled frame megawidget would be different from a frame, the most noticeable difference being that you can't pack/grid things directly into the labelled frame, instead you have to go via a subframe. Having the labelled frame work like a normal frame is more consistent and easier for the programmer at Tcl level.

Implementing

Implementing this is mostly rather straightforward. The labelframe will share most code with the frame, just like toplevel and frame share code today, and like the spinbox was built on the entry. The tricky part is that limitations in geometry management does not leave room for displaying a label. The changes needed in geometry management are simple but introduces a slight backward incompatibility.

The problem is this. Today a widget can set an internal border width. This defines a uniform width area around the edge of the widget that geometry managers should stay away from. This is not enough though, since to display a label the frame needs to get more space on one side where it will put the label. Also, there is no way for a widget to affect its own size (anything it says is overridden by pack/grid), so the labelframe cannot make sure that enough size is requested to make room for the label.

By adding some more fields to the TkWindow structure, the information needed can be transferred to the geometry manager.

First, the present internalBorderWidth field is split into four fields, one for each side.

Second, minimum requested width/height fields are added.

This requires one macro per field for reading them and two new APIs to set the fields:

void Tk_SetInternalBorderWidthEx(tkwin, left, right, top, bottom)
void Tk_SetMinimumRequestedSize(tkwin, minWidth, minHeight)

Geometry managers would need to be updated to take the new fields into consideration, and here is where backwards compatibility comes in. Any extension implementing a geometry manager would need to be updated in the same way as grid/pack/place will be. The change is trivial, and even if not done most things will work anyway. An updated Tk plus an old extension plus an old script will still work and thus no one needs to worry about upgrading.

I consider this a minor thing since it wont break any existing applications. The only thing that will break is if someone would try to use a geometry manager that is not updated within a labelframe. And even in that case you can work around it with an extra frame.

Rejected alternatives

The ability to display a label could have been given to the normal frame by adding the options above to it. Having a new widget class has the following advantages:

The separate widget class can have its own default values, and the user can control it separately from the frame in the option database.

The normal frame does not need to store all the new options, thus saving memory.

For handling of geometry management, some other solutions was regarded.

Instead of splitting the internalBorderWidth in four, an alternative is just adding two fields. One pointing at a side and one telling how much extra border to put on that side. This only saves one field and is less general. For example, it is not possible to implement -padx and -pady with this one.

A more complex solution using callbacks which was featured in revision 1.1 of this TIP has also been discarded because it was too complex.

It would be possible to do without the minimum requested size fields if you give the responsibility to make sure the label has room to the GUI programmer. This could be rather awkward though, e.g. when making an internationalized application where labels can vary a lot.

Reference Implementation

An almost finished implementation exists, and it's just a matter of polishing the last bits to create a patch for this proposal if it is accepted.

At http://www.dtek.chalmers.se/~d1peter/labframe.tcl you can find a pure Tcl demo of labelled frames. Even though it uses sub-frames and thus do not live up to what I want to accomplish here it implements all new options as specified here and can be played with if you want to know more.

Copyright

This document has been placed in the public domain.

History