22 Control de árbol

Los controles de árbol permiten mostrar, seleccionar y opcionalmente modificar, datos relacionados jerárquicamente.
El ejemplo más claro es una estructura de directorios y archivos, donde cada archivo pertenece a un directorio, y cada directorio puede pertenecer a otro.
Pero no es, ni mucho menos, el único caso en el que se puede mostrar información en una estructura de árbol. Estos controles son aptos para mostrar cualquier tipo de información en la que existan relaciones jerárquicas entre elementos, como una jerarquía de clases, una base de datos relacional, ficheros xml, tablas de contenido, etc.
Las estructuras en árbol se componen de nodos, entre los que existe una relación de padre-hijo.
Cada nodo puede tener cero o varios hilos, pero sólo un padre, salvo el nodo raíz, que carece de él.
A los nodos que carecen de hijos se le suele denominar nodos hoja
Hay que tener presente que uno de estos controles puede contener mucha información, y que mostrar todos los nodos no siempre será conveniente y puede dificultar su lectura.
Por ello es habitual que para cada nodo con hijos se muestre un botón a su izquierda que permita desplegar o colapsar el subárbol asociado. El aspecto del botón cambia para mostrar el estado de ese subárbol. Por defecto se muestra un icono '+' para indicar que el árbol puede ser desplegado, y un icono '-' para indicar que el árbol puede ser colapsado.
También hay que tener presente que podemos encontrarnos ante dos tipos diferentes datos con estructura en árbol::
- Partiendo de un único nodo raíz. En este ejemplo ese nodo corresponde al equipo. Ese nodo tendrá varios hijos, uno por cada unidad de disco, y cada unidad tendrá a su vez varios hijos, uno por cada directorio o fichero, y cada directorio, varios hijos más, uno por cada directorio o fichero, y así sucesivamente.
- También podemos encontrarnos con estructuras que contengan varios nodos raíz. Por ejemplo, una tabla de una base de datos relacional puede contener varios registros, y para cada uno de ellos, varios registros en otra tabla, etc.
En la práctica, todas las estructuras en árbol tienen un único nodo raíz, pero a veces podemos prescindir de él si incluirlo no aporta información útil. En el caso del ejemplo de las tablas de una base de datos relacional, todas las tablas relacionadas pertenecerán a la misma base de datos, pero es posible que no nos interese o no sea necesario mostrar ese nodo raíz.
En este capítulo veremos el control wxTreeCtrl, que es el más sencillo de los controles de árbol, ya que en estos controles cada nodo sólo tiene una etiqueta, y todos los nodos son del mismo tipo.
Estilos
Los controles wxTreeCtrl disponen de varios estilos propios.
Estilos relacionados con los botones
- El estilo wxTR_NO_BUTTONS evitará que se muestren los botones para desplegar o colapsar subárboles. Se pueden desplegar o colapsar elementos haciendo doble clic sobre ellos.
- El estilo wxTR_HAS_BUTTONS mostrará los botones de desplegar y colapsar, pero, al menos en Windows, lo ocultará para el nodo raíz.
- El estilo wxTR_TWIST_BUTTONS, que evitaremos usar ya que sólo se implementa en Windows, muestra unos botones alternativos en lugar de '+' y '-'.
Estilos estéticos
- El estilo wxTR_NO_LINES oculta las líneas entre niveles, e indirectamente oculta los botones, salvo que se active wxTR_HAS_BUTTONS.
- El estilo wxTR_LINES_AT_ROOT muestra las líneas entre niveles.
- El estilo wxTR_FULL_ROW_HIGHLIGHT resalta el fondo en toda la línea, en lugar de sólo en la parte del texto. En Windows sólo funciona combinándolo con el estilo wxTR_NO_LINES .
- El estilo wxTR_ROW_LINES (parece que no hace nada, al menos en Windows).
Estilos que afectan al comportamiento del control
- El estilo wxTR_EDIT_LABELS permite editar las etiquetas. Para activar la edición el usuario debe hacer un doble clic lento sobre la etiqueta.
- El estilo wxTR_SINGLE sólo permite la selección de un elemento en cada momento.
- El estilo wxTR_MULTIPLE permite seleccionar varios elementos.
- El estilo wxTR_HIDE_ROOT oculta el nodo raíz.
Estilo por defecto
El estilo wxTR_DEFAULT_STYLE establece los estilos de modo que el aspecto del control sea similar al dl control nativo de la plataforma.
Crear controles de árbol
Como en la mayor parte de los controles, el constructor requiere varios parámetros habituales: un puntero a la ventana padre, un identificador, las coordenadas de la posición, el tamaño del control, unas banderas que indican los estilos, un validador y un nombre.
Como de costumbre, si se opta por una construcción en dos pasos, podemos usar el constructor por defecto y posteriormente usar el método Create, con los mismos parámetros.
Para poder usar uno de estos controles es necesario añadir un nodo raíz. Esto se hace mediante el método AddRoot.
arbol = new wxTreeCtrl(this, idTreeCtrl); arbol->AddRoot(_T("Raíz")); ...
Añadir items
Una vez añadido el nodo raíz, para poder añadir nuevos nodos será necesario especificar el nodo padre. Para ello tendremos que disponer del valor de cada nodo con hijos. Por supuesto, en el caso del nodo raíz esto es prácticamente imprescindible, aunque siempre podemos obtener ese nodo mediante el método GetRootItem.
Para añadir un nodo usaremos el método AppendItem, indicando el nodo padre y la etiqueta.
Los nodos hermanos añadidos con este método, si no se indica lo contrario, se mostrarán en el orden en que se hayan añadido. Veremos más abajo cómo modificar esto.
arbol->AppendItem(arbol->GetRootItem(), _T("Hijo 1")); wxTreeItemId nodo = arbol->AppendItem(arbol->GetRootItem(), _T("Hijo 2")); arbol->AppendItem(nodo, _T("Hijo 2-1")); arbol->AppendItem(nodo, _T("Hijo 2-2")); arbol->AppendItem(nodo, _T("Hijo 2-3"));
Este método dispone de otros tres parámetros de los que hablaremos más abajo.
También podemos insertar elementos en una posición concreta. Para ello podemos usar una de las sobrecargas del método InsertItem. En las dos sobrecargas se añade un parámetro en la segunda posición. En una de ellas se puede indicar el wxTreeItemId a continuación del cual se debe insertar el elemento, y el la otra se puede indicar la posición.
Si se indica una posición hay que tener en cuenta que el valor debe estar entre 0, para insertar en la primera posición y el número de hijos del nodo en el que se inserta.
arbol->InsertItem(arbol->GetRootItem(), 1, _T("Hijo 2"));
El método PrependItem, con el mismo número y tipo de argumentos que AppendItem, insertará el nuevo nodo como primer hijo.
Eliminar items
Para eliminar un ítem concreto usaremos el método Delete, indicando el wxTreeItemId del elemento a borrar. Eliminar un nodo con hijos implica eliminar también todos sus nodos descendientes, es decir, sus hijos y cualquier nodo que dependa de ellos.
Para eliminar todos los nodos hijos de un nodo dado es más conveniente usar DeleteAllChildren, indicando en el parámetro el nodo padre.
Y para eliminar todos los nodos disponemos del método DeleteChildren. Esto borrará todos los elementos, incluido el nodo raíz si no se está usando el estilo wxTR_HIDE_ROOT.
// Elimina el nodo con el foco, si existe: void OrdenTreeCtrl::OnBorrar(wxCommandEvent& event) { if(arbol->GetFocusedItem().IsOk()) arbol->Delete(arbol->GetFocusedItem()); }
Notificación de elemento eliminado
Cuando se elimina un elemento se envía una notificación EVT_TREE_DELETE_ITEM. Si se elimina un nodo con hijos, se enviará una notificación para cada nodo eliminado.
wxBEGIN_EVENT_TABLE(OrdenTreeCtrl, wxDialog) ... EVT_TREE_DELETE_ITEM(idTreeCtrl, OrdenTreeCtrl::OnDeleteItem) ... wxEND_EVENT_TABLE() ... void OrdenTreeCtrl::OnDeleteItem(wxTreeEvent& event) { wxTreeItemId itemId = event.GetItem(); wxString msg; msg << _T("Elemento '") << arbol->GetItemText() << _T("' eliminado."); wxMessageBox(msg, _T("Borrar elemento")); }
Contar elementos
Podemos obtener el número total de elementos del control mediante GetCount.
Si sólo nos interesa el número de nodos hijos de un nodo dado, usaremos el método GetChildrenCount.
Etiquetas
Una vez añadidos los elementos podemos recuperar sus etiquetas mediante GetItemText o asignarles un nuevo valor con SetItemText. En ambos métodos tendremos que pasar como primer parámetro el wxTreeItemId del elemento. En el caso de SetItemText, el segundo parámetro es una referencia constante de un objeto wxString que contiene el nuevo valor de la etiqueta.
wxString cad=_T("Nueva etiqueta"); arbol->SetItemText(itemId, cad); cad = arbol->GetItemText(itemId);

Añadir imágenes
Como estos controles heredan de la clase wxWithImages es posible mostrar un imagen asociada a cada elemento del árbol.
Esto se hace especificando un índice dentro de un objeto wxImageList asociado al control.
Podemos asociar dos listas de imágenes, una para las imágenes de los botones y otra para las imágenes de estado.
Para establecer las imágenes de estado podemos usar varios métodos:
- AssignStateImageList, que sólo está disponible para la versión genérica.
- SetStateImageList, que establece una lista de imágenes wxImageList.
- SetStateImages, que es la recomendable, que establece la lista de imágenes a partir de un wxVector de referencias a objetos wxBitmapBundle.
Nota: El método SetStateImages se añade en la versión 3.3.0 de wxWidgets, por lo que, en el momento de escribir esto, aún no está disponible. Se puede usar en su lugar el método SetImages, heredado de la clase wxWithImages, y que usa el mismo tipo de argumento.
wxVector<wxBitmapBundle> imgs; imgs.push_back(wxBitmapBundle(rojo_xpm)); imgs.push_back(wxBitmapBundle(verde_xpm)); arbol->SetImages(imgs); // Previo a 3.3.0 arbol->SetStateImages(imgs); // A partir de 3.3.0
Esto asigna un conjunto de imágenes con el control, pero además deberemos asignar a cada nodo una imagen para cada estado.
Existen, de momento, cuatro posibles estados, definidos en el enumerado wxTreeItemIcon: normal, seleccionado, expandido y seleccionado-expandido.
Mediante el método SetItemImage podemos asignar una de las imágenes a cada uno de los estados de cada nodo, indicando el nodo, el índice de la imagen y el estado.:
arbol->SetItemImage(nodo, 0, wxTreeItemIcon_Normal); arbol->SetItemImage(nodo, 1, wxTreeItemIcon_Selected); arbol->SetItemImage(nodo, 2, wxTreeItemIcon_Expanded); arbol->SetItemImage(nodo, 3, wxTreeItemIcon_SelectedExpanded);
Otra forma de asignar imágenes a nodos es usar los parámetros image y selimage de los métodos para añadir nodos, aunque esto sólo nos permite usar una o dos imágenes para cada elemento.
En image indicaremos el índice de la imagen a mostrar cuando el elemento no esté seleccionado, y en selimage el índice de la imagen para el nodo seleccionado. Si se ha indicado un valor mayor que -1 para image y un valor -1 para selimage, se usará la misma imagen independientemente del estado.
nodo = arbol->AppendItem(nodo2, _T("Hijo 2-1"), 4, 5);
Si queremos usar imágenes diferentes para nodos expandidos y no expandidos deberemos usar el método SetItemImage.
Para establecer las imágenes de los botones también disponemos de varios métodos, aunque sólo están disponibles en la versión genérica:
- AssignButtonsImageList, usa una lista de imágenes, que pasan a ser propiedad del control, por lo que se encargará de destruirlas al destruir el propio control.
- SetButtonsImageList, también usa una lista de imágenes, pero en este caso no la toma en propiedad, por lo que deberá ser destruida explícitamente.
La lista de imágenes debe contener cuatro imágenes, una para cada estado posible del elemento: normal, seleccionado, expandido y expandido-seleccionado. De otro modo se producirá un error.
wxImageList *buts = new wxImageList(16,16); buts->Add(wxBitmap("navigate_plus.png", wxBITMAP_TYPE_PNG), wxColour(255,255,255)); buts->Add(wxBitmap("navigate_plus.png", wxBITMAP_TYPE_PNG), wxColour(255,255,255)); buts->Add(wxBitmap("navigate_minus.png", wxBITMAP_TYPE_PNG), wxColour(255,255,255)); buts->Add(wxBitmap("navigate_minus.png", wxBITMAP_TYPE_PNG), wxColour(255,255,255)); arbol->AssignButtonsImageList(buts);
Notificación de clic en imagen de estado
Si el usuario hace clic sobre la imagen de estado de un elemento se enviará una notificación EVT_TREE_STATE_IMAGE_CLICK.
Usar control genérico
En sistemas que no dispongan de este tipo de control de forma nativa se usa la versión genérica. Pero podemos forzar el uso de esa versión aunque exista una nativa. De este modo podremos usar características que únicamente estén disponibles en la versión genérica.
Para hacerlo deberemos incluir además de "treectrl.h" el fichero "treectlg.h" y definir la macro wxUSE_TREECTRL como TRUE, ni no está previamente definida:
#define wxUSE_TREECTRL TRUE #include <wx/treectrl.h> #include <wx/generic/treectlg.h>
De este modo podremos usar tanto objetos wxGenericTreeCtrl como wxTreeCtrl,
wxGenericTreeCtrl* arbol; ... arbol = new wxGenericTreeCtrl(this, idTreeCtrl, wxDefaultPosition, wxDefaultSize, wxTR_DEFAULT_STYLE | wxTR_HIDE_ROOT);
Selección simple
Por defecto, estos controles tienen activado el estilo de selección simple wxTR_SINGLE, es decir, sólo se puede seleccionar un elemento cada vez.
Para recuperar el wxTreeItemId del elemento seleccionado se usa el método GetSelection, que equivale para controles de selección simple a GetFocusedItem.
wxString cadena = arbol->GetItemText(arbol->GetSelection());
Lo más habitual es que sea el usuario el que seleccione el elemento que quiera, pero a veces nos puede interesar seleccionar uno desde el código. Para ello podemos usar el método SelectItem. El primer parámetro es el elemento a seleccionar. Un segundo parámetro opcional, si es false deseleccionará el elemento.
Si únicamente queremos deseleccionar un elemento también podemos usar el método Unselect.
Podemos obtener el estado de selección de un elemento mediante IsSelected.
Selección múltiple
Si se especifica el estilo wxTR_MULTIPLE el usuario podrá seleccionar varios elementos. Para recuperar los elementos seleccionados se usa el método GetSelections, pasando una referencia a un objeto wxArrayTreeItemIds que recibirá la lista de elementos seleccionados. El valor de retorno contendrá en número de elementos seleccionados.
La clase wxArrayTreeItemIds no está documentado, pero tiene esta forma:
class wxArrayTreeItemIds : public wxArrayTreeItemIdsBase { public: void Add(const wxTreeItemId& id) { wxArrayTreeItemIdsBase::Add(id.m_pItem); } void Insert(const wxTreeItemId& id, size_t pos) { wxArrayTreeItemIdsBase::Insert(id.m_pItem, pos); } wxTreeItemId Item(size_t i) const { return wxTreeItemId(wxArrayTreeItemIdsBase::Item(i)); } wxTreeItemId operator[](size_t i) const { return Item(i); } };
Por ejemplo:
wxArrayTreeItemIds sels; size_t n; n = arbol->GetSelections(sels); cadena = _T(""); for(size_t i=0; i<n; i++) { cadena += arbol->GetItemText(sels[i]); cadena += _T(","); }
A la hora de seleccionar elementos desde el programa podemos usar SelectItem, que funciona también con controles de selección múltiple.
El método SelectChildren se puede usar para seleccionar todos los hijos de un elemento.
Para eliminar la selección de un elemento en controles de selección múltiple se usa UnselectItem.
Y para eliminar la selección de todos los elementos seleccionados, UnselectAll.
También disponemos del método ToggleItemSelection que se puede usar para seleccionar un elemento no seleccionado o para deseleccionar uno ya seleccionado.
Notificaciones de selección
Disponemos de dos notificaciones relacionadas con la selección de elementos.
La notificación EVT_TREE_SEL_CHANGING es enviada cuando se ha solicitado el cambio en el estado de selección de un elemento. Esto nos proporciona una oportunidad de vetar la selección o deselección de un elemento.
La notifiación EVT_TREE_SEL_CHANGED se envía cuando el estado de selección de un elemento ha cambiado.
Dato de elemento
Como sucede con muchos de los controles que almacenan elementos, en este caso también se puede asignar un dato a cada elemento del árbol.
Esto se puede hacer directamente al añadir los nodos, indicando un wxTreeItemData en el último parámetro, o posteriormente, mediante el método SetItemData.
Esos datos pasan a ser propiedad del control, es decir, serán destruidos automáticamente cuando el control sea destruido.
Para usar estos datos tendremos que crear una clase derivada de wxTreeItemData, a la que añadiremos los datos necesarios así como la interfaz para recuperar y modificar esos valores.
class TreeData : public wxTreeItemData { private: int id; int idpadre; wxString etiqueta; wxTreeItemId itemId; public: TreeData(int i, int p, const wxString etq) : id(i), idpadre(p), etiqueta(etq), itemId(0) {} int Id() const { return id; } int Padre() const { return idpadre; } wxString& Etiqueta() { return etiqueta; } void SetEtiqueta(const wxString &equ) { etiqueta = equ; } void SetItemId(wxTreeItemId id) { itemId = id; } wxTreeItemId GetItemId() const { return itemId; } };
Para cada nodo al que debamos asociar datos crearemos un objeto de esa clase usado el operador new. Esto es importante, porque cuando el control sea destruido, los datos de elemento se destruirán usando el operador delete.
Después asociaremos los datos con el elemento, usando SetItemData.
Esto también se puede hacer directamente al insertar o añadir el elemento al control:
dataItem =new TreeData(1, 0, _T("Etiqueta")); nodo = arbol-&AppendItem(arbol-&GetRootItem(), dataItem-&Etiqueta(), 4, 5, dataItem);
El control asociará cada dato con su elemento, la clase wxTreeItemData contiene un dato miembro para ello. Podemos recuperar el elemento asociado a un dato mediante el método wxTreeItemData#GetId">GetId.
Para recuperar el dato asociado a un elemento usaremos el método GetItemData.
TreeData* data = static_cast<TreeData*>(arbol->GetItemData(itemId));
Estos datos son útiles en muchos casos. Por ejemplo, cuando no toda la información asociada a un nodo se muestra en el árbol. Hay que tener en cuenta que estos controles sólo muestran una etiqueta para cada nodo, cuando en realidad cada uno de esos nodos puede referirse a un objeto con mucha más información.
Un uso frecuente del uso de esos datos es para ordenar los elementos según criterios diferentes del orden alfabético.
Contar elementos
Si necesitamos contar elementos de un control de árbol disponemos de dos métodos.
El método GetCount devuelve el número total de elementos del control.
Por otra parte, el método GetChildrenCount devuelve el número de hijos del nodo cuyo wxTreeItemId pasamos como parámetro.
Ordenar elementos
Si no invocamos el método SortChildren, los elementos se mostrarán en el orden en que se hayan añadido, dependiendo del método utilizado para hacerlo.
Al invocar el método SortChildren, los elementos serán reordenados. Por defecto, el orden es el alfabético ascendente y sensible a mayúsculas y mnúsculas.
Podemos modificar el criterio de ordenación creando nuestro control a partir de una clase derivada en la que sobrescribamos el método OnCompareItems. Este método se usará para comparar dos elementos, y sigue la norma habitual de retornar un valor menor que cero si el segundo elemento debe aparecer antes que el primero, mayor que cero si debe aparecer después, y cero sin son iguales, para el criterio de orden ascendente y al contrario para el orden descendente.
// Clase derivada de wxGenericTreeCtrl o wxTreeCtrl // Rescribiendo el método OnCompareItems class MiTreeCtrl : public wxGenericTreeCtrl { public: MiTreeCtrl(wxWindow *parent, wxWindowID id = wxID_ANY, const wxSize &size = wxDefaultSize, long style = wxTR_DEFAULT_STYLE) : wxGenericTreeCtrl(parent, id, wxDefaultPosition, size, style) {} virtual int OnCompareItems(const wxTreeItemId &item1, const wxTreeItemId &item2); }; ... // Función de comparación de ejemplo: int MiTreeCtrl::OnCompareItems(const wxTreeItemId &item1, const wxTreeItemId &item2) { TreeData *dato1 = static_cast<TreeData*>(GetItemData(item1)); TreeData *dato2 = static_cast<TreeData*>(GetItemData(item2)); return dato1->Valor()-dato2->Valor(); } ... // Crear el control de árbol de nuestra clase derivada: arbol = new MiTreeCtrl(this, idTreeCtrl, wxSize(200,200), wxTR_MULTIPLE | wxTR_DEFAULT_STYLE | wxTR_HIDE_ROOT); ... // En nuestro ejemplo hay un botón para ordenar el árbol, que llama a la función "OrdenarHijos": void OrdenTreeCtrl::OnOrdenar(wxCommandEvent& event) { OrdenarHijos(arbol->GetRootItem()); } // La función recursiva para ordenar // Necesitamos recursividad si queremos ordenar todos los hijos de todas las ramas // independientemente de la profundidad que tenga la estructura void OrdenTreeCtrl::OrdenarHijos(wxTreeItemId nodo) { wxTreeItemIdValue cookie; // Ordena el nivel actual: arbol->SortChildren(nodo); // Recorre la lista de hijos del 'nodo' ordenando cada uno: wxTreeItemId nodo2 = arbol->GetFirstChild(nodo, cookie); do { if(nodo2.IsOk()) { OrdenarHijos(nodo2); nodo2 = arbol->GetNextChild(nodo, cookie); } } while(nodo2.IsOk()); } ...
En estos casos es habitual usar los datos de elemento para establecer el orden, cuando la etiqueta no contiene el valor que usaremos como criterio, o resulta más sencillo extraerlo de otro lugar.
En el programa de ejemplo hemos creado una clase derivada de wxTreeItemData, a la que hemos añadido algunos valores que nos permiten reconstruir el árbol. Para ello usamos un identificador único para cada dato, y un segundo valor que indica el id del nodo padre. También se ha añadido un texto para la etiqueta y un valor numérico. Nuestros nodos se ordenarán ascendentemente por ese valor numérico.
class TreeData : public wxTreeItemData { private: int id; int idpadre; wxString etiqueta; int valor; public: TreeData(int i, int p, const wxString etq, int v) : id(i), idpadre(p), etiqueta(etq), valor(v) {} int Id() const { return id; } int Padre() const { return idpadre; } int Valor() const { return valor; } wxString Etiqueta() { wxString cad; cad << etiqueta << "(" << valor << ")"; return cad; } };
En la práctica, tal vez, sería más interesante almacenar otro valor de tipo wxTreeItemId, que actualizaremos cuando se añada cada nodo, de manera que no tengamos que buscar cada vez el nodo padre de cada nuevo nodo insertado.
Editar etiquetas
Si se ha activado el estilo wxTR_EDIT_LABELS, el usuario podrá editar las etiquetas de los elementos "In situ", haciendo un doble clic lento sobre la etiqueta a editar.
También podemos forzar la edición de la etiqueta de un elemento mediante EditLabel, o forzar la finalización de la edición mediante EndEditLabel. Un segundo parámetro con valor true cancelará la edición, aunque este método sólo está disponible en Windows.
Notificaciones de edición de etiquetas
Cuando la edición de la etiqueta empiece se enviará una notificación EVT_TREE_BEGIN_LABEL_EDIT, y cuando termine una EVT_TREE_END_LABEL_EDIT. En este caso tendremos que consultar wxTreeEvent::IsEditCancelled() para saber si el usuario ha aceptado o cancelado la edición.
wxBEGIN_EVENT_TABLE(EditTreeCtrl, wxDialog) ... EVT_TREE_BEGIN_LABEL_EDIT(idTreeCtrl, EditTreeCtrl::OnBeginEditLabel) EVT_TREE_END_LABEL_EDIT(idTreeCtrl, EditTreeCtrl::OnEndEditLabel) ... wxEND_EVENT_TABLE() ... void EditTreeCtrl::OnBeginEditLabel(wxTreeEvent& event) { wxTreeItemId itemId = event.GetItem(); ... } void EditTreeCtrl::OnEndEditLabel(wxTreeEvent& event) { wxTreeItemId itemId = event.GetItem(); if(event.IsEditCancelled()) { return; } ... }
Colapsar y desplegar
Generalmente los elementos del control se despliegan o se colapsan por el usuario. Pero si nuestra aplicación necesita hacerlo para mostrar u ocultar ciertos elementos, disponemos de varios métodos.
Para expandir un único nodo, de modo que se muestren sus nodos hijos usaremos Expand, indicando como parámetro el wxTreeItemIddel nodo a expandir.
Si además de los nodos hijos queremos que el nodo se expanda recursivamente podemos usar el método ExpanAllChildren, con el mismo argumento que el anterior.
Si lo que queremos es expandir todos los nodos del control usaremos el método ExpandAll, que no necesita ningún parámetro.
Si sólo nos interesa que un nodo concreto sea visible el método EnsureVisible desplegará nos elementos necesarios para asegurarse de que el nodo indicado es visible.
Para colapsar un elemento usaremos el método Collapse, indicando el nodo a colapsar.
EL método CollapseAll colapsa todo el árbol, equivale a llamar a Collapse usando como parámetro el nodo raíz.
El método anterior colapsa un elemento, pero no sus elementos hijos si estaban colapsados a su vez. Si el elemento vuelve a desplegarse, los nodos hijos que previamente estaban desplegados se volverán a mostrar desplegados.
Para colapsar un elemento y todos sus hijos recursivamente se usa el método CollapseAllChildren.
Por último, el método CollapseAndReset colapsa un nodo y además borra todos sus nodos hijo.
El método Toggle desplegará un elemento colapsado o lo colapsará si está expandido.
Notificaciones de colapso y expansión de elementos
Cuando un elemento va a ser colapsado, independientemente de si fue por una acción del usuario o por una llamada a uno de los métodos para colapsar elementos, se enviará una notificación EVT_TREE_ITEM_COLLAPSING. Esto se puede usar para impedir que el elemento sea efectivamente colapsado, invocando el método Veto().
Una vez colapsado un elemento se envía una notificación EVT_TREE_ITEM_COLLAPSED.
Simétricamente, cuando un elemento va a ser expandido se envía una notificación EVT_TREE_ITEM_EXPANDING, que también puede ser vetado.
Una vez un elemento a sido expandido, se envía una notificación EVT_TREE_ITEM_EXPANDED.
El método IsExpanded nos dice si un elemento concreto está o no expandido.
Navegación
A menudo necesitaremos recorrer el árbol, o parte de él, para aplicar alguna función o para buscar algún elemento concreto.
Ya hemos hablado del método GetRootItem, que obtiene el wxTreeItemId del nodo raíz.
EL método GetItemParent obtiene el nodo padre del elemento pasado como parámetro.
Si nos interesa saber si un nodo contiene hijos usaremos el método SetItemHasChildren.
El método GetFirstChild devuelve el wxTreeItemId del primer hijo del nodo pasado como parámetro. El método GetLastChild nos devuelve el último nodo hijo.
Una vez tenemos el primer nodo, podemos obtener los siguientes mediante GetNextChild.
La pareja de métodos GetFirstChild y GetNextChild trabajan con un objeto opaco auxiliar de la clase wxTreeItemIdValue, que funciona como una cookie. Esto permite hacer recorridos recursivos, de modo que se puedan emparejar esos métodos para recorrer la misma rama de elementos hermanos, usando el mismo valor wxTreeItemIdValue:
void Funcion(wxTreeItemId nodo) { wxTreeItemIdValue cookie; wxTreeItemId hijo; ... // Aplicar al 'nodo' las operaciones requeridas if(arbol->ItemHasChildren(nodo)) { hijo = arbol->GetFirstChild(nodo, cookie); do { // Recorrer recursivamente: Funcion(hijo); // Obtener el siguiente hijo: hijo = arbol->GetNextChild(hijo, cookie); } while(hijo.IsOk()); }
Usar estos métodos para recorrer los elementos de un control de árbol de forma recursiva no es tan trivial.
La idea es pasar la cookie a cada llamada recursiva de modo que podamos seguir explorando los nodos hermanos o seguir el recorrido en cada rama con nodos hijos:
void DragDropTreeCtrl::LeerArbol(const wxTreeItemId& idParent, wxTreeItemIdValue cookie=0) { wxTreeItemId nodo; if ( !cookie ) nodo = arbol->GetFirstChild(idParent, cookie); else nodo = arbol->GetNextChild(idParent, cookie); if (!nodo.IsOk()) return; // Hacer algo con el nodo wxString label = arbol->GetItemText(nodo); if(arbol->ItemHasChildren(nodo)) LeerArbol(nodo); LeerArbol(idParent, cookie); }
También podemos recorrer los nodos hermanos mediante los métodos GetNextSibling, para encontrar el siguiente y GetPrevSibling para encontrar el anterior.
Para saber si un elemento tiene el foco se usa el método GetFocusedItem.
Otros métodos
Otros métodos útiles son:
- IsEmpty: para averiguar si el árbol carece de elementos.
- IsVisible: para saber si un elemento está en la parte visible del control.
Modificar apariencia de elementos
Para cada elemento podemos modificar el color del texto, con SetItemTextColour y el color del fondo, con SetItemBackgroundColour.
También podemos poner el texto en negrita o normal mediante SetItemBold.
Arrastre de elementos
Hablaremos de métodos de arrastre de elementos en un capítulo independiente, pero veremos aquí parte del mecanismo que es sencillo de aplicar cuando el origen y destino de las operaciones de drag&drop es el mismo control wxTreeCtrl.
Cualquier operación de arrastre empieza cuando el usuario hace clic sobre un elemento, y sin soltar el botón, empieza a mover el ratón.
Estos controles disponen de dos notificaciones para dar comienzo a una operación de arrastre. EVT_TREE_BEGIN_DRAG para operaciones que usen el botón izquierdo del ratón y EVT_TREE_BEGIN_RDRAG para las que comienzan con el botón derecho.
Para que se pueda realizar una de estas operaciones hay que habilitarlas. Eso se hace respondiendo a una de esas dos notificaciones y llamando al método wxTreeEvent::Allow().
La operación termina cuando el usuario suelta el botón del ratón.
Cuando eso sucede, se envía una notificación EVT_TREE_END_DRAG.
wxBEGIN_EVENT_TABLE(DragDropTreeCtrl, wxDialog) ... EVT_TREE_BEGIN_DRAG(idTreeCtrl, DragDropTreeCtrl::OnBeginDrag) EVT_TREE_END_DRAG(idTreeCtrl, DragDropTreeCtrl::OnEndDrag) ... wxEND_EVENT_TABLE() ... void DragDropTreeCtrl::OnBeginDrag(wxTreeEvent& event) { event.Allow(); origen = event.GetItem(); } void DragDropTreeCtrl::OnEndDrag(wxTreeEvent& event) { destino = event.GetItem(); // Realizar las operaciones necesarias para mover o copiar el origen en el destino }
Ejemplo 22
Windows:
Nombre | Fichero | Fecha | Tamaño | Contador | Descarga |
---|---|---|---|---|---|
Ejemplo 22 | wx022.zip | 2025-06-16 | 29481 bytes | 3 |
Linux:
Nombre | Fichero | Fecha | Tamaño | Contador | Descarga |
---|---|---|---|---|---|
Ejemplo 22 | wx022.tar.gz | 2025-06-16 | 20755 bytes | 4 |