9 Controles combobox

Ejemplo de combobox
Ejemplo de combobox

Un combobox es una combinación de un control de edición y un control listbox o choice.

La idea es que el control recuerde entradas previas para el dato que queremos leer, de modo que el usuario pueda usar la lista para usar un valor conocido, o introducir un nuevo valor si no está en la lista, o incluso aunque esté.

En una aplicación en la que un dato probablemente tenga un conjunto de valores finito, pero no predecible, podemos facilitar la vida del usuario recordando, si no todas, al menos los últimos valores que ha introducido el usuario o mejor, los que ha introducido con mayor frecuencia.

Por ejemplo, para una aplicación de mensajería, en un momento determinado el usuario debe introducir el nombre de una población. Dependiendo de dónde esté el usuario, o de la procedencia de los datos que esté procesando, probablemente haya un conjunto de nombres de poblaciones que tenga que introducir más frecuentemente, pero ese conjunto será, en principio, diferente para cada usuario.

Es decir, si el usuario está situado en la población A, es más probable que tenga que enviar paquetes a poblaciones cercanas a A, tanto más cuanto más habitantes tengan esas poblaciones. Otro usuario situado en la población B tendrá otro conjunto de poblaciones frecuentes diferente.

En este caso es interesante que para cada usuario la aplicación mantenga un historial personalizado con los nombres de las poblaciones que probablemente más usa.

Por supuesto, eso implica almacenar ese historial entre ejecuciones sucesivas de la aplicación usando algún tipo de base de datos más o menos sofisticada, pero ese es otro problema.

Estilos

Aunque este control admite algunos estilos, sólo algunos serán útiles. Por ejemplo, los que afectan al aspecto gráfico, como wxCB_SIMPLE o wxCB_DROPDOWN no están disponibles en todas las plataformas. El primero usa una lista del tipo listbox, que se muestra siempre, pero sólo está disponible para Windows. El segundo tampoco está disponible en todas las plataformas, y no he detectado ninguna diferencia al usarlo en Windows.

El estilo wxCB_READONLY convierte, en la práctica, el control en un control wxChoice, es decir, no permite al usuario introducir valores que no estén en la lista.

El estilo wxCB_SORT ordena los elementos de la lista alfabéticamente.

Y el estilo wxTE_PROCESS_ENTER hace que el control genere un evento wxEVT_TEXT_ENTER, que permite que la aplicación procese las pulsaciones de la tecla enter.

Eventos

Con los eventos pasa algo similar. Algunos como wxEVT_COMBOBOX_DROPDOWN y wxEVT_COMBOBOX_CLOSEUP, que se envían cuando la lista de despliega o se contrae, sólo están disponibles en algunas plataformas. Además, su utilidad es limitada.

El evento wxEVT_COMBOBOX se envía cuando el usuario selecciona una de las opciones de la lista.

wxEVT_TEXT se envía cuando el contenido de la parte editable se modifica por el usuario.

Y wxEVT_TEXT_ENTER cuando el usuario pulsa la tecla enter, y el control tiene el estilo wxTE_PROCESS_ENTER.

Añadir controles combobox

El constructor difiere un poco de la norma general.

Los dos primeros parámetros son, como siempre, la ventana padre y el identificador.

El tercero es una cadena con el valor que se mostrará en la parte del control de edición de texto. El valor por defecto wxEmptyString, es una cadena vacía.

El cuarto y quinto parámetro corresponden a la posición y tamaño.

A continuación se puede añadir una lista de cadenas con las opciones. Hay dos posibilidades, usar un entero con la cantidad de opciones seguido de un array C de cadenas wxString, o bien usar un único parámetro de tipo wxArrayString. Por supuesto, podemos indicar una lista vacía y añadir las opciones más tarde usando los métodos de la clase wxItemContainer, como hacíamos con los controles listbox.

Los tres últimos parámetros son el estilo, un validador y el nombre del control.

Es muy probable que en estos casos los datos que pasemos al control incluyan una cadena, para el valor inicial y el de retorno, y una lista de opciones:

class datosComboBox
{
    public:
        datosComboBox() {}
        datosComboBox(const wxString& valor, const wxArrayString& a) : opcion(valor), lista(a) {}

    //private:
        wxString opcion;
        wxArrayString lista;
};

Validador

Un validador para un combobox es prácticamente idéntico que para un control de edición, ya que la lista se inicializa o bien desde el constructor del control o desde el constructor de su ventana padre, y el valor de retorno se toma desde la parte del control de edición.

class cbValidator : public wxValidator {
public:
    cbValidator(wxString *v = nullptr);
    virtual wxObject* Clone() const { return new cbValidator(*this); }
    virtual bool TransferFromWindow();
    virtual bool TransferToWindow();
    virtual bool Validate(wxWindow * parent);
private:
    wxString* m_valor;
    DECLARE_DYNAMIC_CLASS( cbValidator )
    DECLARE_EVENT_TABLE()
};

La implementación tampoco tiene muchas novedades, salvo que el método TransferToWindow no tiene que hacer nada, ya que si el texto en la parte del control de edición está en la lista, ese elemento será marcado como seleccionado automáticamente:

IMPLEMENT_DYNAMIC_CLASS( cbValidator, wxValidator )
BEGIN_EVENT_TABLE( cbValidator, wxValidator )
END_EVENT_TABLE()

cbValidator::cbValidator(wxString *v) : wxValidator(), m_valor(v) {}

bool cbValidator::TransferFromWindow()
{
    try {
        wxComboBox *lb = dynamic_cast<wxComboBox*>(m_validatorWindow);
        *m_valor = lb->GetValue();
    }
    catch (...) {
        wxFAIL_MSG( _T("cbValidator sólo funciona con wxComboBox"));
        return false;
    }
    return true;
}

bool cbValidator::TransferToWindow()
{
    return true;
}

bool cbValidator::Validate(wxWindow* parent)
{
    return true;
}

Almacenar respuestas

A la hora de almacenar opciones previamente introducidas tenemos varias opciones, pero siempre es conveniente limitar el número de valores en la lista, para que el control siga siendo útil para el usuario y manejable por la aplicación.

Supongamos que limitamos el número de elementos en la lista a diez. Podemos aplicar varios criterios a la hora de añadir un elemento nuevo cuando eso implique que haya que eliminar uno de los existentes.

Una alternativa es eliminar siempre la opción que más tiempo lleva sin usarse. Para esto siempre trataremos el array de opciones como una lista FIFO, o sea, una cola, de modo que el primero en entrar sea el primero en salir. En nuestro ejemplo, si tenemos diez elementos en la lista, y la opción seleccinada no está en la lista, eliminaremos el último y añadiremos el nuevo en la primera posición. Además, si la opción estaba en la lista, la borraremos de su posición actual y la añadiremos en la primera posición.

void wx009Frame::OnComboBox(wxCommandEvent& event)
{
    combobox *dlg = new combobox(this, dataCombobox);
    dlg->ShowModal();
    if(dataCombobox.opcion!=wxEmptyString) {
        if(wxNOT_FOUND == dataCombobox.lista.Index(dataCombobox.opcion)) {
            dataCombobox.lista.Insert(dataCombobox.opcion, 0);
            if(dataCombobox.lista.GetCount() > 10) {
                dataCombobox.lista.Remove(dataCombobox.lista.Last());
            }
        } else {
            dataCombobox.lista.Remove(dataCombobox.opcion);
            dataCombobox.lista.Insert(dataCombobox.opcion, 0);
        }
    }
}

Otra posible alternativa sería almacenar más opciones, por ejemplo cincuenta, junto con un contador de usos de cada opción. Mostraríamos en el control las diez más usadas.

Ejemplo de bitmapcombobox
Ejemplo de bitmapcombobox

Combobox con bitmaps

Disponemos de una variante de de combobox que permite añadir un mapa de bits a cada opción.

En principio, todo lo discho para el control wxComboBox es válido para wxBitmapComboBox. Las diferencias son muy pocas, por ejemplo, sólo dispone de los estipo wxCB_READONLY, wxCB_SORT y wxTE_PROCESS_ENTERU, y según la documentación, sólo emite los eventos EVT_COMBOBOX, EVT_TEXT y EVT_TEXT_ENTER.

Además, para poder usar estos controles es necesario incluir el fichero de cabecera bmpcbox.h.

Si no añadimos mapas de bits, el control se comporta exactamente igual que wxComboBox.

Para añadir esos mapas de bits no podemos hacerlo directamente desde el constructor. Tenemos tres opciones:

  1. Usar uno de los métodos Append, dependiendo de si queremos añadir datos de cliente o no. Estos métodos tienen como primer parámetro el texto de la etiqueta asociado a la opción y como segundo parámetro una referencia constante a un objeto wxBitmap.
  2. Usar uno de los métodos Insert, que funcionan igual que los anteriores, salvo que en el tercer parámetro podemos indicar la posición en que queremos insertar la opción.
  3. Finalmente, podemos usar el método SetItemBitmap. En este caso el primer parámetro es el índice de la opción a la que se asigna el mapa de bits, y el segundo parámetro es una referencia constante a un objeto de la clase wxBitmapBundle. Este método permite asignar mapas de bits a opciones que ya estén en el control, por lo que podremos añadir las opciones usando el constructor.

Por ejemplo, usando Append:

    m_comboBox = new wxBitmapComboBox(this, idBitmapComboBox, data.opcion, wxDefaultPosition, wxSize(200, wxDefaultCoord), 
        0, nullptr, wxCB_DROPDOWN, bcbValidator(&data.opcion));
    for(size_t i=0; i<data.lista.GetCount(); i++) {
        switch(data.color[i]) {
            case 0: m_comboBox->Append(data.lista[i], wxBITMAP_PNG(bulletverde16)); break;
            case 1: m_comboBox->Append(data.lista[i], wxBITMAP_PNG(bulletamarillo16)); break;
            case 2: m_comboBox->Append(data.lista[i], wxBITMAP_PNG(bulletazul16)); break;
            case 3: m_comboBox->Append(data.lista[i], wxBITMAP_PNG(bulletrojo16)); break;
        }
    }

O usando SetItemBitmap:

    m_comboBox = new wxBitmapComboBox(this, idBitmapComboBox, data.opcion, wxDefaultPosition, wxSize(200, wxDefaultCoord), 
        data.lista, wxCB_DROPDOWN, bcbValidator(&data.opcion));
    std::vector<wxBitmapBundle> bmp;
    bmp.push_back(wxBitmapBundle(wxBITMAP_PNG(bulletverde16)));
    bmp.push_back(wxBitmapBundle(wxBITMAP_PNG(bulletamarillo16)));
    bmp.push_back(wxBitmapBundle(wxBITMAP_PNG(bulletazul16)));
    bmp.push_back(wxBitmapBundle(wxBITMAP_PNG(bulletrojo16)));
    for(size_t i=0; i<data.lista.GetCount(); i++) {
        m_comboBox->SetItemBitmap(i, bmp[data.color[i]]);
    }

Ejemplo 9

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 9 wx009.zip 2024-12-02 10350 bytes 5