Árboles de Standard Widget Toolkit: creación, ordenación y búsqueda

Este artículo explica la manera de desarrollar e implementar árboles en Standard Widget Toolkit (SWT). Aprenda cómo crear un árbol SWT y rellenarlo con datos, cómo utilizar columnas para clasificar datos, cómo extender un árbol para que pueda soportar la ordenación de filas y cómo buscar en el contenido de un árbol.

George Kyriacou, Reliability Test Engineer , IBM

George Kyriacou joined the IBM Software Lab in Dublin, Ireland, in 2008 after completing an MSc in Computer Science from Trinity College. He currently works as a Reliability Test Engineer for the IBM Lotus Domino System Test team, where he develops tools to improve the testing of Lotus Notes and Lotus Domino. You can reach him at George_Kyriacou@ie.ibm.com.



12-05-2009

Standard Widget Toolkit (SWT) es un kit de herramientas de interfaz gráfica de usuario para múltiples sistemas operativos que permite a los desarrolladores acceder a los widgets y componentes gráficos nativos de las diferentes plataformas subyacentes, utilizando Java™ como único lenguaje de programación.

Originariamente, SWT fue desarrollado para su uso en el proyecto Eclipse; no obstante, con el correr de los años, SWT evolucionó y se transformó en un kit de herramientas independiente que se puede utilizar en cualquier aplicación que requiera una implantación GUI avanzada y que mantiene el aspecto nativo de la plataforma sobre la que se está ejecutando.

Dado que el kit de herramientas IBM® Lotus® Expeditor está basado en la plataforma Eclipse, es necesario tener un buen conocimiento práctico del kit de herramientas SWT para poder desarrollar aplicaciones compuestas y complementos que están integrados utilizando productos IBM Lotus y IBM WebSphere®.

Este artículo se centra en los árboles, que representan un aspecto de SWT. En la figura 1 puede verse un ejemplo de la estructura de un árbol.

Figura 1. Ejemplo de árbol SWT
Ejemplo de árbol SWT

Un aspecto difícil de los árboles SWT, y sobre el que se busca mucha información en los foros de soporte público, es la implantación de la ordenación de los elementos del árbol. Este artículo ofrece un tratamiento detallado de los árboles de SWT, y apunta a complementar el contenido existente y a facilitar el dominio y el uso de esta útil característica en las aplicaciones compuestas propias.

Enfoque y requisitos previos

Este artículo presenta un formato de tutorial paso a paso con fragmentos de código. Se utilizan capturas de pantalla para ilustrar la dirección que toma la implantación. La solución final es completa y puede integrarse fácilmente en cualquier aplicación SWT existente.

Para aprovechar este artículo al máximo, es necesario contar con un conocimiento intermedio del lenguaje de programación Java y un conocimiento básico del kit de herramientas SWT. Se presupone que usted está familiarizado con el desarrollo Java en Eclipse y que sabe cómo enlazar un proyecto con el kit de herramientas SWT. Si desea obtener más información, consulte Developing SWT applications using Eclipse.


Conocimiento de los árboles

En las aplicaciones GUI, los árboles presentan un medio intuitivo de navegación de datos organizando la información en una estructura jerárquica. Es posible trabajar con un determinado conjunto de datos mientras la información innecesaria permanece oculta y no obstruye el proceso. Ver, a modo de ejemplo, la ventana Preferencias en Eclipse (figura 2).

Figura 2. Ventana Preferencias de Eclipse
Ventana Preferencias de Eclipse

Árbol SWT básico

Comencemos con un árbol SWT básico. El código del listado 1 es un ejemplo de un árbol básico en SWT.

Listado 1. Implantación de un árbol básico
			1:import org.eclipse.swt.SWT;
2:import org.eclipse.swt.layout.FillLayout;
3:import org.eclipse.swt.widgets.Display;
4:import org.eclipse.swt.widgets.Shell;
5:import org.eclipse.swt.widgets.Tree;
6:import org.eclipse.swt.widgets.TreeItem;
7:
8:public class First {
9:    public static void main(String[] args) {
10:        Display display = new Display();
11:
12:        Shell shell = new Shell(display);
13:        shell.setText("SWT Trees");
14:        shell.setLayout(new FillLayout());
15:        shell.setSize(400, 300);
16:
17:        Tree tree = new Tree(shell, SWT.BORDER);
18:
19:        for (int i = 0; i < 5; i++) {
20:            TreeItem treeItem = new TreeItem(tree, 0);
21:            treeItem.setText("TreeItem" + i);
22:            for (int j = 0; j < 5; j++) {
23:                TreeItem subTreeItem = new TreeItem(treeItem, SWT.NONE);
24:                subTreeItem.setText("SubTreeItem" + j);
25:                for (int k = 0; k < 5; k++) {
26:                    TreeItem subSubTreeItem = new TreeItem(subTreeItem,
27:                            SWT.NONE);
28:                    subSubTreeItem.setText("SubSubTreeItem" + k);
29:                }
30:            }
31:        }
32:
33:        shell.open();
34:        while (!shell.isDisposed()) {
35:            if (!display.readAndDispatch())
36:                display.sleep();
37:        }
38:        display.dispose();
39:    }
40:}

Para crear un árbol, es necesario instanciar la clase Tree (línea 17 del listado 1); y para crear las ramas del árbol, se deberá instanciar la clase TreeItem (líneas 20, 23 y 26). La figura 3 muestra la presentación del árbol básico cuando el ejemplo se ejecuta en Microsoft® Windows®.

Figura 3. Árbol básico
Árbol básico

Árboles básicos con columnas

Es posible utilizar columnas para clasificar los datos. El ejemplo de código del listado 2 muestra cómo se pueden agregar columnas a un árbol.

Listado 2. Árbol básico con columnas
1:import java.util.Random;
2:
3:import org.eclipse.swt.SWT;
4:import org.eclipse.swt.layout.FillLayout;
5:import org.eclipse.swt.widgets.Display;
6:import org.eclipse.swt.widgets.Shell;
7:import org.eclipse.swt.widgets.Tree;
8:import org.eclipse.swt.widgets.TreeColumn;
9:import org.eclipse.swt.widgets.TreeItem;
10:
11:public class Columns {
12:    public static void main(String[] args) {
13:        Display display = new Display();
14:
15:        Shell shell = new Shell(display);
16:        shell.setText("Basic Tree with Columns");
17:        shell.setLayout(new FillLayout());
18:        shell.setSize(400, 300);
19:
20:        Tree tree = new Tree(shell, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
21:        tree.setHeaderVisible(true);
22:
23:        TreeColumn column1 = new TreeColumn(tree, SWT.NONE);
24:        column1.setText("TreeColumn0");
25:        column1.setWidth(200);
26:        column1.setAlignment(SWT.LEFT);
27:
28:        TreeColumn column2 = new TreeColumn(tree, SWT.NONE);
29:        column2.setText("TreeColumn1");
30:        column2.setWidth(200);
31:        column2.setAlignment(SWT.CENTER);
32:
33:        Random generator = new Random();
34:        
35:        for (int i = 0; i < 5; i++) {
36:            TreeItem treeItem = new TreeItem(tree, 0);
37:            treeItem.setText(new String[] { "TreeItem" + i,
38:                    Integer.toString(generator.nextInt()) });
39:            for (int j = 0; j < 5; j++) {
40:                TreeItem subTreeItem = new TreeItem(treeItem, SWT.NONE);
41:                subTreeItem.setText(new String[] { "SubTreeItem" + j,
42:                        Integer.toString(generator.nextInt()) });
43:                for (int k = 0; k < 5; k++) {
44:                    TreeItem subSubTreeItem = new TreeItem(subTreeItem,
45:                            SWT.NONE);
46:                    subSubTreeItem.setText(new String[] { "SubSubTreeItem" + k,
47:                            Integer.toString(generator.nextInt()) });
48:                }
49:            }
50:        }
51:
52:        shell.open();
53:        while (!shell.isDisposed()) {
54:            if (!display.readAndDispatch())
55:                display.sleep();
56:        }
57:        display.dispose();
58:    }
59:}

Las columnas se agregan a los árboles instanciando la clase TreeColumn (líneas 23 y 28). Es muy importante fijar el ancho de las columnas (líneas 25 y 30) ya que, de lo contrario, no se mostrarán correctamente. Además, el encabezado del árbol se debe fijar como visible (línea 21) para poder visualizar los encabezados de la columna.

Para fijar los datos de cada columna, se debe transferir una matriz de tipo String al método setText de los elementos del árbol (líneas 37, 41 y 46). En este caso se utiliza un número entero aleatorio para el valor de la segunda columna (ver figura 4).

Figura 4. Árbol básico con columnas
Árbol básico con columnas

Ordenación de árboles

Para agregar una ordenación a los árboles SWT, debemos implantar una escucha de selección que sea llamada cada vez que se selecciona una de las columnas del árbol. El listado 3 muestra un código de ejemplo de una escucha de selección que se puede utilizar para ordenar las columnas de la aplicación de ejemplo.

Listado 3. Implementación de escucha de selección de ordenación de árboles
1:import java.text.Collator; 2:import
1:import java.text.Collator;
2:import java.util.Locale;
3:import java.util.regex.Pattern;
4:
5:import org.eclipse.swt.SWT;
6:import org.eclipse.swt.events.SelectionEvent;
7:import org.eclipse.swt.events.SelectionListener;
8:import org.eclipse.swt.widgets.Tree;
9:import org.eclipse.swt.widgets.TreeColumn;
10:import org.eclipse.swt.widgets.TreeItem;
11:
12:public class SortTreeListener implements SelectionListener {
13:    @Override
14:    public void widgetDefaultSelected(SelectionEvent e) {
15:        
16:    }
17:
18:    @Override
19:    public void widgetSelected(SelectionEvent e) {
20:        sortTree(e);
21:    }
22:
23:    private void sortTree(SelectionEvent e) {
24:        TreeColumn column = (TreeColumn) e.widget;
25:        Tree tree = column.getParent();
26:        TreeItem[] treeItems = tree.getItems();
27:        TreeColumn sortColumn = tree.getSortColumn();
28:        TreeColumn columns[] = tree.getColumns();
29:        tree.setSortColumn(column);
30:        int numOfColumns = columns.length;
31:        int columnIndex = this.findColumnIndex(columns, column, numOfColumns);
32:        Collator collator = Collator.getInstance(Locale.getDefault());
33:        Boolean sort = false;
34:        Pattern pattern = Pattern.compile("([\\+]*|[\\-]*)\\d+");
35:        if ((column.equals(sortColumn)) && 
			(tree.getSortDirection() == SWT.UP)) {
36:            tree.setSortDirection(SWT.DOWN);
37:            for (int i = 1; i < treeItems.length; i++) {
38:                String value1 = treeItems[i].getText(columnIndex).trim();
39:                for (int j = 0; j < i; j++) {
40:                    String value2 = treeItems[j].getText(columnIndex).trim();
41:                    if (pattern.matcher(value1).matches()
42:                            && pattern.matcher(value2).matches()) {
43:                        double d1 = this.getDouble(value1);
44:                        double d2 = this.getDouble(value2);
45:                        if (d1 > d2) {
46:                            sort = true;
47:                        }
48:                    } else if (collator.compare(value1, value2) > 0) {
49:                        sort = true;
50:                    }
51:                    if (sort) {
52:                        String[] values = this.getColumnValues(treeItems[i],
53:                                numOfColumns);
54:                        TreeItem[] subItems = treeItems[i].getItems();
55:                        TreeItem item = new TreeItem(tree, SWT.NONE, j);
56:                        item.setText(values);
57:                        for (TreeItem si : subItems) {
58:                            TreeItem[] subSubItems = si.getItems();
59:                            TreeItem subItem = new TreeItem(item, SWT.NONE);
60:                            subItem.setText(this.getColumnValues(si, numOfColumns));
61:                            for (TreeItem ssi : subSubItems) {
62:                                TreeItem subSubItem = new TreeItem(subItem,
63:                                        SWT.NONE);
64:                                subSubItem.setText(this.getColumnValues(ssi,
65:                                        numOfColumns));
66:                            }
67:                        }
68:                        treeItems[i].dispose();
69:                        treeItems = tree.getItems();
70:                        sort = false;
71:                        break;
72:                    }
73:                }
74:            }
75:        } else {
76:            tree.setSortDirection(SWT.UP);
77:            for (int i = 1; i < treeItems.length; i++) {
78:                String value1 = treeItems[i].getText(columnIndex).trim();
79:                for (int j = 0; j < i; j++) {
80:                    String value2 = treeItems[j].getText(columnIndex).trim();
81:                    if (pattern.matcher(value1).matches()
82:                            && pattern.matcher(value2).matches()) {
83:                        double d1 = this.getDouble(value1);
84:                        double d2 = this.getDouble(value2);
85:                        if (d1 < d2) {
86:                            sort = true;
87:                        }
88:                    } else if (collator.compare(value1, value2) < 0) {
89:                        sort = true;
90:                    }
91:                    if (sort) {
92:                        String[] values = this.getColumnValues(treeItems[i],
93:                                numOfColumns);
94:                        TreeItem[] subItems = treeItems[i].getItems();
95:                        TreeItem item = new TreeItem(tree, SWT.NONE, j);
96:                        item.setText(values);
97:                        for (TreeItem si : subItems) {
98:                            TreeItem[] subSubItems = si.getItems();
99:                            TreeItem subItem = new TreeItem(item, SWT.NONE);
100:                            subItem.setText(this.getColumnValues(si, numOfColumns));
101:                            for (TreeItem ssi : subSubItems) {
102:                                TreeItem subSubItem = new TreeItem(subItem,
103:                                        SWT.NONE);
104:                                subSubItem.setText(this.getColumnValues(ssi,
105:                                        numOfColumns));
106:                            }
107:                        }
108:                        treeItems[i].dispose();
109:                        treeItems = tree.getItems();
110:                        sort = false;
111:                        break;
112:                    }
113:                }
114:            }
115:        }
116:    }
117:
118:    /**
119:     * Find the index of a column
120:     * 
121:     * @param columns
122:     * @param numOfColumns
123:     * @return int
124:     */
125:    private int findColumnIndex(TreeColumn[] columns, TreeColumn column,
126:            int numOfColumns) {
127:        int index = 0;
128:        for (int i = 0; i < numOfColumns; i++) {
129:            if (column.equals(columns[i])) {
130:                index = i;
131:                break;
132:            }
133:        }
134:        return index;
135:    }
136:
137:    /**
138:     * Get the double value from a string
139:     * 
140:     * @param str
141:     * @return double
142:     */
143:    private double getDouble(String str) {
144:        double d;
145:        if (str.startsWith("+")) {
146:            d = Double.parseDouble(str.split("\\+")[1]);
147:        } else {
148:            d = Double.parseDouble(str);
149:        }
150:        return d;
151:    }
152:
153:    /**
154:     * Get the array of string value from the provided TreeItem
155:     * 
156:     * @param treeItem
157:     * @param numOfColumns
158:     * @return String[]
159:     */
160:    private String[] getColumnValues(TreeItem treeItem, int numOfColumns) {
161:        String[] values = new String[numOfColumns];
162:        for (int i = 0; i < numOfColumns; i++) {
163:            values[i] = treeItem.getText(i);
164:        }
165:        return values;
166:    }
167:}

Lo primero que se debe hacer es determinar cuál es la columna seleccionada llamando la función widget() de SelectionEvent (línea 24 del listado 3).

Luego se debe recopilar información de las columnas y de los elementos del árbol (líneas 25-31). Será necesario buscar el índice de la columna en la posición en la que aparece en el árbol. Como no existe un método en la clase TreeColumn o Tree class que brinde esta información, la ID de columna se debe determinar comparando todas las columnas del árbol con la columna seleccionada y buscando el número de índice correcto.

El método findColumnIndex (línea 125) está diseñado para realizar esta tarea. Es necesario buscar el índice de columna para poder extraer el contenido textual de la columna. Luego se utilizará ese contenido para comparar cada una de las columnas y determinar su posición correcta en el árbol ordenado.

Se debe determinar si la columna seleccionada para su ordenación es la columna que fue ordenada inmediatamente antes y, si lo es, en qué dirección (ascendente o descendente) fue ordenada. Esta condición se muestra en la línea 35, donde, si la columna se ordena en forma consecutiva y la dirección anterior era ascendente, se deberá establecer una ordenación descendente. En cualquier otro caso, el árbol deberá ordenarse de manera ascendente (líneas 75-116).

Además, se debe determinar si los valores almacenados en la columna que se ordena son numéricos. Si los valores numéricos son tratados como texto, se producirán resultados incorrectos, particularmente si esos resultados están firmados; por lo tanto, el texto de la columna debe ser convertido en un tipo numérico.

Para establecer si una columna contiene un número, se puede utilizar una expresión regular (línea 34). Si el patrón coincide (líneas 41 y 81), almacene el valor de la columna en una variable de tipo doble (líneas 43-44 y 83-84), usando el método getDouble(String str) (línea 143).

La ordenación se efectúa iterando los elementos del árbol (líneas 37-39). Todos los valores almacenados en las columnas son comparados de dos en dos (líneas 45, 48, 85 y 88). Si es necesario cambiar el lugar de dos elementos, se deberá establecer como verdadero una variable de ordenación booleana (líneas 46, 49, 86 y 89). Cuando la variable de ordenación se fija en verdadero, los dos elementos afectados se vuelven a crear como nuevos elementos del árbol en el orden correcto, y los viejos elementos se eliminan del árbol (líneas 51-67 y 91-107).

Para fijar la flecha que muestra la dirección en que se ordena la columna, es necesario llamar el método setSortDirection(int direction) de la clase Tree (líneas 36-37).

Para activar la ordenación de las columnas, usted deberá agregar el siguiente código al método principal antes de llamar el método Shell.open():

column1.addSelectionListener(new SortTreeListener());
column2.addSelectionListener(new SortTreeListener());

El listado 4 muestra la implantación completa del árbol básico con columnas, a la que se agrega la capacidad de ordenación de columnas en las líneas 27 y 33.

Listado 4. Árbol básico con ordenación de columnas agregada
1:import java.util.Random;
2:
3:import org.eclipse.swt.SWT;
4:import org.eclipse.swt.layout.FillLayout;
5:import org.eclipse.swt.widgets.Display;
6:import org.eclipse.swt.widgets.Shell;
7:import org.eclipse.swt.widgets.Tree;
8:import org.eclipse.swt.widgets.TreeColumn;
9:import org.eclipse.swt.widgets.TreeItem;
10:
11:public class Sorting {
12:    public static void main(String[] args) {
13:        Display display = new Display();
14:
15:        Shell shell = new Shell(display);
16:        shell.setText("Sorting Trees");
17:        shell.setLayout(new FillLayout());
18:        shell.setSize(400, 300);
19:
20:        Tree tree = new Tree(shell, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
21:        tree.setHeaderVisible(true);
22:
23:        TreeColumn column1 = new TreeColumn(tree, SWT.NONE);
24:        column1.setText("TreeColumn0");
25:        column1.setWidth(200);
26:        column1.setAlignment(SWT.LEFT);
27:        column1.addSelectionListener(new SortTreeListener());
28:
29:        TreeColumn column2 = new TreeColumn(tree, SWT.NONE);
30:        column2.setText("TreeColumn1");
31:        column2.setWidth(200);
32:        column2.setAlignment(SWT.CENTER);
33:        column2.addSelectionListener(new SortTreeListener());
34:
35:        Random generator = new Random();
36:
37:        for (int i = 0; i < 5; i++) {
38:            TreeItem treeItem = new TreeItem(tree, 0);
39:            treeItem.setText(new String[] { "TreeItem" + i,
40:                    Integer.toString(generator.nextInt()) });
41:            for (int j = 0; j < 5; j++) {
42:                TreeItem subTreeItem = new TreeItem(treeItem, SWT.NONE);
43:                subTreeItem.setText(new String[] { "SubTreeItem" + j,
44:                        Integer.toString(generator.nextInt()) });
45:                for (int k = 0; k < 5; k++) {
46:                    TreeItem subSubTreeItem = new TreeItem(subTreeItem,
47:                            SWT.NONE);
48:                    subSubTreeItem.setText(new String[] { "SubSubTreeItem" + k,
49:                            Integer.toString(generator.nextInt()) });
50:                }
51:            }
52:        }
53:
54:        shell.open();
55:        while (!shell.isDisposed()) {
56:            if (!display.readAndDispatch())
57:                display.sleep();
58:        }
59:        display.dispose();
60:    }
61:}

La figura 5 muestra un árbol básico con ordenación de columnas.

Figura 5. Árbol básico con ordenación de columnas
Árbol básico con ordenación de columnas

Expansión y contracción de todos los elementos del árbol

Una característica útil para agregar a sus aplicaciones es la capacidad de expandir y contraer todos los elementos de nivel superior del árbol. Esto se puede lograr agregando dos botones: uno para expandir y otro para contraer todos los elementos de nivel superior del árbol. El código del listado 5 es un ejemplo de cómo puede implantarse esta característica.

Listado 5. Implantación de la expansión y contracción de los elementos del árbol
1:import java.util.Random;
2:
3:import org.eclipse.swt.SWT;
4:import org.eclipse.swt.events.SelectionEvent;
5:import org.eclipse.swt.events.SelectionListener;
6:import org.eclipse.swt.layout.GridData;
7:import org.eclipse.swt.layout.GridLayout;
8:import org.eclipse.swt.widgets.Button;
9:import org.eclipse.swt.widgets.Display;
10:import org.eclipse.swt.widgets.Shell;
11:import org.eclipse.swt.widgets.Tree;
12:import org.eclipse.swt.widgets.TreeColumn;
13:import org.eclipse.swt.widgets.TreeItem;
14:
15:public class ExpandAll {
16:    private static Tree tree = null;
17:    
18:    public static void main(String[] args) {
19:        Display display = new Display();
20:
21:        Shell shell = new Shell(display);
22:        shell.setText("Expand All Items");
23:        GridLayout gridLayout = new GridLayout();
24:        gridLayout.numColumns = 2;
25:        shell.setLayout(gridLayout);
26:        shell.setSize(400, 300);
27:
28:        Button expButton = new Button(shell, SWT.PUSH);
29:        expButton.setText("+");
30:        GridData gridData = new GridData(23, 23);
31:        expButton.setLayoutData(gridData);
32:        expButton.addSelectionListener(new ExpandAllItemsListener(true));
33:       
34:        Button colButton = new Button(shell, SWT.PUSH);
35:        colButton.setText("-");
36:        gridData = new GridData(23, 23);
37:        colButton.setLayoutData(gridData);
38:        colButton.addSelectionListener(new ExpandAllItemsListener(false));
39:
40:        tree = new Tree(shell, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
41:        tree.setHeaderVisible(true);
42:
43:        gridData = new GridData(GridData.FILL_BOTH);
44:        gridData.horizontalSpan = 2;
45:        tree.setLayoutData(gridData);
46:        
47:        TreeColumn column1 = new TreeColumn(tree, SWT.NONE);
48:        column1.setText("TreeColumn0");
49:        column1.setWidth(200);
50:        column1.setAlignment(SWT.LEFT);
51:        column1.addSelectionListener(new SortTreeListener());
52:
53:        TreeColumn column2 = new TreeColumn(tree, SWT.NONE);
54:        column2.setText("TreeColumn1");
55:        column2.setWidth(200);
56:        column2.setAlignment(SWT.CENTER);
57:        column2.addSelectionListener(new SortTreeListener());
58:
59:        Random generator = new Random();
60:
61:        for (int i = 0; i < 5; i++) {
62:            TreeItem treeItem = new TreeItem(tree, 0);
63:            treeItem.setText(new String[] { "TreeItem" + i,
64:                    Integer.toString(generator.nextInt()) });
65:            for (int j = 0; j < 5; j++) {
66:                TreeItem subTreeItem = new TreeItem(treeItem, SWT.NONE);
67:                subTreeItem.setText(new String[] { "SubTreeItem" + j,
68:                        Integer.toString(generator.nextInt()) });
69:                for (int k = 0; k < 5; k++) {
70:                    TreeItem subSubTreeItem = new TreeItem(subTreeItem,
71:                            SWT.NONE);
72:                    subSubTreeItem.setText(new String[] { "SubSubTreeItem" + k,
73:                            Integer.toString(generator.nextInt()) });
74:                }
75:            }
76:        }
77:
78:        shell.open();
79:        while (!shell.isDisposed()) {
80:            if (!display.readAndDispatch())
81:                display.sleep();
82:        }
83:        display.dispose();
84:    }
85:    
86:    static class ExpandAllItemsListener implements SelectionListener {
87:        private boolean expand = false;
88:
89:        public ExpandAllItemsListener(Boolean expand) {
90:            this.expand = expand;
91:        }
92:
93:        @Override
94:        public void widgetDefaultSelected(SelectionEvent e) {
95:            expandTreeItems();
96:        }
97:
98:        @Override
99:        public void widgetSelected(SelectionEvent e) {
100:            expandTreeItems();
101:        }
102:
103:        public void expandTreeItems() {
104:            TreeItem[] treeItems = tree.getItems();
105:            if (treeItems != null) {
106:                for (TreeItem treeItem : treeItems) {
107:                    treeItem.setExpanded(expand);
108:                }
109:            }
110:        }
111:    }
112:}

En primer lugar se deben agregar los dos botones (líneas 28-38 del listado 5) que expanden o contraen todos los elementos del árbol. Luego se debe implantar una escucha de selección (líneas 86-111) que se ejecute cuando uno de los dos botones esté seleccionado.

El constructor del SelectionListener acepta una variable booleana que determina si los elementos del árbol se expandirán o se contraerán, en función del botón seleccionado. El método expandTreeItems() (línea 103) procesa una iteración en la matriz TreeItems y llama el método setExpanded(boolean expanded) de la clase TreeItem class (línea 107), transfiriendo la variable booleana establecida por el constructor de la clase.

Para obtener la matriz de los elementos de nivel superior del árbol, se puede llamar el método getItems() de la clase Tree (línea 104).

La figura 6 muestra un árbol con todos los TreeItems expandidos.

Figura 6. Árbol con TreeItems expandidos
Árbol con TreeItems expandidos

Búsqueda en los árboles

Otra característica que se puede agregar a una aplicación es la función de búsqueda, la cual resulta especialmente útil si el usuario trabaja con grandes conjuntos de datos.

El código del listado 6 muestra cómo se puede implantar una escucha para efectuar una búsqueda en los elementos de nivel superior del árbol.

Listado 6. Escucha de búsqueda en los elementos del árbol
1:import org.eclipse.jface.dialogs.InputDialog;
2:import org.eclipse.swt.SWT;
3:import org.eclipse.swt.events.KeyEvent;
4:import org.eclipse.swt.events.KeyListener;
5:import org.eclipse.swt.widgets.MessageBox;
6:import org.eclipse.swt.widgets.Tree;
7:import org.eclipse.swt.widgets.TreeItem;
8:
9:public class SearchListener implements KeyListener {
10:    private Tree tree = null;
11:
12:    @Override
13:    public void keyPressed(KeyEvent e) {
14:        tree = (Tree) e.widget;
15:
16:        String searchString = showSearchPopup();
17:        if (searchString == null) {
18:            return;
19:        }
20:
21:        if (!findString(searchString, tree.getItems())) {
22:            MessageBox messageBox = new MessageBox(tree.getShell(), SWT.OK
23:                    | SWT.ICON_ERROR);
24:            messageBox.setMessage("Could not find: '" + searchString + "'");
25:            messageBox.setText("Search Error");
26:            messageBox.open();
27:        }
28:
29:    }
30:
31:    @Override
32:    public void keyReleased(KeyEvent e) {
33:
34:    }
35:
36:    private String showSearchPopup() {
37:        InputDialog d = new InputDialog(this.tree.getParent().getShell(),
38:                "Search", "Search text", "", null);
39:        d.open();
40:        return d.getValue();
41:    }
42:
43:    private boolean findString(String searchString, TreeItem[] treeItems) {
44:        for (TreeItem treeItem : treeItems) {
45:            for (int i = 0; i < tree.getColumnCount(); i++) {
46:                String text = treeItem.getText(i);
47:                if ((text.toUpperCase().contains(searchString.toUpperCase()))) {
48:                    tree.setSelection(treeItem);
49:                    return true;
50:                }
51:            }
52:        }
53:
54:        return false;
55:    }
56:}

La escucha se puede agregar al árbol como un KeyListener; de esta manera, cuando un usuario escriba algo mientras uno de los elementos del árbol esté seleccionado, aparecerá una ventana de entrada como la que se muestra en la figura 7.

Figura 7. Ventana de búsqueda
Ventana de búsqueda

El algoritmo es simple; procesa una iteración en los elementos de nivel superior del árbol y compara su contenido con la cadena de búsqueda (líneas 43-52 del listado 6). Cuando se encuentra la cadena de búsqueda en uno de los elementos del árbol, ese elemento se seleccionará (línea 48). Si la cadena no se encuentra, aparecerá el siguiente mensaje de error de búsqueda: No se encontró: 'TreeItemX' (líneas 21-27).

Ahora, la única modificación que resta es agregar el SearchListener a la colección de escuchas clave, tal como se muestra en la línea 23 del listado 7.

Listado 7. Búsqueda básica en un árbol
1:import java.util.Random;
2:
3:import org.eclipse.swt.SWT;
4:import org.eclipse.swt.layout.FillLayout;
5:import org.eclipse.swt.widgets.Display;
6:import org.eclipse.swt.widgets.Shell;
7:import org.eclipse.swt.widgets.Tree;
8:import org.eclipse.swt.widgets.TreeColumn;
9:import org.eclipse.swt.widgets.TreeItem;
10:
11:public class Searching {
12:    public static void main(String[] args) {
13:        Display display = new Display();
14:
15:        Shell shell = new Shell(display);
16:        shell.setText("Searching");
17:        shell.setLayout(new FillLayout());
18:        shell.setSize(400, 300);
19:
20:        Tree tree = new Tree(shell, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL
21:                | SWT.FULL_SELECTION);
22:        tree.setHeaderVisible(true);
23:        tree.addKeyListener(new SearchListener());
24:
25:        TreeColumn column1 = new TreeColumn(tree, SWT.NONE);
26:        column1.setText("TreeColumn0");
27:        column1.setWidth(200);
28:        column1.setAlignment(SWT.LEFT);
29:        column1.addSelectionListener(new SortTreeListener());
30:
31:        TreeColumn column2 = new TreeColumn(tree, SWT.NONE);
32:        column2.setText("TreeColumn1");
33:        column2.setWidth(200);
34:        column2.setAlignment(SWT.CENTER);
35:        column2.addSelectionListener(new SortTreeListener());
36:
37:        Random generator = new Random();
38:
39:        for (int i = 0; i < 5; i++) {
40:            TreeItem treeItem = new TreeItem(tree, 0);
41:            treeItem.setText(new String[] { "TreeItem" + i,
42:                    Integer.toString(generator.nextInt()) });
43:            for (int j = 0; j < 5; j++) {
44:                TreeItem subTreeItem = new TreeItem(treeItem, SWT.NONE);
45:                subTreeItem.setText(new String[] { "SubTreeItem" + j,
46:                        Integer.toString(generator.nextInt()) });
47:                for (int k = 0; k < 5; k++) {
48:                    TreeItem subSubTreeItem = new TreeItem(subTreeItem,
49:                            SWT.NONE);
50:                    subSubTreeItem.setText(new String[] { "SubSubTreeItem" + k,
51:                            Integer.toString(generator.nextInt()) });
52:                }
53:            }
54:        }
55:
56:        shell.open();
57:        while (!shell.isDisposed()) {
58:            if (!display.readAndDispatch())
59:                display.sleep();
60:        }
61:        display.dispose();
62:    }
63:}

Por último, es posible modificar fácilmente el algoritmo de búsqueda para efectuar una búsqueda recursiva en todos los elementos del árbol.


Conclusión

Los árboles SWT representan una manera sumamente eficaz de organizar datos y presentarlos al usuario. Utilizando la información y los ejemplos de código de este artículo, usted podrá mejorar y aprovechar al máximo sus aplicaciones compuestas o basadas en SWT.


Agradecimiento

El autor agradece especialmente a Gary Denner por su permanente apoyo y por su colaboración en este artículo.

Recursos

Comentarios

developerWorks: Ingrese

Los campos obligatorios están marcados con un asterisco (*).


¿Necesita un IBM ID?
¿Olvidó su IBM ID?


¿Olvidó su Password?
Cambie su Password

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


La primera vez que inicie sesión en developerWorks, se creará un perfil para usted. La información en su propio perfil (nombre, país/región y nombre de la empresa) se muestra al público y acompañará a cualquier contenido que publique, a menos que opte por la opción de ocultar el nombre de su empresa. Puede actualizar su cuenta de IBM en cualquier momento.

Toda la información enviada es segura.

Elija su nombre para mostrar



La primera vez que inicia sesión en developerWorks se crea un perfil para usted, teniendo que elegir un nombre para mostrar en el mismo. Este nombre acompañará el contenido que usted publique en developerWorks.

Por favor elija un nombre de 3 - 31 caracteres. Su nombre de usuario debe ser único en la comunidad developerWorks y debe ser distinto a su dirección de email por motivos de privacidad.

Los campos obligatorios están marcados con un asterisco (*).

(Por favor elija un nombre de 3 - 31 caracteres.)

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


Toda la información enviada es segura.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=Lotus, WebSphere
ArticleID=395671
ArticleTitle=Árboles de Standard Widget Toolkit: creación, ordenación y búsqueda
publish-date=05122009