23 Control TreeList

En este capítulo veremos los controles wxTreeListCtrl, que combinan en un sólo control un wxTreeCtrl y wxListCtrl.
En estos controles, cada nodo puede tener una o varias columnas de datos.
Además la clase que encapsula estos controles contiene métodos para ordenar sus elementos según los valores de diferentes columnas. También permite añadir imágenes y casillas de verificación, de dos o tres estados.
Estilos
Estos controles disponen de varios estilos que permiten definir el comportamiento y el aspecto del control.
Los estilos wxTL_SINGLE y wxTL_MULTIPLE permiten establecer un control de selección única o múltiple, respectivamente. Si no se indica ninguno de ellos, por defecto el control será de selección única.
Los estilos wxTL_CHECKBOX, wxTL_3STATE y wxTL_USER_3STATE, permiten añadir una casilla de verificación a la primera columna. El primero añadirá casillas de dos estados, el segundo y tercero añade casillas de tres estados. En el caso de wxTL_3STATE, el estado indeterminado sólo se podrá asignar por el programa, pero no por el usuario.
wxTL_NO_HEADER oculta las cabeceras de las columnas.
wxTL_DEFAULT_STYLE usa el estilo por defecto, que actualmente es wxTL_SINGLE.
Control wxTreeListCtrl
Para crear un control de este tipo usaremos el constructor que permite especificar sus parámetros, o, como en el resto de controles, el constructor por defecto y una llamada posterior a Create.
Como de costumbre, el primer parámetro es un puntero a la ventana que contiene el control, seguido de un identificador.
El resto de parámetros son opcionales: posición y tamaño, estilo y un nombre.
wxTreeListCtrl *arbol = new wxTreeListCtrl( this, idTreeListCtrl, wxDefaultPosition, wxSize(200,200), wxTL_MULTIPLE | wxTL_DEFAULT_STYLE);
Una vez creado hay que añadir al menos una columna. Para ello se usa el método AppendColumn, indicando el texto de la etiqueta, la anchura, el modo de alineamiento del texto y unas banderas con opciones.
La anchura puede ser un valor numérico o el valor wxCOL_WIDTH_AUTOSIZE. para que se calcule automáticamente. La última columna añadida ignora este valor, y ocupará el resto del espacio disponible en el control.
El parámetro que permite elegir el alineamiento del texto puede tomar uno de los valores del enumerado wxAlignment. Este estilo afecta tanto a la cabecera como a los elementos que contenga el control.
Para las banderas hay dos valores que pueden ser combinados: wxCOL_RESIZABLE para permitir al usuario cambiar la anchura de la columna, y wxCOL_SORTABLE, que permite que el usuario pueda ordenar los elementos según los valores de esa columna.
arbol->AppendColumn(_T("Autor")); arbol->AppendColumn(_T("Título"));
Comportamiento inesperado
En Windows, al insertar estos controles usando un sizer tenemos un efecto inesperado. En lugar de mostrarse el control correctamente, sólo aparece un pequeño rectángulo en la zona superior izquierda.
No he encontrado documentación al respecto, pero he conseguido solucionarlo añadiendo una línea de código y modificando otra.
Generalmente añadimos el control especificando el tamaño deseado, a continuación lo añadimos al sizer, junto al resto de controles y finalmente usamos el método SetSizerAndFit para ajustar el tamaño y contenido de la ventana.
Esto funciona correctamente en Linux, pero por algún motivo que desconozco no funciona en Windows.
arbol = new wxTreeListCtrl(this, idTreeListCtrl, wxDefaultPosition, wxSize(400,300), wxTL_MULTIPLE | wxTL_DEFAULT_STYLE); boxVertical->Add(arbol, wxSizerFlags().Expand().Border()); ... SetSizerAndFit(boxVertical);
Modificando el tamaño del control, indicando -1 para su altura, y añadiendo una llamada al método SetSize para asignar el tamaño deseado más tarde, todo funciona correctamente.
arbol = new wxTreeListCtrl(this, idTreeListCtrl, wxDefaultPosition, wxSize(400,-1), wxTL_MULTIPLE | wxTL_DEFAULT_STYLE); boxVertical->Add(arbol, wxSizerFlags().Expand().Border()); ... arbol->SetSize(wxSize(400,300)); SetSizerAndFit(boxVertical);
Eliminar columnas
Para eliminar columnas disponemos de dos métodos.
El método ClearColumns elimina todas las columnas del control.
DeleteColumn permite eliminar una única columna, indicando como parámetro el índice de la columna a eliminar.
Imágenes
Si se quiere añadir imágenes a los elementos del control hay que asignarle previamente una lista de imágenes.
Para ello se usa un objeto de la clase wxImageList, que contendrá las imágenes disponibles para los elementos.
Para añadir esas imágenes al control hay dos posibilidades:
- Usar el método AssignImageList, que transfiere la propiedad de la lista de imágenes al control. Esto significa que una vez el control sea destruido, también lo será la lista de imágenes asociada.
- Usar el método SetImageList, que no transfiere la propiedad de la lista al control, de modo que deberá ser liberada por el programa posteriormente.
imagenes = new wxImageList(16, 16, false); imagenes->Add(wxBitmap(down_blue_xpm)); imagenes->Add(wxBitmap(down_green_xpm)); imagenes->Add(wxBitmap(left_blue_xpm)); imagenes->Add(wxBitmap(left_green_xpm)); imagenes->Add(wxBitmap(blue_xpm)); imagenes->Add(wxBitmap(green_xpm)); arbol->AssignImageList(imagenes);
Añadir elementos
Disponemos de tres métodos para añadir elementos al control:
- AppendItem, añade el elemento a continuación del último elemento con el padre indicado.
- InsertItem, permite indicar el elemento a continuación del cual se añadirá éste. AppendItem equivale a llamar a este método con el valor wxTLI_LAST.
- PrependItem, añade el elemento en la primera posición con el padre indicado. Equivale a llamar a eInsertItem con el valor wxTLI_FIRST.
El primer parámetro siempre será el nodo padre.
El método InsertItem tiene un segundo parámetro que indica el nodo a continuación del cual se insertará el nuevo elemento.
A continuación se indica el texto de la primera columna del elemento.
Los siguientes parámetros son opcionales. imageClosed indica el índice de la imagen dentro de la lista de imágenes asociada al control, que se mostrará cuando no se muestren los hijos del elemento, es decir, cuando el nodo esté colapsado.
imageOpened indica el índice de la imagen dentro de la lista de imágenes asociada al control, que se mostrará cuando se muestren los hijos del elemento, esto es, cuando el nodo esté expandido.
El último parámetro es un valor de cliente asociado al elemento de la clase wxClientData, o de una clase derivada de ella.
Estos controles contienen un elemento raíz virtual no visible, que usaremos para añadir elementos a partir del elementos sin nodos padre. Para obtener ese elemento se usa el valor devuelto por el método GetRootItem.
nodo = arbol->AppendItem(arbol->GetRootItem(), _T("Fulano"),4,0);
Los métodos para añadir elementos sólo permiten establecer valores para la primera columna.
Para establecer los valores del resto de columnas usaremos el método SetItemText.
Este método requiere como primer parámetro un objeto de la clase wxTreeListItem. Podemos usar el valor devuelto por cualquiera de los métodos para añadir elementos.
El segundo parámetro es el índice de la columna al que queremos asignar el texto, empezando por cero. La columna 0 es la primera columna.
A continuación se indica el texto a asignar a la columna.
arbol->SetItemText(nodo, 1, _T("Título"));
Una sobrecarga de este método sólo tiene los parámetros primero y tercero, y permite modificar la etiqueta de la primera columna.
Por supuesto, este método se puede utilizar para modificar el valor de la etiqueta de cualquier columna de cualquier elemento.
También hay métodos para modificar las imágenes asociadas a un elemento SetItemImage, indicando el ítem y los índices de las imágenes a utilizar para el nodo colapsado y expandido.
arbol->SetItemImage(nodo, 4, 0);
Dato de elemento
Es norma general que en controles que contengan elementos se pueda asignar un dato a cada uno de esos elementos.
Esto se puede hacer directamente al añadir los nodos, indicando un objeto de la clase wxClientData o de una clase derivada, en el último parámetro, o posteriormente, mediante el método SetItemData.
Al igual que en el caso de los controles wxTreeCtrl, esos datos pasan a ser propiedad del control, es decir, serán destruidos automáticamente cuando el elemento sea destruido.
Para usar estos datos generalmente crearemos una clase derivada de wxClientData, a la que añadiremos los datos necesarios así como la interfaz para recuperar y modificar esos valores.
class TreeListData : public wxClientData { private: int id; int idpadre; wxString nombre; wxString telefono; wxString email; wxTreeListItem itemId; public: TreeListData(int i, int p, const wxString n, wxString t, wxString e) : id(i), idpadre(p), nombre(n), telefono(t), email(e) {} int Id() const { return id; } int Padre() const { return idpadre; } wxString& Nombre() { return nombre; } wxString& Telefono() { return telefono; } wxString& Email() { return email; } void SetItemId(wxTreeListItem id) { itemId = id; } wxTreeListItem 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, ya sea indicándolo al insertar el elemento, o usando SetItemData.
nodo = arbol->AppendItem(arbol->GetRootItem(), dataItem[i]->Nombre(), 4, 5, dataItem[i]); arbol->SetItemText(nodo, 1, dataItem[i]->Telefono()); arbol->SetItemText(nodo, 2, dataItem[i]->Email()); dataItem[i]->SetItemId(nodo);
Para recuperar el dato asociado a un elemento usaremos el método GetItemData.
Estos datos son útiles en muchos casos. Por ejemplo, cuando no toda la información asociada a un nodo se muestra en el árbol porque hayamos ocultado alguna columna. También serán útiles para ordenar los elementos por diferentes columnas.
Eliminar ítems
Para eliminar elementos también disponemos de dos métodos.
DeleteAllItems elimina todos los elementos que contiene el control.
El método DeleteItem eliminará el ítem especificado mediante su wxTreeListItem, y además todos sus elementos descendientes.
Marcas de chequeo
Estos controles permiten añadir una casilla de verificación a cada uno de los elementos.
Hay tres modos en que pueden funcionar estas marcas. La más simple consiste en una casilla de dos estados: marcada o sin marcar.
Para activar estas casillas se usa el estilo wxTL_CHECKBOX.
También podemos usar casillas de verificación de tres estados, en las que el tercer estado, indeterminado, se pueda seleccionar por el usuario, mediante el estilo wxTL_USER_3STATE, o únicamente por el programa, mediante wxTL_3STATE.
En cualquier caso podemos establecer el valor de la marca mediante el método CheckItem ,indicando el elemento y el valor del estado a establecer.
wxTreeListItem nodo = arbol->AppendItem(arbol->GetRootItem(), _T("Nodo")); Arbol->CheckItem(nodo, wxCHK_CHECKED);
Los valores del estado pertenecen al tipo enumerado wxCheckBoxState, y pueden ser wxCHK_UNCHECKED, wxCHK_CHECKED y si el estilo lo permite wxCHK_UNDETERMINED.
Para obtener el estado de la marca de un elemento se usa el método GetCheckedState.
if(wxCHK_CHECKED == arbol->GetCheckedState(item)) { ... }
Como existe una jerarquía entre los elementos del control, la clase wxTreeListCtrl nos proporciona métodos adicionales para trabajar con grupos de elementos relacionados.
Así, el método CheckItemRecursively permite establecer las marcas para un elemento y para todos sus elementos descendientes.
De forma similar, el método AreAllChildrenInState permite verificar si las marcas de verificación de todos los descendientes de un elemento están en el mismo estado.
Esto se suele usar en controles con estilo wxTL_3STATE para decidir si el estado de un nodo debe ser el mismo estado, si todos sus hijos están en él, o wxCHK_UNDETERMINED en caso contrario.
Esto se puede hacer más fácilmente mediante el método UpdateItemParentStateRecursively.
Notificaciones de marcas de verificación
Estos controles generan una notificación EVT_TREELIST_ITEM_CHECKED cuando el estado de una marca de verificación cambia. Esto es útil sI necesitamos realizar alguna acción cuando esto sucede. Por ejemplo, actualizar el estado de la marca del elemento padre y las de sus descendientes cuando el usuario modifica alguna marca de cualquier elemento.
El método GetOldCheckedState de wxTreeListEvent obtiene el estado previo de la marca de verificación.
wxBEGIN_EVENT_TABLE(Check3TreeList, wxDialog) ... EVT_TREELIST_ITEM_CHECKED(idTreeListCtrl, Check3TreeList::OnCambioCheck) ... wxEND_EVENT_TABLE() ... void Check3TreeList::OnCambioCheck(wxTreeListEvent& event) { wxTreeListItem item = event.GetItem(); wxCheckBoxState estado = arbol->GetCheckedState(item); arbol->CheckItemRecursively(item, estado); arbol->UpdateItemParentStateRecursively(item); }
Selecciones
Por supuesto, cada elemento del control puede estar o no seleccionado.
El usuario puede seleccionar un elemento mediante el ratón o el teclado, pero para seleccionar un elemento desde el programa usaremos el método Select, indicando el elemento a seleccionar. En controles de selección única, al seleccionar un elemento se eliminará la selección del previamente seleccionado, si lo había.
Para controles de selección múltiple podemos marcar todos los elementos como seleccionados mediante SelectAll,
Para eliminar la selección existen los métodos similares: Unselect y UnselectAll, aunque sólo pueden usarse en controles de selección múltiple.
Para averiguar si un elemento está o no seleccionado se usa el método IsSelected.
Aunque en general será más útil usar GetSelection, para controles de selección única, que devuelve el elemento seleccionado, y en caso de que no hay ninguno, el elemento devuelto no será válido, es decir, devolverá false si se invoca a su método IsOk.
wxTreeListItem nodo; nodo = arbol->GetSelection(); if(nodo.IsOk()) { ... }
Para controles de selección múltiple se usa el método GetSelections, que toma como parámetro un objeto wxTreeListItems, que contendrá un vector con los elementos seleccionados.
void TreeList::OnBorrarItems(wxCommandEvent& event) { wxTreeListItems sels; size_t n; n = arbol->GetSelections(sels); for(size_t i=0; i<n; i++) { arbol->DeleteItem(sels[i]); } }
Notificaciones de selección
El control enviará una notificación EVT_TREELIST_SELECTION_CHANGED cada vez que un elemento sea seleccionado o deje de estar seleccionado. Esto se puede usar para varias cosas, dependiendo de la aplicación.
La notificación EVT_TREELIST_ITEM_ACTIVATED se envía cuando el usuario hace doble clic sobre un elemento o cuando se pulsa Return con un elemento seleccionado.
El método GetItem de wxTreeListEvent obtiene el elemento seleccionado.
Cuando un elemento de es deseleccionado también se genera el evento, pero el objeto wxTreeListEvent no proporciona información sobre el elemento que ha perdido la selección.
Lo mismo sucede en controles de selección múltiple si la selección del nuevo elemento conlleva la selección de varios elementos.
Ordenar elementos
Para que estos controles puedan ordenar los elementos según los valores de una columna es necesario que esa columna tenga activa la bandera wxCOL_SORTABLE.
arbol->AppendColumn(_T("Nombre"), wxCOL_WIDTH_AUTOSIZE, wxALIGN_LEFT, wxCOL_SORTABLE);
Por defecto, si no se indica lo contrario, el orden será el alfabético. Si nuestra aplicación requiere un criterio diferente para ordenar alguna columna será necesario crear una clase derivada de wxTreeListItemComparator, en la que deberemos sobrescribir el método Compare.
class Comparador : public wxTreeListItemComparator { public: int Compare(wxTreeListCtrl* arbol, unsigned columna, wxTreeListItem primero, wxTreeListItem segundo); };
Esta clase dispone de un destructor virtual que puede sobrescribirse si fuese necesario liberar algún recurso que nuestro método de comparación tenga que obtener.
Posteriormente se debe invocar al método SetItemComparator para que el control use el nuevo criterio de ordenación, o invocarlo con un puntero nulo (nullptr), para restablecer el criterio por defecto.
... comparador = new Comparador; ... arbol->SetItemComparator(comparador); ...
Si una columna tiene activada la bandera wxCOL_SORTABLE y el usuario hace clic sobre la cabecera de esa columna, los elementos serán ordenados según los valores de esa columna en cada nivel de anidamiento. Esto también se puede hacer desde el programa mediante el método SetSortColumn, indicando como parámetros el índice de la columna a ordenar y un valor booleano con valor true para indicar un orden ascendente o false para el orden descendente.
EL método GetSortColumn permite obtener la columna por la que están ordenados los elementos. Si no se ha establecido ningún orden, el valor de retorno será false. En caso contrario, se actualizarán los valores de los objetos apuntados por los parámetros. El primer parámetro es un puntero a un entero sin signo que recibirá el índice de la columna por la que se ha ordenado el control y el segundo parámetro es un puntero a un booleano que recibirá el valor true si los elementos están ordenados ascendentemente y false si el orden es el contrario.
Notificaciones de orden
El control enviará una notificación EVT_TREELIST_COLUMN_SORTED cada vez que los elementos hayan sido reordenados.
El método GetColumn de wxTreeListEvent obtiene el índice de la columna por la que se ha ordenado el control.
Navegación
El control dispone de varios métodos para recorrer los diferentes elementos que contiene.
Ya hemos mencionado el método GetRootItem, que obtiene el elemento raíz del control, que no se muestra, pero que sirve para añadir elementos en la primera capa.
EL método GetItemParent obtiene el elemento padre del elemento pasado como parámetro.
El método GetFirstItem obtiene el primer elemento del árbol, que es el primer hijo del elemento raíz.
GetNextItem obtiene el siguiente elemento a partir del elemento dado. El orden en el que se recorren los elementos es en profundidad, es decir, el siguiente nodo de un nodo con hijos será su primer hijo. El siguiente al de un nodo sin hijos será el siguiente hermano, etc.
La combinación de estos dos métodos permite recuperar todos los elementos del control:
wxTreeListItem item; item = arbol->GetFirstItem(); while(item.IsOk()) { if(wxCHK_CHECKED == arbol->GetCheckedState(item)) { cadena += arbol->GetItemText(item); cadena += _T(","); } item = arbol->GetNextItem(item); }
El método GetFirstChild obtiene el primer elemento hijo del elemento dado. Si el elemento pasado como parámetro no tiene hijos, el método IsOk del elemento devuelto retornará false.
Por último, el método GetNextSibling devuelve el siguiente elemento en el mismo nivel del elemento dado, es decir, el siguiente hermano.
Expandir y colapsar
Los elementos del árbol pueden ser expandidos o colapsados por el usuario usando el ratón o el teclado, pero también puede hacerse desde el programa usando el método Collapse, que colapsará la rama indicada como parámetro. Si se indica el elemento raíz, se colapsará todo el aŕbol.
El método Expand expandirá la rama correspondiente al elemento dado.
IsExpanded nos permite determinar si la rama correspondiente a un elemento está expandida o no.
Notificaciones de expansión
Estos controles envían una notificación EVT_TREELIST_ITEM_EXPANDING cuando se ha solicitado la expansión de un elemento, pero antes de que se produzca. Esto proporciona al programa la posibilidad de evitar la expansión.
La notificación EVT_TREELIST_ITEM_EXPANDED se envía después de realizada la expansión.
No existen notificaciones equivalentes para los colapsos.
Ejemplo 23
Windows:
Nombre | Fichero | Fecha | Tamaño | Contador | Descarga |
---|---|---|---|---|---|
Ejemplo 23 | wx023.zip | 2025-09-08 | 21683 bytes | 54 |
Linux:
Nombre | Fichero | Fecha | Tamaño | Contador | Descarga |
---|---|---|---|---|---|
Ejemplo 23 | wx023.tar.gz | 2025-09-08 | 14457 bytes | 48 |