16 Controles de cabecera

Ejemplo de control de cabecera

Los controles de cabecera no se suelen usar directamente, pero forman parte de otros controles como wxGrid, wxListCtrl, wxListView o wxDataViewCtrl.

Estos controles se basan en la clase abstracta wxHeaderCtrl no puede ser usada directamente y es necesario crear clases heredadas que implementen al menos los constructores y el método GetColumn. Para los ejemplos que ilustran el uso de estos controles usaremos la clase wxHeaderCtrlSimple, que sí se puede usar directamente.

Estas clases proporcionan soporte para reordenar las columnas, ya sea configurando explícitamente el orden de las columnas y llamando a SetColumnsOrder() o arrastrando las columnas interactivamente, si está habilitado especificando el estilo wxHD_ALLOW_REORDER.

También permite la visualización de los iconos en la cabecera, en lugar de etiquetas de texto, y que no tienen nada que ver con los gráficos que se añaden para indicar el orden de los elementos bajo la cabecera.

Estilos

El estilo wxHD_ALLOW_REORDER permite al usuario modificar el orden de las columnas pulsando con el botón izquierdo y arrastrándolas. Es el estilo por defecto, y equivale a wxHD_DEFAULT_STYLE.

Cuando se especifica el estilo wxHD_ALLOW_HIDE se mostrará un menú cuando el usuario haga clic derecho que permite ocultar y mostrar columnas. Las columnas siempre se pueden mostrar u ocultar mediante código.

El estilo wxHD_BITMAP_ON_RIGHT, si la columna tiene asignada una imagen, la muestra a la derecha del texto. Por defecto se muestra a la izquierda.

Crear control de cabecera

El constructor de cualquier clase para crear controles de cabecera requiere los siguientes parámetros:

  • Ventana padre.
  • Identificador.
  • Posición.
  • Tamaño.
  • Estilos.
  • Nombre.

Por ejemplo:

    wxHeaderCtrlSimple* headerCtrl = new wxHeaderCtrlSimple(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 
        wxHD_DEFAULT_STYLE | wxHD_ALLOW_HIDE);

Columnas

Un control de cabecera sin columnas no sirve de mucho. Las clases base para crear objetos de columnas de cabecera son wxHeaderColumn y wxSettableHeaderColumn. Se trata de clases abstractas, de modo que no se pueden usar directamente, sino sólo como base para crear clases propias.

La primera sólo contiene métodos para crear columnas que no pueden ser modificadas, es decir, sólo incorpora los métodos para recuperar información sobre la columna, como su etiqueta, visibilidad, orden, dimensiones, etc.

La segunda además incorpora métodos para modificar esas propiedades de las columnas.

Si queremos añadir columnas deberemos crear una clase derivada de de una de ellas, o bien usar la clase wxHeaderColumnSimple, o incluso objetos de clases derivadas de esta última.

Si decidimos crear nuestras propias clases derivadas de wxHeaderColumn o wxSettableHeaderColumn, tendremos que crear los constructores y el método Create con los siguientes prototipos, además del constructor por defecto:

    wxHeaderColumn(const wxString& title,
                   int width = wxCOL_WIDTH_DEFAULT,
                   wxAlignment align = wxALIGN_NOT,
                   int flags = wxCOL_DEFAULT_FLAGS);
    wxHeaderColumn(const wxBitmap &bitmap,
                   int width = wxCOL_WIDTH_DEFAULT,
                   wxAlignment align = wxALIGN_CENTER,
                   int flags = wxCOL_DEFAULT_FLAGS);

El constructor para la clase wxHeaderColumnSimple requiere los siguientes parámetros:

  • Título
  • Anchura.
  • Alineamiento del texto del título, uno de los valores wxAlignment.
  • Banderas.

Para la anchura se puede especificar una anchura en pixels, o uno de los valores wxCOL_WIDTH_DEFAULT, el valor por defecto o wxCOL_WIDTH_AUTOSIZE, se calcula la anchura automáticamente. Si no se han definido métodos para establecer estos dos valores será necesario especificar una medida concreta.

Los valores para el alineamiento del texto pueden ser wxALIGN_LEFT, wxALIGN_RIGHT o wxALIGN_CENTER.

Los valores para las banderas indican ciertas propiedades de cada cabecera, mediante uno o varios valores del enumerado anónimo que tiene los siguientes valores:

enum
{
    // column can be resized (included in default flags)
    wxCOL_RESIZABLE   = 1,
    // column can be clicked to toggle the sort order by its contents
    wxCOL_SORTABLE    = 2,
    // column can be dragged to change its order (included in default)
    wxCOL_REORDERABLE = 4,
    // column is not shown at all
    wxCOL_HIDDEN      = 8,
    // default flags for wxHeaderColumn ctor
    wxCOL_DEFAULT_FLAGS = wxCOL_RESIZABLE | wxCOL_REORDERABLE
};

Por ejemplo:

    headerCtrl->AppendColumn(wxHeaderColumnSimple(_T("Autor"), FromDIP(100)));
    headerCtrl->AppendColumn(wxHeaderColumnSimple(_T("Título"), FromDIP(100), wxALIGN_LEFT, wxCOL_HIDDEN));
    headerCtrl->AppendColumn(wxHeaderColumnSimple(_T("Año"), FromDIP(60)));

Si no se especifican banderas, estas se calculan a partir de los estilos del control de cabecera al que se pertenecen.

Nota: El método FromDIP de la clase wxWindow, se usa para convertir los valores de píxel independientes de DPI al valor en píxeles apropiado para el conjunto de herramientas actual.

Reordenar y ocultar columnas

Como ya hemos comentado más arriba, si se especifica el estilo wxHD_ALLOW_HIDE se desplegará un menú emergente cuando el usuario haga clic derecho sobre alguna de las cabeceras de columna. Este menú permite ocultar o mostrar columnas.

Sin embargo, ninguna de las clases predefinidas, ni tampoco la clase wxHeaderCtrlSimple, define el método UpdateColumnVisibility, que es necesario para completar la operación de mostrar u ocultar columnas. Por defecto este método provoca un assert y no hace nada.

Algo similar ocurre si se especifica el estilo wxHD_ALLOW_REORDER, que también es el valor por defecto. En este caso el menú emergente es más completo y también permite reordenar las cabeceras. En este caso tampoco está definido el método UpdateColumnsOrder, que también provoca un assert. Esto sólo se produce al intentar ordenar las cabeceras desde el menú, y no cuando se arrastran con el ratón.

Por lo tanto, si queremos que nuestro control responda adecuadamente a este menú deberemos crear una clase propia que redefina estos dos métodos. Por ejemplo:

class MiHeaderCtrl: public wxHeaderCtrlSimple {
public:
    MiHeaderCtrl() : wxHeaderCtrlSimple() {}
    MiHeaderCtrl(wxWindow *parent,
               wxWindowID winid = wxID_ANY,
               const wxPoint& pos = wxDefaultPosition,
               const wxSize& size = wxDefaultSize,
               long style = wxHD_DEFAULT_STYLE,
               const wxString& name = wxASCII_STR(wxHeaderCtrlNameStr)) :
            wxHeaderCtrlSimple(parent, winid, pos, size, style, name) {}
    void UpdateColumnVisibility(unsigned int idx, bool show) {
        unsigned int c=0;
        for(unsigned int i=0; i<GetColumnCount(); i++) {
            if(GetColumn(i).IsShown()) c++;
        }
        if(c>1 || show) ShowColumn(idx, show);
    }
    void UpdateColumnsOrder(const wxArrayInt& order) {
        SetColumnsOrder(order);
    }
};

En este caso, cuando se ocultan columnas, hemos implementado un método que impide que se oculten todas. Esto es necesario, al menos en este ejemplo, ya que si no hay columnas visibles no será posible desplegar el menú contextual, de modo que no habrá forma de volver a mostrar ninguna columna.

Mapas de bits

Antes vimos que se pueden insertar columnas con una etiqueta de texto o con un mapa de bits:

#include "arrowup.xpm"
...
    headerCtrl->AppendColumn(wxHeaderColumnSimple(arrowup_xpm, FromDIP(60)));

Ahora bien, no es recomendable usar columnas que sólo contengan un mapa de bits, ya que no son compatibles con los menús emergentes que permiten ocultar y ordenar columnas, puesto que al no disponer de una etiqueta de texto, la creación del menú fallará. De modo que siempre usaremos una etiqueta, y opcionalmente podemos añadir un mapa de bits.

Indicadores de orden

En los controles de cabecera hay que tener clara la diferencia entre los conceptos de "sort" y "order", dado en que en español no tenemos traducciones diferentes para estas palabras, esto puede provocar algunos problemas a la hora de diferenciar estos conceptos.

Cuando hablamos de "order" nos referimos al orden en que se muestran las columnas dentro del control de cabecera.

Por contra, con "sort" nos referimos al orden de las filas de datos o elementos para los que el control actúa como cabecera.

Como el control de cabecera aislado no contiene filas de datos, las funciones para ordenar esas filas usando como clave el valor de determinada o determinadas columnas, no se incluyen como parte de la definición de las clases de cabecera o columna, y las veremos con mucha más claridad futuros capítulos.

Sin embargo, el control wxHeaderCtrlSimple dispone de métodos para mostrar u ocultar las marcas que indican si el orden (sort) de una columna es ascendente o descendente.

Cuando se añade la bandera wxCOL_SORTABLE a una columna es posible mostrar esas marcas, aunque lo cierto es que, al menos en la versión actual, se pueden mostrar aunque no se use esa bandera, y probablemente sólo se use en clases derivadas de ellas o en otros controles que incorporen controles de cabecera.

La clase wxHeaderColumnSimple dispone de varios métodos relacionados con el ordenamiento de filas, aunque su definición en la mayoría es trivial, es decir, no hacen nada:

  • IsSortKey: (trivial) indica si una columna se usa como clave de ordenamiento de filas.
  • IsSortOrderAscending: (trivial) indica si el orden es ascendente (true), o descendente (false).

La clase wxHeaderCtrlSimple también dispone de algunos métodos relacionados con el ordenamiento de filas:

  • RemoveSortIndicator: oculta la marca que indica el orden de filas de la columna que la esté mostrando actualmente, si la hay.
  • ShowSortIndicator: muestra la marca de orden de filas de la columna y con el orden indicados, true corresponde al orden descendente y false al ascendente. Sólo una de las columnas puede mostrar la marca, al mostrarla en una, automáticamente se oculta en la que se mostrase previamente.

Si en una columna optamos por mostrar la marca de orden, el mapa de bits asociado, si existiera, dejará de mostrarse, ya que ocupará su lugar.

Eventos

Los controles de cabecera pueden responder a varios tipos de eventos relacionados con pulsaciones de botones del ratón. Hay eventos para detectar pulsaciones simples o doble-clics en el control, para cualquiera de los tres botones del ratón: izquierdo, derecho o central:

  • EVT_HEADER_CLICK(id, func): clic izquierdo sobre el encabezado de una columna.
  • EVT_HEADER_RIGHT_CLICK(id, func): clic derecho sobre el encabezado de una columna.
  • EVT_HEADER_MIDDLE_CLICK(id, func): clic central sobre el encabezado de una columna.
  • EVT_HEADER_DCLICK(id, func): doble-clic izquierdo sobre el encabezado de una columna.
  • EVT_HEADER_RIGHT_DCLICK(id, func): doble-clic derecho sobre el encabezado de una columna.
  • Se ha hecho doble clic derecho sobre el encabezado de una columna.
  • EVT_HEADER_MIDDLE_DCLICK(id, func): doble-clic central sobre el encabezado de una columna.
  • EVT_HEADER_SEPARATOR_DCLICK(id, func): doble-clic izquierdo sobre un separador entre columnas.

Podemos usar esos eventos a nuestra discreción en nuestras aplicaciones, aunque algunos tienen un uso tradicional que conviene respetar para que el comportamiento del control sea intuitivo.

Por ejemplo, el doble clic sobre un separador entre columnas se suele usar para ajustar la anchura de la columna automáticamente en función de la anchura del texto de los datos de la columna. Para ello el control proporciona el método UpdateColumnWidthToFit() que facilita su implementación).

Otro grupo de eventos de se relacionan con el redimensionado de columnas:

  • EVT_HEADER_BEGIN_RESIZE(id, func): se emite cuando el usuario ha empezado a arrastrar el separador a la derecha de una columna. Esto permite, por ejemplo, impedir que el usuario pueda redimensionar la anchura de una columna.
  • EVT_HEADER_RESIZING(id, func): se emite mientras el usuario está arrastrando el separador a la derecha de una columna.
  • EVT_HEADER_END_RESIZE(id, func): se emite cuando la operación de arrastre del separado ha concluido porque el usuario ha liberado el botón izquierdo del ratón. El valor actual de la anchura se puede obtener desde wxHeaderCtrlEvent::GetWidth(), y esto permite a la aplicación tener la última palabra en cuando a la anchura, o bien respetando esa anchura, o estableciendo una nueva según algún criterio de diseño, por ejemplo, para impedir anchuras demasiado pequeñas o grandes.

El tercer grupo de eventos está relacionado con el reordenamiento de columnas:

  • EVT_HEADER_BEGIN_REORDER(id, func): se emite cuando el usuario ha comenzado a arrastrar la columna. Esto permite a la aplicación evitar que ciertas columnas puedan ser reordenadas.
  • EVT_HEADER_END_REORDER(id, func): se emite cuando el usuario ha soltado la columna en su nueva ubicación. Si se maneja, la nueva posición de la cabecera será wxHeaderCtrlEvent::GetNewOrder().

Por último, el evento EVT_HEADER_DRAGGING_CANCELLED(id, func) se emite si cualquiera de las operaciones de arrastre anterior fue cancelada, bien porque se pulsó la tecla Esc, o porque se perdió la captura del ratón.

Por lo que parece, para la clase wxHeaderCtrlSimple no es posible responder a estos eventos, o al menos yo no he sido capaz de hacerlo. En cualquier caso, dada la poca utilidad de usar estos controles de forma aislada, tampoco parece necesario.

Ejemplo 16

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 16 wx016.zip 2025-02-15 10888 bytes 6