Understanding TkTreeCtrl

Table of Contents

Loading the treectrl package

package require treectrl

Creating a new treectrl window

set T [treectrl  .myTreeCtrl ?option value ...?]

Adding columns

set columnID [$T column create ?option value ...?]

The [$T column create] command not only creates a new column but also adds that column to the list of columns maintained by the treectrl window. The new column appears at the end of the list of columns.

Note  There is a special column called the tail column that is created when the treectrl window is created. The tail column always appears to the right of any other columns created by the [$T column create] command. It is not possible to delete the tail column.

Adding items

set itemID [$T item create ?option value ...?]

Note  The root item is created when a treectrl window is created. It is not possible to delete the root item.

Modifying the hierarchy of items

There are 4 commands which set the parent-child relationship between one item and another. You only need to use one of these commands to add an item to another item's list of children.

$T item firstchild $parent $child
$T item lastchild $parent $child
$T item nextsibling $item $sibling
$T item prevsibling $item $sibling

Note  An item can only be a child of a single parent. Also, you cannot make the root item a child of another item.

There is a 5th command which removes an item from its parent's list of children.

$T item remove $item

Note  An item without a parent is known as an orphan and is never displayed in the treectrl window. You can get a list of orphan items using the command [$T orphans].

Specifying the appearance of items

Up to this point you have learned how to create a new treectrl window, add columns, add items, and set the parent-child relationship between those items. But what do those items look like on screen? How are the images, text, fonts, colors etc specified for each item? In fact there is no default appearance for items provided by a treectrl window. The good news is you have a great deal of control over the appearance of items so you do not have to make due with what the treectrl developer thought was best. The bad news is you have to do a bit more work (i.e., typing) than you might like.

Lets start with a basic question: what types of graphical information can be displayed by an item? The answer is there are 6 types of graphical things that can be displayed, namely bitmaps, 3D borders, images, rectangles, strings of text, and other Tk windows. There can be any number of these 6 graphical things displayed in each column of each item. Each graphical thing has its own set of options to control its appearance, such as the font used for strings of text, or the color used to fill a rectangle. The graphical things I have been telling you about are called elements.

$T element create myElement1 image ?option value...?
$T element create myElement2 text ?option value...?

So now you know there are bitmap elements, 3D border elements, image elements, rectangle elements, text elements and window elements. When you create a treectrl window you need to decide which of these elements to use to display your data. You also need to decide how to arrange these elements on screen. So how do you group elements together to arrange them on screen? The answer is you use a style. A style is a thing that maintains layout information for a list of elements.

$T style create myStyle1 ?option value...?

Note  There is no limit on the number of styles or elements that may be created in a treectrl window.

After you create a new style, you must specify a list of one or more elements that the style will arrange on screen.

$T style elements myStyle1 [list myElement1 myElement2]

Note  An element can be used only once by a particular style. However, the same element can be used by more than one style at a time. 

Your new style is no use on its own, you must now assign the style to the column of an item. One, and only one style may be specified for each column of each item. So as far as graphical appearance goes, you can think of an item as a list of styles, one style per column. If no style is specified for a column of an item, then nothing is displayed there (except for the background color).

$T item style set $itemID $columnID myStyle1

Now you may be asking yourself, do I need to create a brand new style for every column of every item? The answer is no. When you create a style, it is defined for the whole treectrl window. You can assign that style to the particular columns of whatever items you choose. The same style can be assigned to different columns of the same item, and to the columns of any number of different items.

If you understand what I've been telling you up to this point, you will now be wondering how it is that different items can have different text or images displayed if those items are sharing the same style. It's true that after you assign a particular style to more than one item those items will all display exactly the same information, as determined by the element options and layout information for those elements. What you need to do is configure some of the element options in a particular column of a particular item.

$T item element configure $itemID $columnID myElement2 ?option value...?

The [$T item element configure] command is how you would set the text for each item, for example. The whole point of styles is to avoid assigning colors, fonts etc for every single item, which would be slow and use a lot of memory. You could have an element that displays a folder image, and another element that displays a file image, and use those elements in many different items, thereby avoiding assigning the same image over and over again.

The style layout options

You know how to create elements and styles, and how to specify a list of elements for a style. I mentioned that "A style is a thing that maintains layout information for a list of elements." So after you specify a style's list of elements, you must then tell the style how to arrange its elements on screen.

$T style layout myStyle1 myElement1 ?option value...?

The [$T style layout] command is like a geometry manager for elements, similar to the Tk grid, pack and place commands. With the help of an example I will show you how some of the style layout options work.

package require treectrl
treectrl .t
pack .t

set column [.t column create -text hello -width 150 -itembackground linen]

.t element create elemGray rect -fill gray -width 20 -height 10
.t element create elemBlue rect -fill blue -width 20 -height 30
.t element create elemGreen rect -fill green -width 20 -height 20

.t style create style1
.t style elements style1 {elemGray elemBlue elemGreen}

.t item style set root $column style1

You can see that by default the elements are arranged from left to right in the same order given to the [.t style elements] command. Each element is given only as much space as it requests, in this case 20 pixels each. Also notice that the height of the root item is equal to the height of the tallest element (the blue box).

.t style layout style1 elemBlue -padx 4 -pady 2

An element has 0 or more pixels of padding on each side. The padding pushes the element away from other elements and from the edges of the item. Notice that the root item is now 4 pixels taller because of the -pady padding.

.t style layout style1 elemGreen -expand n

The -expand option tells the style to give extra space to the -padx and -pady options. I told the style to give all the extra vertical space to the north or top side of the green box. As as result the green box is pushed to the bottom of the root item.

.t style layout style1 elemGreen -expand ns

Now half of the extra space is given to the top of the green box and half to the bottom. As a result, the green box is centered vertically inside the item.

.t style layout style1 elemGray -iexpand y

This new option -iexpand tells the style to give extra space to the inside of the gray box instead of to the outside like -expand does.

.t style layout style1 elemGray -iexpand xy

Now the gray box has been given all the extra horizontal space of the root item.

.t style layout style1 elemGray -sticky ""

The -sticky option controls how an element is stretched and positioned within the space given to it. The gray box is still given extra "inside" space from the -iexpand option, but it isn't stretching to fill that space.

.t style layout style1 elemGray -sticky w

Now the gray box is sticking to the west or left side of its inside space.

.t style layout style1 elemGray -sticky ws

Now the gray box is also sticking to the south or bottom side of its inside space.

.t style layout style1 elemGray -sticky wnes

Now the default behavior is restored, and the gray box sticks to all sides of its inside space.

Note  Some elements like text and images are not stretched by the -sticky option.

.t style layout style1 elemGray -detach yes

The -detach option tells the style to place an element by itself, without affecting the position of any other elements. The gray box completely fills the root item because of the -iexpand and -sticky options. The blue and green boxes are now shifted to the left since the gray box is not taking up space on the left. The gray box appears behind the blue and green boxes because it is first in the list of elements. Elements are drawn from first to last in the style's list of elements.

.t style layout style1 elemGray -union elemGreen

What happened to the gray box? The answer is the gray box is now exactly the same size as the green box. The -union option lets you wrap an element around one or more other elements.

.t style layout style1 elemGray -ipadx 4 -ipady 4

You can see the gray box now because I added some padding to the inside of it.

.t style layout style1 elemGray -iexpand n

Now the style is giving extra vertical space to the inside top of the gray box. The position of the green box is unaffected by this extra padding.

.t style layout style1 elemGray -iexpand ns

The gray box is now stretched to the height of the root item.

How items are arranged on screen

We've looked at how a style arranges its elements. Now we are going to look at how a treectrl window arranges its items. The next example creates a treectrl window with 20 items. Each item has a text element surrounded by a border element.

package require treectrl
treectrl .t -showheader no -showroot no -width 250
pack .t

set columnID [.t column create]

.t element create elemBorder border -background #ece9d8 -filled yes -relief solid -thickness 1
.t element create elemText text

.t style create style1
.t style elements style1 {elemBorder elemText}
.t style layout style1 elemBorder -union elemText -ipadx 4 -ipady 4
.t style layout style1 elemText

for {set i 1} {$i <= 20} {incr i} {
    set itemID [.t item create]
    .t item style set $itemID $columnID style1
    .t item element configure $itemID $columnID elemText -text "Item $i"
    .t item lastchild root $itemID
}

Here you can see the default behavior, which is to arrange the 20 items from top to bottom.

.t configure -orient horizontal

Here we see the first option that controls item arrangement, the treectrl's -orient option. Now all the items are arranged from left to right instead of from top to bottom. If you are displaying a film strip of images it might be useful to arrange items this way.

.t configure -wrap "5 items"

I told the treectrl to put no more than 5 items in a row.

.t configure -wrap window

Instead of breaking each row at 5 items, the treectrl puts as many items in a row that fit into the window.

.t configure -width 200

Here I just made the window narrower to demonstrate wrapping.

.t configure -width 250 -wrap "200 pixels"

The window is back to its original width of 250 pixels. The -wrap option now says to break each row at 200 pixels, which is exactly how wide I made the treectrl in the previous example, so we end up with the same arrangement of items. You have now seen the 3 different possibilities for the -wrap option:
  1. -wrap "N items"
  2. -wrap window
  3. -wrap "N pixels"
.t configure -orient vertical -wrap window

Wrapping works just as well when items are arranged from top to bottom.

How the width of items is determined

When more than one column is visible, every item has the same width no matter what options you use. When only one column is visible, the amount of width given to an item depends on a number of factors. The next example creates a list of 20 items with only one column. Every item is drawn with a "linen" color background to show you how much width is actually given to each item. With this example I will show you how the width of items is affected by various options.

package require treectrl
treectrl .t -showheader no -showroot no -width 410 -height 300
pack .t -expand yes -fill both
set columnID [.t column create -itembackground linen]

.t element create elemBorder border -background #ece9d8 -filled yes -relief solid -thickness 1
.t element create elemText text

.t style create style1
.t style elements style1 {elemBorder elemText}
.t style layout style1 elemBorder -union {elemText} -ipadx 4 -ipady 4
.t style layout style1 elemText

foreach n {5 15 20 10 10 5 15 10 20 15 15 25 10 5 15 5 10 20 15 25}  {
    set itemID [.t item create]
    .t item style set $itemID $columnID style1
    set text [string range "abcdefghijklmnopqrstuvwxyz" 0 $n]
    .t item element configure $itemID $columnID elemText -text $text
    .t item lastchild root $itemID
}

With top-to-bottom layout, and only one column, and no wrapping, every item is exactly as wide as the column. The column's width is affected by the requested width of the items, the requested width of the header (i.e. the column's bitmap/image/text/arrow), and the column's -minwidth, -width, -maxwidth, -expand and -squeeze options.

.t column configure $columnID -width 200

When the column has a fixed width, the width given to every item is equal to that width. In this case the requested width of the items, the requested width of the header, and the other column options have no effect on item width.

.t configure -wrap "10 items"

Now there are 2 vertical groups of items. I call each group a range of items. The items are arranged from top to bottom within each range. Both ranges have the same width of 200 pixels.

.t column configure $columnID -width ""

I cleared the fixed column width. Both ranges have a different width from the other. Now each range is exactly as wide as the widest item in that range.

.t configure -itemwidthequal yes

The -itemwidthequal option causes all items to have the same width. The width of every item is equal to the width of the widest item. The left range is now exactly as wide as the right range, which was the widest.

.t configure -itemwidthequal no -orient horizontal -wrap window

Laying out items left-to-right to demonstrate the next option.

.t configure -itemwidthmultiple 100

The -itemwidthmultiple option gives every item an even multiple of 100 pixels. The narrower items are 100 pixels wide, while the widest are 200.

.t configure -itemwidthmultiple "" -itemwidth 100


Here the -itemwidth option gives every item a width of 100 pixels.

The two tables below summarize how item width is determined. The order of precedence is from top to bottom within each table.

Situation (-orient vertical) How an item's width is determined Items have equal width?
More than one column is visible sum of the calculated widths of all visible columns yes
-wrap == "" column's calculated width yes
-itemwidth > 0 -itemwidth option yes
column -width != "" column's -width option
Using the column's -width option this way is deprecated. Use the treectrl's -itemwidth option instead.
yes
-itemwidthequal == true width of widest item
round up using -itemwidthmultiple
yes
all other cases requested width of style, plus indentation if this is the tree column
round up using -itemwidthmultiple
All items in a range have the same width, but each range may have a different width.

Situation (-orient horizontal) How an item's width is determined Items have equal width?
More than one column is visible sum of the calculated widths of all visible columns yes
-itemwidth > 0 -itemwidth option yes
column -width != "" column's -width option
Using the column's -width option this way is deprecated. Use the treectrl's -itemwidth option instead.
yes
-itemwidthequal == true width of widest item
round up using -itemwidthmultiple
yes
all other cases requested width of style, plus indentation if this is the tree column
round up using -itemwidthmultiple
no

Adding items on demand

If your items have a parent-child relationship (as opposed to a flat list where every item is a child of the root item), and if you need to create many thousands of items, it will be faster to add the items only when the user attempts to display them. This can be done by using the <Expand-before> event which is generated before an item is expanded.

package require treectrl
treectrl .t -width 400 -height 300 -showrootbutton yes
pack .t -expand yes -fill both
set columnID [.t column create -text "Column 0"]
.t configure -treecolumn $columnID

.t element create elemBorder border -background #ece9d8 -filled yes -relief solid -thickness 1
.t element create elemText text

.t style create style1
.t style elements style1 {elemBorder elemText}
.t style layout style1 elemBorder -union {elemText} -ipadx 4 -ipady 4
.t style layout style1 elemText

.t item configure root -button yes
.t item style set root $columnID style1
.t item element configure root $columnID elemText -text "The root item"

for {set i 1} {$i <= 100} {incr i} {
    set itemID [.t item create -button yes]
    .t item collapse $itemID
    .t item style set $itemID $columnID style1
    .t item element configure $itemID $columnID elemText -text "Item $itemID"
    .t item lastchild root $itemID
}

This example creates a treectrl and adds 100 child items to the root item. Every child has a button to indicate that it has children and may be expanded. However, none of the items created actually have child items yet. They will only be added when the user expands an item (by clicking on the item's button, for example). The next bit of code shows how that is done.

.t notify bind .t <Expand-before> {
    AddChildItems %T %I
}

proc AddChildItems {tree parent} {

    if {[$tree item numchildren $parent] > 0} return

    set columnID first

    for {set i 1} {$i <= 100} {incr i} {
        set itemID [$tree item create -button yes]
        $tree item collapse $itemID
        $tree item style set $itemID $columnID style1
        $tree item element configure $itemID $columnID elemText -text "Item $itemID"
        $tree item lastchild $parent $itemID
    }

    return
}

After running the new bit of code item #4 is expanded by clicking its button.


Now item #106 is expanded. In this example you can open items forever to reveal new items.