站内搜索: 请输入搜索关键词
当前页面: 图书首页 > SWT: The Standard Widget Toolkit

9.1 Classes Tree and TreeItem - SWT: The Standard Widget Toolkit

Previous Section  < Day Day Up >  Next Section

9.1 Classes Tree and TreeItem

9.1.1 Example

graphics/09inf01.gif

9.1.2 Tree and TreeItem Hierarchies

graphics/09inf02.gif

9.1.3 Tree Styles

Style

Description

SWT.SINGLE

Create a single-select tree

SWT.MULTI

Create a multiselect tree

SWT.CHECK

Create a tree with check boxes


9.1.4 Tree Events

Event

Description

SWT.Selection

A tree item was selected

SWT.DefaultSelection

A tree item was default selected

SWT.Expand

A tree item was expanded

SWT.Collapse

A tree item was collapsed


9.1.5 TreeItem Styles (none)

Style

Description


9.1.6 TreeItem Events (none)

Event

Description


Trees are selectable controls that are capable of displaying hierarchies of nodes. Each node in a tree is represented by an instance of the class TreeItem. A tree item can be expanded or collapsed, showing or hiding other tree items. When an item is collapsed, a small "plus" indicator is displayed. An expanded item shows a "minus" indicator. These indicators are platform-specific and may not resemble plus or minus characters.[1] Tree items can draw a string, an icon, or both at the same time.

[1] They are often triangles on GTK and the Macintosh but can change with the operating system theme.

Users are familiar with tree controls because they are often used to implement the desktop file system browser. File systems are hierarchical. This makes Tree the ideal choice for a visual representation of a file system. However, it is prohibitively time-consuming to construct a tree item for every directory in a file system. When there are potentially too many items in a tree, programs often create the tree items on demand. The best time to do this is when items are expanded. If the user does not expand an item, child items are not created (see the section Using SWT.Expand to Fill a Tree Lazily).

Like lists and tables, when a tree is created with the SWT.MULTI style, users can select more than one tree item at the same time. Unlike List and Table, Tree is not indexed-based. For example, you cannot query the fifth row in a tree because the fifth row can change when the fourth row is expanded.

Tree items by themselves do not support any styles or listeners. Instead, listeners and styles are associated with the tree.

Why Doesn't TreeItem Support SWT.Selection?

Sometimes it's hard to remember exactly how responsibilities are divided among the various classes in SWT, particularly when item classes are involved. For example, ToolItem and Button both support SWT.Selection. Why doesn't TreeItem? Although it might be easy enough to define rules such as "always put the event on the item," dogma can get in the way of pragmatics. For example, it makes sense for ToolItem to support the SWT.Selection event because tool items behave like buttons. However, it does not make sense for TreeItem to support SWT.Selection because if this were the case, to listen for selection in a tree, programs would need to add a selection listener to every instance of TreeItem. Selection is really better served as a property of the tree, despite the fact that the user actually selects a tree item with the mouse.

In SWT, we try to organize class responsibilities pragmatically, rather than just mechanically following a pattern.


Sometimes a tree is used to represent a hierarchy of boolean options. These kinds of trees, called check box trees, are created with the style SWT.CHECK.[2] Check box trees draw check boxes next to each item. These check boxes often resemble check buttons.

[2] Although it would have been more consistent to support the SWT.CHECK for TreeItem rather than Tree, Windows forced the issue. Check box behavior on Windows is a property of the tree, specified when the tree is created.

Trees Always Have scroll bars

Due to a Microsoft Windows limitation, trees on every platform will always have scroll bars,[3] even if you do not create them with SWT.H_SCROLL or SWT.V _SCROLL.


[3] In early versions of SWT, this rule was not enforced. Programs that were written on Windows relied on this behavior, failing on other platforms. As a result, we were forced to include this Windows limitation as part of SWT.

9.1.7 Creating a Hierarchy

Creating a hierarchy of tree items is straightforward in SWT. The constructors in the class TreeItem allow the parent of an item to be the tree or another tree item. When the parent of an item is a tree, the tree item is a root. Otherwise, it is an interior node in the hierarchy. For greater flexibility, the position parameter of the TreeItem constructor allows a tree item to be inserted at an index, relative to the other tree items in the items list of the parent.

The following program creates a tree that is three levels deep with four nodes on each level. The result is shown in Figure 9.1.






public static void main(String[] args) {

    Display display = new Display();

    Shell shell = new Shell(display);

    Tree tree = new Tree(shell, SWT.BORDER);

    for (int i = 0; i < 4; i++) {

        TreeItem itemI = new TreeItem(tree, SWT.NULL);

        itemI.setText("Item " + i);

        for (int j = 0; j < 4; j++) {

            TreeItem itemJ = new TreeItem(itemI, SWT.NULL);

            itemJ.setText("Item " + i + " " + j);

            for (int k = 0; k < 4; k++) {

                TreeItem itemK =

                    new TreeItem(itemJ, SWT.NULL);

                itemK.setText(

                    "Item " + i + " " + j + " " + k);

            }

        }

    }

    tree.setSize(200, 200);

    shell.pack();

    shell.open();

    while (!shell.isDisposed()) {

        if (!display.readAndDispatch()) display.sleep();

    }

    display.dispose();

}


Figure 9.1.

graphics/09fig01.gif


9.1.8 Text and Images

TreeItem supports the standard API to assign text and images to a widget. Like Button, TreeItem does not support mnemonics, wrapping, the SWT.WRAP style, or more than one line of text in an item.

setText(String string) Sets the text for the tree item. The string cannot contain line delimiters or mnemonic characters.

getText() Returns the tree item text. This is an empty string if the text has never been set.

setImage(Image image) Sets the image for the tree item.

The First Image Defines the Size of All Images in the Control

Due to the same Windows limitation that is shared by ToolBar, TabFolder, and Table, Tree scales the images it displays to be the same size. By definition, this is the size of the first image that was inserted into the control.


getImage() Returns the TreeItem image. This is null if the image has never been set.

9.1.9 Expanding and Collapsing

At any particular time, a tree item that has children is in either one of two states: expanded or collapsed. A tree item that is expanded shows its children. When a tree item is collapsed, it does not show its children. Tree items that do not have any children are called leaf items and do not have an expanded or collapsed state.[4]

[4] Strictly speaking, a leaf item is not expanded because getExpanded() returns false for a leaf. However, if SWT had defined a getCollapsed() method, it would also return false.

To expand or collapse TreeItem, use setExpanded(). Calling setExpanded() on a leaf item does nothing.

setExpanded(boolean expanded) Sets the expanded state of the item. This method does nothing if the item is already expanded or is a leaf item.

getExpanded() Returns the expanded state of an item. Leaf items return false to indicate that they are not expanded.

When a tree item is either expanded or collapsed by the user, a corresponding SWT.Expand or SWT.Collapse event is issued. If the setExpanded() method is used to expand or collapse the item, an event is not issued.

On some platforms, such as Windows, when the tree item is collapsed, the selection can change as a result of the collapse operation. For example, if the selection is in an item belonging to the subtree that is about to be collapsed, the selection is assigned elsewhere in the tree (usually to the item that is being collapsed). In this case, an SWT.Selection event is issued, regardless of whether the user collapsed the tree item or the program collapsed it using setExpanded(false).

Why Can setExpanded() Cause SWT.Selection?

At first, this seems to break the rules. On the surface, you might think that the user, not the program, should be the source of SWT.Selection events. After all, calling setSelection() doesn't cause them. In this case, the difference is that when collapsing an item changes the selection, the program has no simple way to detect the change. It doesn't matter that the user didn't collapse the item; the selection was changed without calling setSelection(). This makes the change unexpected. To be consistent, an SWT.Selection event is issued when this happens. This means that a program that tracks the selection will not get out of step when calling setExpanded(). For a similar reason, when setSelection() expands tree items to show a new selection, SWT.Expand events are sent.


9.1.10 Hierarchical Operations

Tree and TreeItem provide a number of hierarchical operations that allow you to determine the parent朿hild relationship between items. The methods to do this are found in both the Tree and TreeItem classes. Because Tree is not an index-based control, these methods are not index-based.

getParentItem() Returns the parent item of the tree item. If the item is a root item, null is returned. This method is included in Tree for completeness only. It always returns null when called on a tree.

getItemCount() Returns the number of items in a tree or a tree item.

getItems() Returns an array of the child items in a tree or a tree item. This is a copy of the array that is used to hold the items so that modifying this array has no effect.

The following code fragment prints a text representation of a tree by visiting every node and printing it indented by its depth in the tree.






static void traverse(TreeItem[] items) {

    for (int i = 0; i < items.length; i++) {

        TreeItem item = items[i];

        String string = item.toString();

        while (item.getParentItem() != null) {

            string = "\t" + string;

            item = item.getParentItem();

        }

        System.out.println(string);

        traverse(items[i].getItems());

    }

}


This code is simple and was written to show the use of getParentItem() and getItems(). A better implementation would avoid the string concatenation and include a level parameter that was incremented with every recursive call. This would allow us to get rid of the loop that computes depth.

At first, this implementation of traverse() might seem a little strange. Good programming practice dictates that recursive traversal code should perform the operation on the item, then make the recursive call on each child of the item. However, our traverse() method takes an array of items. This implementation of traverse(), although unorthodox, is correct. Its implementation was forced by the fact that although Tree.getItems() and TreeItem.getItems() have the same method signature, there is no interface to capture the commonality. Without resorting to casting, it is not possible to write a version of the traverse() method that takes a single object.

Here is an alternate implementation of traverse() that uses two methods: one that takes a tree item and another that takes a tree. It does not cast but contains two identical loops.






static void traverse(TreeItem item) {

    String string = item.toString();

        TreeItem temp = item.getParentItem();

    while (temp != null) {

        string = "\t" + string;

        temp = temp.getParentItem();

    }

    System.out.println(string);

    TreeItem[] items = item.getItems();

    for (int i = 0; i < items.length; i++) {

        traverse(items[i]);

    }

}



static void traverse(Tree tree) {

    TreeItem[] items = tree.getItems();

    for (int i = 0; i < items.length; i++) {

        traverse(items[i]);

    }

}


As of R3.0, interfaces are not defined in SWT to capture commonality between classes. The rationale for this design decision is beyond the scope of this book.

9.1.11 Checked and Grayed Tree Items

Trees support a checked and grayed state for tree items. As was previously described, in order to check or gray the check box for a tree item, the tree must have been created with the SWT.CHECK style. Figure 9.2 shows a check box tree running on Windows.

Figure 9.2.

graphics/09fig02.gif


setChecked(boolean checked) Sets the checked state of the item. The checked state of an item usually takes the form of a small check box, located before the icon or text of the item. If the tree was not created with the SWT.CHECK style, nothing is checked.

getChecked() Returns the checked state of the item. If the tree was not created with the SWT.CHECK style, false is returned.

setGrayed(boolean grayed) Sets the grayed state of the item. When the grayed state is true, the check box portion of the item draws using a "grayed" look. Note that the item is not disabled and can still be selected by the user. If the tree was not created with the SWT.CHECK style, nothing is grayed.

getGrayed() Returns the grayed state of the item. If the tree was not created with the SWT.CHECK style, false is returned.

The checked state of a tree item is independent of the checked state of its parent or child items. This means that when an item is checked or unchecked, other items are unaffected. However, many users expect that when an item is checked or unchecked, its child items are also automatically checked or unchecked. Grayed tree items are often used to indicate that some of the items below a checked item are not checked. Because neither of these features is implemented by default, you will need to write the code to make this happen.

The following program maintains the checked state and grayed state of the tree items in a tree, as described above. An SWT.Selection listener is used to compute the two states when the user selects a check box. The result, with some items checked, is shown in Figure 9.3.






static void checkPath(

    TreeItem item,

    boolean checked,

    boolean grayed) {

    if (item == null) return;

    if (grayed) {

        checked = true;

    } else {

        int index = 0;

        TreeItem[] items = item.getItems();

        while (index < items.length) {

            TreeItem child = items[index];

            if (child.getGrayed()

                || checked != child.getChecked()) {

                checked = grayed = true;

                break;

            }

            index++;

        }

    }

    item.setChecked(checked);

    item.setGrayed(grayed);

    checkPath(item.getParentItem(), checked, grayed);

}



static void checkItems(TreeItem item, boolean checked) {

    item.setGrayed(false);

    item.setChecked(checked);

    TreeItem[] items = item.getItems();

    for (int i = 0; i < items.length; i++) {

        checkItems(items[i], checked);

    }

}



public static void main(String[] args) {

    Display display = new Display();

    Shell shell = new Shell(display);

    Tree tree = new Tree(shell, SWT.BORDER | SWT.CHECK);

    tree.addListener(SWT.Selection, new Listener() {

        public void handleEvent(Event event) {

            if (event.detail == SWT.CHECK) {

                TreeItem item = (TreeItem) event.item;

                boolean checked = item.getChecked();

                checkItems(item, checked);

                checkPath(

                    item.getParentItem(),

                    checked,

                    false);

            }

        }

    });

    for (int i = 0; i < 4; i++) {

        TreeItem itemI = new TreeItem(tree, SWT.NULL);

        itemI.setText("Item " + i);

        for (int j = 0; j < 4; j++) {

            TreeItem itemJ = new TreeItem(itemI, SWT.NULL);

            itemJ.setText("Item " + i + " " + j);

            for (int k = 0; k < 4; k++) {

                TreeItem itemK =

                    new TreeItem(itemJ, SWT.NULL);

                itemK.setText(

                    "Item " + i + " " + j + " " + k);

            }

        }

    }

    tree.setSize(200, 200);

    shell.pack();

    shell.open();

    while (!shell.isDisposed()) {

        if (!display.readAndDispatch()) display.sleep();

    }

    display.dispose();

}


Figure 9.3.

graphics/09fig03.gif


The checkItems() method recursively checks or unchecks the item and all child items. As you would expect, regardless of the new checked state, all items are ungrayed. This is done because items in the subtree are all either checked or unchecked.

The checkPath() method is used to ensure that when the checked state of an item changes, its ancestors in the tree are either checked, grayed, or unchecked to match the new state of their children. This method is initially called with the parent of the item whose state has changed, the new checked state, and the desired grayed state. While processing the ancestors of an item, if any child is either grayed or checked, the item must become both grayed and checked.

9.1.12 TreeItem Foreground, Background, and Font

TreeItem supports the ability to set the foreground color, background color, and font used by an individual item.

setForeground(Color color) Sets the foreground color of the item. If the color is null, the default foreground color for the item is restored.

getForeground() Returns the foreground color of the item. If the color has not been set, the default foreground color is returned.

setBackground(Color color) Sets the background color of the item. If the color is null, the default background color of the item is restored.

getBackground() Returns the background color of the item. If the color has not been set, the default background color is returned.

setFont(Font font) Sets the font that is used to draw the text in the item. If the font is null, the default font is restored.

getFont() Returns the font that is used to draw text in the item. If the font has not been set, the default font is returned.

9.1.13 The Selection

Application programs often need to work with the selection in a tree. Once again, because trees are not index-based controls, selection is specified using tree items. The following methods are used to manipulate the selection in a tree:

setSelection(TreeItem[ ] selection) Sets the selection to be the array of items. The previous selection is cleared, and the tree is scrolled and expanded so that the new selection becomes visible. If the tree is single-select and the array contains more than one item, the first item is selected.

getSelection() Returns an array of the selected items. This is a copy so that modifying the array has no effect on the control. When no items are selected, this array is empty.

getSelectionCount() Returns the number of items that are selected in the tree.

selectAll() Selects all the items in a tree. If the tree is single-select, no action is taken.

deselectAll() Deselects all items in the tree.

9.1.14 Scrolling

Tree controls provide the ability to scroll a tree by getting and setting the top item. The top item of a tree is the tree item that is displayed at the top of the control. The top item is set using the setTopItem() method.

setTopItem(TreeItem item) Scrolls to show the new item at the top of the tree control. Depending on the platform and the number of items in the tree, it may not be possible for the particular item to become the top item. This can happen when the platform does not allow the control to scroll so that white space follows that last item.

getTopItem(TreeItem item) Returns the item that is at the top of the tree. The top item can change when items are added, deleted, scrolled, expanded, or collapsed.

Generally speaking, application programs rarely need to scroll a tree to show the selection. This is because setSelection() automatically makes the selection visible to the user. However, there are circumstances where you might need to show the selection. In these cases, the showSelection() method can be used.

showSelection() Scrolls to show the selection. The selection is made visible somewhere inside the control. If the selection is already visible, nothing happens. Because the control may be scrolled, this method is used to bring the selection to the attention of the user.

showItem(TreeItem item) Scrolls to show the item. As with show-Selection(), the item is made visible but the exact location of the item in the tree is unspecified.

9.1.15 Removing Items

Tree items are removed using the TreeItem method dispose(). When an item is disposed, child items are also disposed. One way to remove all the tree items in a tree is to dispose of all the roots. This can be time-consuming and cause too many redraws, especially when there are many items in the tree. The recommended way to dispose all items in a tree is to use the Tree method removeAll().

removeAll() Disposes all the items in a tree and redraws once after all the items have been disposed.

9.1.16 Tree Events

SWT.Selection (SelectionEvent)

The SWT.Selection event (typed event SelectionEvent) is sent whenever the user selects an item with the mouse or the keyboard or when the selection changes because of the tree being expanded or collapsed (see the Why Can setExpanded() Cause SWT.Selection? box). The relevant event fields during a selection event for Tree are as follows.

Public Fields of Class Event That Are Valid during SWT.Selection

Field

Description

detail

Set to SWT.CHECK when the user selected the check box portion of the item

item

The item that was affected when the selection changed. Typically, this item is selected but can be unselected when the detail is SWT.CHECK or in a multiselect tree, when the user changes the selection by deselecting the item.


SWT.DefaultSelection (SelectionEvent)

The SWT.DefaultSelection event (typed event SelectionEvent) is sent whenever the user performs the platform-specific operation that is treated as a default selection. On most platforms, default selection occurs when the user double-clicks on an item or presses the <Enter> key. The relevant event fields during a default selection event for Tree are as follows.

Public Fields of Class Event That Are Valid during SWT.DefaultSelection

Field

Description

detail

Set to SWT.CHECK when the user selected the check box part of the item

item

The primary item that was affected when the selection changed


SWT.Expand (TreeEvent)

Table 9.1 shows the expand event for tree controls.

Table 9.1. Expand Event

Untyped Event

Description

SWT.Expand

A tree item was expanded

Typed Event

Listener

Methods

TreeEvent

TreeListener (and TreeAdapter)

treeCollapsed(TreeEvent)

treeExpanded(TreeEvent)


The SWT.Expand event (typed event TreeEvent) is sent whenever the user expands a tree item with the mouse or the keyboard or when the item is expanded as the result of any method other than setExpanded(). The relevant event fields during an expand event for Tree are as follows.

Public Fields of Class Event That Are Valid during SWT.Expand

Field

Description

item

The item that was expanded


Using SWT.Expand to Fill a Tree Lazily

Building a tree that contains thousands of items is time-consuming. For example, a tree used to represent a large hierarchical file system would need not only to create thousands of tree items but also to visit every directory on the file system. Fortunately, the SWT.Expand event can be used to create tree items lazily, as the user expands the tree.

The following example uses an SWT.Expand listener to fill a tree with directories and files as the user expands the tree. This involves creating a dummy item to force the tree to show the plus indicator. This dummy item does not have a file or directory associated with it. When a real item is created, the setData() method is used to store the file or directory.

The roots of the tree, which are always directories, are initialized to contain dummy items. When a tree item is expanded for the first time, the dummy item is deleted, and the real items are created. If any of the real items are directories, a dummy child item is created for each one, allowing the lazy filling process to continue the next time the user selects one of these items. Figure 9.4 shows the result open to an arbitrary place in a file system.






public static void main(String[] args) {

    Display display = new Display();

    Shell shell = new Shell(display);

    shell.setText("Lazy Tree");

    Tree tree = new Tree(shell, SWT.BORDER);



    /* Initialize the roots of the tree */

    File[] roots = File.listRoots();

    for (int i = 0; i < roots.length; i++) {

        TreeItem root = new TreeItem(tree, SWT.NULL);

        root.setText(roots[i].toString());

        root.setData(roots[i]);



        /* Use a dummy item to force the '+' */

        new TreeItem(root, SWT.NULL);

    }



    /* Use SWT.Expand to lazily fill the tree */

    tree.addListener(SWT.Expand, new Listener() {

        public void handleEvent(Event event) {



            /*

            * If the item does not contain a

            * dummy node, return. A dummy item

            * is a single child of the root that

            * does not have any application data.

            */

            TreeItem root = (TreeItem) event.item;

            TreeItem[] items = root.getItems();

            if (items.length != 1) return;

            if (items[0].getData() != null) return;

            items[0].dispose();



            /* Create the item children */

            File file = (File) root.getData();

            File[] files = file.listFiles();

            if (files == null) return;

            for (int i = 0; i < files.length; i++) {

                TreeItem item =new TreeItem(root,SWT.NULL);

                item.setText(files[i].getName());

                item.setData(files[i]);

                /* Use a dummy item to force the '+' */

                if (files[i].isDirectory()) {

                    new TreeItem(item, SWT.NULL);

                }

            }

        }

    });



    /* Set size of the tree and open the shell */

    tree.setSize(300, 300);

    shell.pack();

    shell.open();

    while (!shell.isDisposed()) {

        if (!display.readAndDispatch()) display.sleep();

    }

    display.dispose();

}


Figure 9.4.

graphics/09fig04.gif


Note that a better implementation would sort the items, show icons, and include only directories as nodes in the tree.

SWT.Collapse (TreeEvent)

Table 9.2 shows the collapse event for tree controls.

Table 9.2. Collapse Event

Untyped Event

Description

SWT.Collapse

A tree item was collapsed

Typed Event

Listener

Methods

TreeEvent

TreeListener (and TreeAdapter)

treeCollapsed(TreeEvent)

treeExpanded(TreeEvent)


The SWT.Collapse event (typed event TreeEvent) is sent whenever the user collapses an item with the mouse or the keyboard or when the item is collapsed as the result of any method other than setExpanded(). The relevant event fields during a collapse event for Tree are as follows.

Public Fields of Class Event That Are Valid during SWT.Collapse

Field

Description

item

The item that was collapsed


    Previous Section  < Day Day Up >  Next Section