18 ListControl y ListView

Los controles de lista que vimos en el capítulo 8 sólo podían mostrar una columna de datos. Pero a menudo los datos que tenemos que mostrar tienen varias propiedades. Esto es frecuente, por ejemplo, cuando tenemos que mostrar registros de algún tipo de base de datos.
Además, nos puede interesar mostrar los datos en diferentes formatos u ordenarlos según distintas propiedades.
A cada opción le corresponde un ítem y para cada ítem pueden añadirse varios subítems, opcionalmente. Generalmente siempre habrá al menos un subítem, ya que de otro modo sería preferible usar un control de lista más simple.
Aquí entran los controles como wxListCtrl o wxListView.
Al igual que los controles de lista básico, estos controles permiten seleccionar una de las opciones o varias.
Este capítulo va a ser largo, ya que estos controles son muy complejos y tienen muchas opciones.
Modos
La principal característica de esta clase de controles es que admiten diferentes modos para la visualización de los datos que contienen. Cada modo muestra las opciones en el interior del control de formas diferentes.
- Modo lista
Es el modo más simple. En este modo sólo se muestran los ítems, mientras que los subítems permanecen ocultos. Opcionalmente, a cada ítem se le puede añadir un icono. La diferencia con un wxListBox es que se puede aprovechar toda la anchura del control, añadiendo tantas columnas como sea necesario.
Para usar este modo hay que especificar el estilo wxLC_LIST.
- Modo reporte
En este modo se muestra en cada línea un ítem y los subítems correspondientes, además de una cabecera opcional.
A este modo le corresponde el estilo wxLC_REPORT.
- Modo de iconos grandes
Para cada ítem se muestra un icono de tamaño normal junto a la etiqueta correspondiente al ítem.
Este estilo se selecciona con la constante wxLC_ICON.
- Modo de iconos pequeños
Para cada ítem se muestra un icono de tamaño pequeño junto a la etiqueta correspondiente al ítem.
Para activar este estilo se usa la constante wxLC_SMALL_ICON.
- Un objeto wxPoint, que indica un punto físico. Un tercer parámetro indica la dirección de búsqueda.
- Una cadena wxString, que indica la cadena a buscar, sin hacer distinción entre mayúsculas y minúsculas. Un tercer parámetro indica si la cadena a encontrar debe ser igual (false) o si debe empezar con la cadena indicada (true).
- Un valor wxUIntPtr, que buscará el siguiente ítem con el dato de cliente indicado.
- IsVirtual que devuelve true si el control es virtual.
- EnableAlternateRowColours activa un patrón de alternancia de colores para las filas.
- RefreshItem redibuja un ítem.
- RefreshItems redibuja un rango de ítems.
- EnsureVisible hace que el ítem indicado sea visible en el control, desplazando el contenido en la dirección adecuada.
- GetCountPerPage devuelve el máximo número de ítems visibles en el control.
- GetItemText devuelve el contenido de una columna de un ítem.
- GetTopItem devuelve el índice del elemento visible más alto en la vista de lista o de reporte.
- InReportView devuelve true si está activa la vista de reporte.
- IsEmpty devuelve true si la lista no contiene ítems.
- IsVisible devuelve true si el ítem indicado es visible.
- SetItemText establece el texto de un ítem.
- SetSingleStyle añade o elimina un estilo del control.
- Focus establece el ítem con el foco.
- GetFocusedItem obtiene el índice con el foco.
- IsSelected devuelve true si el ítem indicado está seleccionado.
- Select selecciona o deselecciona el ítem indicado, dependiendo del valor del segundo parámetro.
- SetColumnImage establece la imagen para una columna.
Listas de imágenes
Cuando se usan los modos de iconos grandes o pequeños, las imágenes para esos iconos se extraen de un objeto de la clase wxImageList. Esta clase permite manejar diferentes tipos de imágenes, iconos o mapas de bits, todos ellos de las mismas dimensiones.
En lugar de especificar una imagen para cada ítem del control, se especifica el índice de la imagen dentro del objeto wxImageList asociado a él.
Hay que crear una lista de imágenes para cada tamaño de iconos, esto es, una lista de imágenes para la vista de iconos grandes y otra para la vista de iconos pequeños. Esta última lista también se usa en la vista de reporte y el modo lista.
Para crear una lista de imágenes, empezaremos por crear un objeto de esa clase, indicando las dimensiones de cada imagen individual, y opcionalmente si queremos usar una máscara y el número inicial de imágenes.
Posteriormente podemos añadir tantas imágenes como sea necesario. Las imágenes pueden ser mapas de bits procedentes de cualquier fichero de imagen (bmp, gif, png, xpm, etc), o del fichero de recursos.
El origen de las imágenes depende de nuestras preferencias. Por ejemplo, los ficheros de recursos son más habituales en aplicaciones Windows. Usar ficheros externos tiene sus ventajas e inconvenientes, ya que permite a usuarios modificar las imágenes que se usarán en la aplicación cambiando el contenido de esos ficheros. Una opción interesante es usar ficheros xpm, ya que esas imágenes se almacenan como un array que se integra con el código de la aplicación, sin necesidad de usar ficheros de recursos o ficheros externos.
Si la imagen a insertar contiene canal alfa o información sobre transparencia, el parámetro de máscara se ignora y se usa la información disponible.
wxBitmap bm; wxImageList* listaImagenGrande=new wxImageList(48, 48); bm.LoadFile("semaforo_verde_g.png", wxBITMAP_TYPE_PNG); // Desde un fichero externo listaImagenGrande->Add(bm); listaImagenGrande->Add(wxBITMAP_PNG(amarillogrande)); // Desde el fichero de recursos listaImagenGrande->Add(wxBITMAP_PNG(rojogrande));
Una ver disponemos de una lista de imágenes deberemos asignarla al control para que pueda usarla. Hay dos formas de asignar una lista de imágenes a un control de lista. Mediante el método AssignImageList se le da la propiedad de la lista al control, de modo que cuando el control es destruido, la lista de imágenes también lo es. Por otra parte, el método SetImageList, asocia la lista de imágenes con el control, pero no se la da en propiedad, de modo que tendremos que destruir la lista explícitamente cuando ya no sea necesaria. Esta segunda forma es preferible si la misma lista de imágenes es compartida por varios controles.
En principio tendremos que asignar tres listas de imágenes a cada control de lista: la de iconos grandes, la de iconos pequeños y la de estado, pero esta última aún no está implementada, de modo que al menos de momento no nos preocuparemos de ella.
Ambos métodos requieren dos parámetros: un puntero a la lista de imágenes y un segundo parámetro que indica qué rol tendrá esa lista en el control. Los valores para este segundo parámetro serán wxIMAGE_LIST_NORMAL, wxIMAGE_LIST_SMALL o wxIMAGE_LIST_STATE, para la lista de iconos grandes, iconos pequeños o iconos de estado, respectivamente.
listCtrl->AssignImageList(listaImagenGrande, wxIMAGE_LIST_NORMAL); // o: listCtrl->SetImageList(listaImagenGrande, wxIMAGE_LIST_NORMAL);
Columnas
Cada control wxListCtrl contiene un control de cabecera integrado, aunque sólo será visible en el modo reporte.
Esta cabecera estará dividida en una o más columnas, cada una de las cuales define una de las características de los ítems que contiene el control de lista.
Además, la cabecera permitirá al usuario, dependiendo del modo en que definamos cada columna, reordenar las columnas, ordenar los ítems según el orden de los valores de esa columna, mostrar u ocultar columnas, etc.
Aunque las columnas sólo son mostradas en la vista de reporte, deberemos añadir al menos una, y normalmente más, para que tenga sentido usar este tipo de controles en lugar de un control de lista básico.
Para añadir una columna se usa el método InsertColumn o AppendColumn.
La única diferencia es que InsertColumn permite especificar la posición de la columna en su primer argumento, mientras que AppendColumn la añade siempre al final.
Los otros tres parámetros indican el texto, la posición del texto en la columna (izquierda, derecha o centrado), y la anchura de la columna, que puede ser wxLIST_AUTOSIZE para que se calcule automáticamente, siendo estos dos últimos opcionales. Si no se indican se ajustará el texto a la izquierda y la anchura se calculará automáticamente.
Además, InsertColumn tiene una sobrecarga que usa como segundo argumento una referencia a un objeto wxListItem, que permite especificar la anchura, formato y texto, y además el índice de una imagen de la lista de imágenes de iconos pequeños.
listCtrl->InsertColumn(0, _T("Autor")); wxListItem listitem; listitem.SetAlign(wxLIST_FORMAT_CENTRE); listitem.SetImage(2); listitem.SetWidth(wxLIST_AUTOSIZE ); listitem.SetText(_T("Título")); listCtrl->InsertColumn(1, listitem); listCtrl->AppendColumn(_T("Fecha"));
Añadir ítems a la lista
Para añadir ítems al control usaremos una de las sobrecargas del método InsertItem. Las tres primeras sobrecargas corresponden a formas simplificadas para insertar un ítem con un texto, con una imagen o con ambas.
La cuarta sobrecarga permite definir más parámetros, como un dato de ítem, o el estado del ítem.
Para añadir los subítems se usa una de las sobrecargas del método SetItem. La primera sobrecarga permite especificar el índice del ítem, la columna, el texto y el índice de la imagen. La segunda usa una referencia a un objeto wxListItem.
Es importante que el control esté en modo reporte cuando se añada información sobre los subítems, de otro modo se mostrarán mensajes de aviso, aunque la información se añadirá igualmente.
listCtrl->InsertItem(1, _T("Nombre"), 1); listitem.Clear(); listitem.SetId(1); listitem.SetColumn(1); listitem.SetText(_T("titulo")); listCtrl->SetItem(listitem); listCtrl->SetItem(1, 2, _T("fecha"), 2);
Cada vez que se inserta un ítem en el control se genera un evento EVT_LIST_INSERT_ITEM. No será habitual, pero si decidimos capturar y procesar esos eventos, nuestro programa podrá realizar ciertas acciones cada vez de que un ítem sea añadidol.
BEGIN_EVENT_TABLE(wx018Frame, wxFrame) ... EVT_LIST_INSERT_ITEM(idListCtrl, OnInsertItem) ... END_EVENT_TABLE() ... void wx018Frame::OnInsertItem(wxListEvent& event) { wxString cad; if(event.GetId()==idListCtrl) { cad.Printf(_T("Item insertado: #%ld"), event.GetIndex()); wxMessageBox(cad, _T("ListCtrl")); } }
Selección de ítems
Por defecto, los controles wxListCtrl permiten seleccionar varios ítems. Esto se puede modificar para que sólo pueda seleccionarse un ítem, especificando el estilo wxLC_SINGLE_SEL al crear el control, o modificando el estilo posteriormente mediante el método SetWindowStyleFlag.
listCtrl = new wxListCtrl(this, idListCtrl, wxDefaultPosition, wxDefaultSize, wxLC_REPORT); ... listCtrl->SetWindowStyleFlag(wxLC_SINGLE_SEL | wxLC_REPORT);
Además, cada vez que se seleccione un ítem se genera un evento EVT_LIST_ITEM_SELECTED, y cada vez que se deseleccione un ítem, se genera un evento EVT_LIST_ITEM_DESELECTED.
BEGIN_EVENT_TABLE(wx018Frame, wxFrame) ... EVT_LIST_ITEM_SELECTED(idListCtrl, OnItemSelected) ... END_EVENT_TABLE() ... void wx018Frame::OnItemSelected(wxListEvent& event) { wxString cad; if(event.GetId()==idListCtrl) { cad.Printf(_T("Item seleccionado: #%ld"), event.GetIndex()); wxMessageBox(cad, _T("ListCtrl")); } }
También se generan eventos al activar un ítem haciendo doble clic sobre él, EVT_LIST_ITEM_ACTIVATED, o cuando adquiere el foco, EVT_LIST_ITEM_FOCUSED.
Si lo que queremos es recuperar información sobre un ítem concreto tendremos que usar el método GetItem. Tendremos que pasar como parámetro una referencia a un objeto wxListItem, que deberemos inicializar adecuadamente para indicar qué ítem y cuáles de sus propiedades queremos recuperar.
Por ejemplo, si queremos recuperar la etiqueta del segundo subítem del tercer ítem, tendremos que iniciar el objeto así:
wxListItem item; item.SetId(2); // Tercer ítem, al primero le corresponde el valor 0 item.SetColumn(2); // Segundo subítem, el valor 0 corresponde al ítem item.SetMask(wxLIST_MASK_TEXT); // Recuperar el texto listCtrl->GetItem(item); // Obtener la información wxMessageBox(item.GetText(), "Etiqueta"); // Mostrar el texto
Para saber si un ítem está seleccionado además de activar la máscara correspondiente al estado, wxLIST_MASK_STATE, tendremos que activar una segunda máscara para indicar cuáles de los estados queremos recuperar:
wxListItem item; item.SetId(2); // Tercer ítem, al primero le corresponde el valor 0 item.SetMask(wxLIST_MASK_STATE); // Recuperar los estados item.SetStateMask(wxLIST_STATE_SELECTED); // Recuperar el estado de selección listCtrl->GetItem(item); // Obtener la información if(item.GetState() == wxLIST_STATE_SELECTED) wxMessageBox("Seleccionado", "Estado"); else wxMessageBox("No seleccionado", "Estado");
Como los estados son banderas de bits, si estamos recuperando varios estados a la vez tendremos que verificar cada uno usando operadores binarios AND:
wxListItem item; item.SetId(2); // Tercer ítem, al primero le corresponde el valor 0 item.SetMask(wxLIST_MASK_STATE); // Recuperar los estados item.SetStateMask(wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); // Recuperar el estado de selección y foco listCtrl->GetItem(item); // Obtener la información if((item.GetState() & wxLIST_STATE_SELECTED) == wxLIST_STATE_SELECTED) wxMessageBox("Seleccionado", "Estado"); else wxMessageBox("No seleccionado", "Estado");
Por supuesto, se pueden recuperar más datos correspondientes a un ítem combinando las banderas de máscara correspondientes.
Cuando sólo queramos recuperar ciertos ítems, y sobre todo si el control tiene muchos ítems, hay mejores formas de recuperar aquellos que cumplan determinadas condiciones.
Para ello podemos usar el método GetNextItem, que nos permite localizar un ítem con un estado concreto. Para ello indicaremos en el primer parámetro el ítem de inicio de la búsqueda, o -1 para buscar desde el principio. El segundo parámetro siempre será wxLIST_NEXT_ALL, ya que el resto de valores sólo está soportado en Windows, y el tercer parámetro será la combinación de bits de estado que estamos buscando.
El valor de retorno será el índice del ítem encontrado o -1 si no se encontró ninguno.
void listctrl1::OnOk(wxCommandEvent& event) { long item=-1; data.selecionado.clear(); do { item = listCtrl-&GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if(item!=-1) data.selecionado.push_back(item); } while(item!=-1); EndModal(wxID_OK); }
Si necesitamos saber cuántos ítems hay seleccionados en un momento determinado, podemos usar el método GetSelectedItemCount.
Por último, para establecer el estado de un ítem se usa el método SetItemState. No será necesario establecer las máscaras, ya que se activan automáticamente al invocar este método:
item.SetId(data.selecionado[i]); // Opcional: // item.SetMask(wxLIST_MASK_STATE); // item.SetStateMask(wxLIST_STATE_SELECTED); item.SetState(wxLIST_STATE_SELECTED); listCtrl->SetItem(item);
Dato de ítem
Cada ítem puede tener asociado un dato de cliente. Se trata de un valor de tipo wxUIntPtr que se puede usar como un entero long long sin signo o, mediante un casting, como un puntero a un objeto.
Estos datos de cliente se usan para asociar cada ítem con un objeto o valor, como por ejemplo un índice en un array o un identificador en una base de datos, en la aplicación.
Usar estos datos de cliente será muy útil en tareas como ordenar los ítems según diferentes valores de subítems, o modificar los datos asociados con un ítem del control.
Para asignar un dato a un ítem podemos usar los métodos SetItemData o SetItemPtrData. En ambos casos el primer parámetro será la posición del ítem y el segundo parámetro un valor long para SetItemData y un valor wxUIntPtr para SetItemPtrData.
También podemos usar la estructura wxListItem para asociar el dato de cliente cuando se crea el ítem, llamando a su método SetData, que está sobrecargado para usar un parámetro de tipo long o un puntero void.
Para recuperar el dato de cliente de un ítem usaremos el método GetItemData, o bien recuperaremos los datos del ítem en un objeto wxListItem, usando el método GetItem, y posteriormente usando el método GetData sobre ese objeto.
i = 2; listitem.Clear(); listitem.SetId(i); listitem.SetColumn(0); listitem.SetImage(biblioteca[i].tipo); listitem.SetText(biblioteca[i].autor); listitem.SetData(biblioteca[i].clave); listCtrl->InsertItem(listitem); listCtrl->SetItem(i, 1, biblioteca[i].titulo); listCtrl->SetItem(i, 2, biblioteca[i].fecha);
Marcas de chequeo
Como en otros controles de lista que hemos visto anteriormente, en estos controles también es posible añadir una casilla de chequeo a cada ítem. Para ello tendremos que usar el método EnableCheckBoxes para que esas marcas estén disponibles, o para que no lo estén.
Podemos activar la marca de chequeo de un ítem mediante el método CheckItem, indicando el índice del item y un valor booleano que indique si se debe activar (true) o desactivar (false).
Para recuperar el estado de la marca para un ítem concreto disponemos del método IsItemChecked, o podemos usar las notificaciones que se envía cuando se activa una marca, EVT_LIST_ITEM_CHECKED, y cuando se desactiva EVT_LIST_ITEM_UNCHECKED.
El método GetItemCount nos devuelve el número de ítems que contiene el control.
void listctrl2::OnOk(wxCommandEvent& event) { wxListItem item; data.marcado.clear(); for(int i=0; i<listCtrl->GetItemCount(); i++) { if(listCtrl->IsItemChecked(i)) data.marcado.push_back(i); } EndModal(wxID_OK); }
A veces puede resultar útil averiguar si un control listCtrl tiene activada las marcas de chequeo, ya que no se trata de un estilo, tendremos que recurrir al método HasCheckBoxes.
Mostrar y ocultar columnas
No existen métodos específicos para ocultar columnas o mostrar aquellas que estuviesen ocultas. Pero siempre podemos ocultarlas haciendo que su anchura sea nula, usando el método SetColumnWidth. Previamente podemos obtener la anchura actual de la columna usando GetColumnWidth, para poder restaurarla posteriormente, si fuese necesario:
void listctrl2::OnChecklist(wxCommandEvent& event) { int indice=event.GetInt(); bool estado = checkListBox->IsChecked(indice); if(estado) { listCtrl->SetColumnWidth(indice, ancho[indice]); } else { ancho[indice] = listCtrl->GetColumnWidth(indice); listCtrl->SetColumnWidth(indice, 0); } }
Borrar columnas
También es posible borrar columnas completas, mediante el método DeleteColumn, o borrar todas las columnas, usando DeleteAllColumns.
Ordenar columnas
Se puede cambiar el orden de las columnas usando el ratón para arrastrarlas a la posición deseada. Aunque existen métodos para obtener el orden actual o modificarlo, sólo están disponibles para Windows, y por compatibilidad no los usaremos.
Incluso después de reordenar las columnas, los valores de los índices de las columnas no cambian, la columna insertada en una posición, conservará su índice aunque se arrastre a una posición diferente.
Se emiten diferentes notificaciones para informar de que una operación de arrastre de columna ha comenzado EVT_LIST_COL_BEGIN_DRAG, está en curso EVT_LIST_COL_DRAGGING, o ha finalizado EVT_LIST_COL_END_DRAG. Esto nos permite que la aplicación impida que ciertas columnas ocupen determinadas posiciones, o impedir el reordenamiento de columnas.
Ordenar ítems
Una de las funcionalidades de este tipo de controles es que es posible ordenar los ítems según los valores de cada columna, de forma ascendente o descendente. Pero, por supuesto, esto hay que programarlo.
La forma intuitiva de indicar que queremos ordenar los datos es pulsar con el ratón sobre la columna según la cual queremos ordenar. La primera vez se ordenará en orden ascendente y si se vuelve a pulsar, se ordenará por orden descendente.
Para detectar esas pulsaciones disponemos de dos notificaciones: EVT_LIST_COL_CLICK, que se envía cuando se pulsa el botón izquierdo del ratón sobre una columna y EVT_LIST_COL_RIGHT_CLICK, que se envía cuando se pulsa el botón derecho.
En la estructura wxListEvent que se envía como parámetro a la función que procesa estas notificaciones se incluye la información sobre la columna sobre la que se pulsó el botón del ratón, y podemos recuperar ese valor mediante el método GetColumn:
wxBEGIN_EVENT_TABLE(listctrl3, wxDialog) ... EVT_LIST_COL_CLICK(idListCtrl, listctrl3::OnListColClick) ... wxEND_EVENT_TABLE() ... void listctrl3::OnRadioButton(wxCommandEvent& event) { switch(event.GetSelection()) { ...
Para cada columna que queramos ordenar tendremos que crear una función de retrollamada de comparación con el siguiente prototipo:
int wxCALLBACK Compara(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData);
Esta función no puede ser miembro de una clase, debe ser una función "normal".
Su funcionamiento es similar a las funciones de comparación de la función ANSI qsort. Es decir, para ordenar ascendentemente debe devolver un valor mayor que cero su item1 es mayor que item2, un valor menor de cero si item1 es menor que item2 o cero si son iguales. Para el orden descendente los signos son los contrarios, debe devolver un valor menor que cero su item1 es mayor que item2, un valor mayor de cero si item1 es menor que item2.
Los valores de item1 e item2 son los datos de cliente asociados a cada ítem, y sortData es cualquier valor que queramos pasar a la función de comparación.
Hay muchas formas de enfocar el funcionamiento de estas funciones. Por ejemplo, podemos asignar como datos de cliente punteros a los objetos que se usaron para crear cada ítem. Esto no siempre funcionará, por ejemplo, si los datos asociados a cada ítem son miembros de una clase es posible que la función de comparación no pueda acceder a ellos.
En el ejemplo que ilustra este capítulo se usa una referencia a una estructura de datos que contiene todos los ítems del control, además de otros datos que almacenan los ítems seleccionados, y a los que hemos añadido valores que indican el orden de cada columna.
struct repuesto { long clave; int tipo; wxString nombre; int stock; int consumido; }; class datosRepuestos { public: std::vector<repuesto> almacen; std::vector<long> marcado; int ordenNombre; int ordenStock; int ordenConsumido; };
Los valores de orden, como ordenNombre u ordenStock pueden ser 1 ó -1 para indicar orden ascendente o descendente, respectivamente.
Para ordenar los ítems hay que invocar el método SortItems, pasando como parámetros la función de comparación y el valor que se pasará a esa función como sortData.
Como dato de cliente usaremos el índice en el vector de almacen de cada ítem y en sortData pasaremos un puntero al objeto datosRepuestos.
Para indicar qué columna y en qué orden se han ordenado los ítems añadiremos un indicador de orden, usando el método ShowSortIndicator, pasando como parámetro el índice de la columna y un valor booleano, true para orden ascendente y false para orden descendente:
void listctrl3::OnListColClick(wxListEvent& event) { switch(event.GetColumn()) { case 0: listCtrl->SortItems(ComparaNombres, reinterpret_cast<wxIntPtr>(&data)); listCtrl->ShowSortIndicator(0, data.ordenNombre>(0); data.ordenNombre *= -1; break; case 1: listCtrl->SortItems(ComparaStocks, reinterpret_cast<wxIntPtr>(&data)); listCtrl->ShowSortIndicator(1, data.ordenStock>(0); data.ordenStock *= -1; break; case 2: listCtrl->SortItems(ComparaConsumidos, reinterpret_cast<wxIntPtr>(&data)); listCtrl->ShowSortIndicator(2, data.ordenConsumido>(0); data.ordenConsumido *= -1; break; } }
Esta forma de resolver el ordenamiento de ítems facilita mucho el modo de implementar las funciones de comparación:
int wxCALLBACK ComparaNombres(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData) { wxString n1 = reinterpret_cast<datosRepuestos*>((sortData)->(almacen[item1].nombre; wxString n2 = reinterpret_cast<datosRepuestos*>((sortData)->(almacen[item2].nombre; int orden = reinterpret_cast<datosRepuestos*>((sortData)->(ordenNombre; if(n1>n2) return 1*orden; if(n1<n2) return -1*orden; return 0; }
Pero esta es sólo una de las muchas formas en que se pueden usar estos parámetros para ordenar por columnas.
Otros métodos útiles relacionados con el ordenamiento de ítems son GetSortIndicator, que devuelve el índice de la columna que tiene el indicador de orden, 0 -1 si no lo tiene ninguna GetUpdatedAscendingSortIndicator, que devuelve el nuevo valor a utilizar para el indicador de ordenación, IsAscendingSortIndicator, que devuelve true si el indicador ordenación es ascendente y RemoveSortIndicator, que elimina el indicador de ordenamiento.
Eliminar ítems
Por supuesto, se pueden eliminar ítems del control. Mediante el método DeleteItem, eliminaremos el ítem con el índice que pasamos como parámetro.
También podemos eliminar todos los ítems, usando el método DeleteAllItems.
Y el método ClearAll elimina todos los ítems y todas las columnas.
El método DeleteItem genera además un evento EVT_LIST_DELETE_ITEM, que podemos procesar si nuestra aplicación tiene que realizar alguna acción.
Los métodos DeleteAllItems y ClearAll generan un evento EVT_LIST_DELETE_ALL_ITEMS, en lugar de generar un evento para cada ítem eliminado.
Modificar ítems
Es posible editar el contenido de un ítem, para ello hay que crear el control con el estilo wxLC_EDIT_LABELS, y sólo será posible editar la etiqueta de de la columna con índice cero, y sólo en la vista de reporte.
Para iniciar la edición hay que pulsar dos veces sobre la etiqueta del ítem que queremos editar. No se trata de un doble clic, sino de dos clicks independientes. El primer clic selecciona el ítem y el segundo activa un control de edición que se inicia con el texto actual del ítem.
Se enviará una notificación EVT_LIST_BEGIN_LABEL_EDIT al iniciar la edición, antes de que se cree el control de edición. Esto le da una oportunidad a la aplicación de vetar la edición.
Cuando se termina la edición, ya sea porque el usuario ha hecho clic sobre cualquier punto del control de lista o porque ha pulsado enter, se enviará una notificación EVT_LIST_END_LABEL_EDIT. Esto permitirá a la aplicación validar el nuevo valor introducido y actualizar los datos asociados al ítem. Si el usuario pulsa escape se anula la edición y no se envía la notificación.
Del el objeto wxListEvent enviado con la notificación podremos extraer el índice del ítem que se está editando, mediante el método GetIndex, y el nuevo texto introducido en el control de edición mediante el método GetText:
wxBEGIN_EVENT_TABLE(listctrl4, wxDialog) ... EVT_LIST_END_LABEL_EDIT(idListCtrl, listctrl4::OnEndEditLabel) ... wxEND_EVENT_TABLE() ... void listctrl4::OnEndEditLabel(wxListEvent& event) { wxString cad = event.GetText(); cad = event.GetLabel(); data.almacen[event.GetIndex()].nombre = event.GetText(); }
En este ejemplo los cambios no se pueden descartar si por ejemplo el control pertenece a un diálogo y el usuario lo cierra con "cancelar".
Podemos activar la edición desde el programa usando el método EditLabel, lo que también generará un evento EVT_LIST_BEGIN_LABEL_EDIT.
Localizar ítems
Es posible localizar ítems según diferentes condiciones. Para ello se usa el método FindItem, que tiene varias sobrecargas.
En todas las sobrecargas el primer parámetro es el índice del ítem a partir del cual empieza la búsqueda. El valor -1 inicia la búsqueda desde el primer ítem.
El segundo parámetro puede ser:
Drag & drop
Las operaciones de drag & drop tienen cierta complicación y quedan un poco fuera del tema de este capítulo, por lo que las trataremos en un capítulo posterior. Por ahora sólo mencionar que existen dos notificaciones para tratar este tipo de operaciones en relación con controles wxListCtrl: EVT_LIST_BEGIN_DRAG, que se envía cuando el usuario pulsa el botón izquierdo sobre un ítem y mueve el ratón sin soltar el botón; EVT_LIST_BEGIN_RDRAG, que funciona de forma similar, pero con el botón derecho del ratón.
Controles de lista virtuales
Cuando un control wxListCtrl debe contener muchos ítems es mucho más eficiente no añadirlos todos, sino sólo los que resulten visibles en cada momento.
Para ello, estos controles disponen del estilo wxLC_VIRTUAL, que sólo se puede usar junto con wxLC_REPORT., es decir, este modo sólo está disponible para la vista en reporte.
En este tipo de controles la aplicación requerirá la información de cada ítem cuando necesite mostrarlo, en lugar de añadir los items y conservar todas esa información como hemos hecho hasta ahora.
Por supuesto, esto requiere que la información esté disponible para que la aplicación pueda enviarla al control cuando éste la solicite.
Para usar este tipo de controles hay que crear una clase derivada que implemente los métodos OnGetItemText y OnGetItemImage. El primero será invocado automáticamente cada vez que se tenga que mostrar el texto de cualquiera de los ítems o subítems. El segundo cuando se tenga que mostrar una imagen, aunque sólo será invocado para la primera columna.
class miListCtrl : public wxListCtrl { public: miListCtrl() : wxListCtrl() {} miListCtrl(wxWindow * parent, wxWindowID id); ~miListCtrl(); wxString OnGetItemText(long item, long column) const; int OnGetItemImage(long item) const; private: ... };
El método OnGetItemText tiene dos parámetros. El primero es el índice del ítem y el segundo la columna. El método OnGetItemImage sólo recibe como parámetro el índice del ítem. Si no se requiere una imagen, debe retornar -1.
Es necesario indicar el número de ítems que contiene el control, para ello se usa el método SetItemCount.
Si se quieren añadir marcas de chequeo a un control de lista virtual también hay que definir el método OnGetItemIsChecked.
Otros métodos relacionados con controles wxListCtrl virtuales son:
Pulsaciones de tecla
Estos controles responden a pulsaciones de teclas, por ejemplo, las flechas arriba y abajo nos permiten desplazarnos ítem a ítem, RePág y AvPág avanzan y retroceden el número de ítems que son visibles, Inicio y Fin desplazan al primer y último ítem. El resto de teclas se pueden usar para hacer búsquedas de ítems.
Si respondemos al evento EVT_LIST_KEY_DOWN podremos hacer que nuestra aplicación procese las pulsaciones de tecla para completar o sustituir el comportamiento por defecto.
Otros métodos útiles
Otros método que pueden resultar útiles son:
Control ListView
wxListView es una clase similar a wxListCtrl, pero mucho más simple, y la documentación recomienda usarla siempre que sea posible.
Es una clase derivada de wxListView y dispone de los mismos estilos y eventos que ella.
Esta clase dispone además de algunos métodos propios. Por ejemplo, GetFirstSelected devuelve el índice del primer ítem seleccionado, o -1 si no hay ninguno seleccionado.
El método GetNextSelected devuelve el siguiente ítem seleccionado al pasado como parámetro. GetFirstSelected equivale a GetNextSelected(-1):
void listview::OnOk(wxCommandEvent& event) { long item=-1; data.marcado.clear(); //item = listView->GetFirstSelected(); // No necesario en este caso //if(item!=-1) data.marcado.push_back(item); do { item = listView->GetNextSelected(item); if(item!=-1) data.marcado.push_back(item); } while(item!=-1); EndModal(wxID_OK); }
Otros métodos útiles propios son:
En la práctica, en todos los ejemplos en los que hemos usado wxListCtrl se puede usar wxListView.
Ejemplo 18
Para este ejemplo se tiene que usar la librería SQLite.
Windows:
Nombre | Fichero | Fecha | Tamaño | Contador | Descarga |
---|---|---|---|---|---|
Ejemplo 18 | wx018.zip | 2025-04-19 | 206069 bytes | 21 |
Linux:
Nombre | Fichero | Fecha | Tamaño | Contador | Descarga |
---|---|---|---|---|---|
Ejemplo 18 | wx018.tar.gz | 2025-04-19 | 193438 bytes | 3 |