4 Sizers y validadores

Antes de seguir con otros controles hablaremos un poco de dos herramientas que nos van a facilitar mucho la tarea de diseñar y utilizar cuadros de diálogo y marcos que contengan controles: los sizers y los validadores.

En este capítulo comentaremos algunos detalles sobre las clases que encapsulan los sizers y validadores, pero los veremos en funcionamiento a medida que vayamos hablando de nuevos controles.

Sizers

En ejemplos anteriores hemos insertado controles estáticos y botones directamente, calculando de forma manual sus coordenadas y dimensiones. Sin embargo, wxWidgets dispone de varias clases de sizers para que esto no sea necesario, distribuyendo los controles de forma automática a partir de ciertos parámetros.

Además, los sizers tienen otra ventaja importante, automatizan la asignación del foco mediante el clic izquierdo del ratón, algo que resulta más complicado si se prescinde de ellos.

En futuros capítulos veremos que toda esta tarea es más simple usando alguna herramienta de diseño de diálogos, como wxSmith. Sin embargo, estas herramientas crean código de forma automática, y considero que es mejor entender primero cómo funcionan los controles y los sizers antes de utilizarlas, ya que mezclar código generado automáticamente con el nuestro puede hacer que nuestros primeros programas sean más difíciles de entender.

Podemos considerar los sizers como cajas con diferentes compartimentos en los que insertaremos los controles.

El orden en que se crean los controles es importante a la hora de diseñar los diálogos, ya que la navegación usando la tecla tab seguirá ese orden, y no es agradable, y a menudo tampoco conveniente, que el foco vaya saltando de una parte a otra del diálogo en lugar de seguir un orden más lógico.

Pongamos por ejemplo que queremos crear un diálogo con el siguiente diseño:

Cuadro de diálogo
Cuadro de diálogo

Tenemos en la parte izquierda dos cuadros de edición de texto, con sus correspondientes etiquetas estáticas, y a la derecha dos botones: Aceptar y Cancelar.

El sizer más sencillo que podemos usar es wxBoxSizer, que consiste en una caja en la que podemos insertar controles bien en filas o en columnas. Los sizers pueden anidarse, así que además de controles también podemos insertar otros sizers.

Según esto, podemos diseñar nuestro diálogo mediante tres sizers de este tipo. El más externo agrupará los controles en una fila, y en su interior insertaremos dos sizers, cada uno de los cuales agrupará los controles en una columna.

En el sizer de la columna izquierda insertaremos los controles estáticos y los de edición. En el de la columna derecha los botones, y finalmente insertaremos estos dos sizers en el horizontal.

Para mejorar el resultado estéticamente nos interesará añadir un margen entre los bordes del diálogo y los controles, de este modo no parecerá que todo está amontonado. Para ello podemos hacer uso de un objeto wxSizerFlags y de sus métodos. Por ejemplo, el método Border() añade un margen alrededor del objeto que insertemos en el sizer.

Finalmente, añadimos el sizer principial al diálogo y ajustamos sus medidas. Para ello usaremos el método SetSizerAndFit.

En este ejemplo usamos tres objetos wxBoxSizer para obtener el resultado deseado:

    wxBoxSizer *boxHorizontal;
    wxBoxSizer *boxVertical1;
    wxBoxSizer *boxVertical2;

    boxHorizontal = new wxBoxSizer(wxHORIZONTAL);
    boxVertical1 = new wxBoxSizer(wxVERTICAL);
    boxVertical2 = new wxBoxSizer(wxVERTICAL);

    m_staticText1 = new wxStaticText(this, wxID_ANY, wxT("&Texto:"));
    boxVertical1->Add(m_staticText1);
    EdtTexto = new wxTextCtrl(this, idEdtText, wxEmptyString);
    boxVertical1->Add(EdtTexto);

    m_staticText1 = new wxStaticText(this, wxID_ANY, wxT("&Número:"));
    boxVertical1->Add(m_staticText1);
    EdtNumber = new wxTextCtrl(this, idEdtNumber, wxEmptyString);
    boxVertical1->Add(EdtNumber);

    BtnAbout = new wxButton(this, idBtnAbout, wxT("&Sobre"));
    boxVertical2->Add(BtnAbout);
    BtnAbout->SetDefault();

    BtnQuit = new wxButton(this, idBtnQuit, wxT("S&alir"));
    boxVertical2->Add(BtnQuit);
    this->SetEscapeId(idBtnQuit);

    boxHorizontal->Add(boxVertical1, wxSizerFlags().Border());
    boxHorizontal->Add(boxVertical2, wxSizerFlags().Border());

    SetSizerAndFit(boxHorizontal);

Existen varios tipos de sizers, y todos derivan directa o indirectamente de la clase abstracta wxSizer. Están, por una parte, el wxBoxSizer y sus derivadas, wxStaticBoxSizer, wxStdDialogButtonSizer y wxWrapSizer, y por otra wxGridSizer, y sus derivadas wxFlexGridSizer y wxGridBagSizer. Veremos a continuación las características y uso de cada una de ellas.

Se comentan algunos de sus métodos más interesantes, pero se recomienda consultar la documentación de referencia para conocer el resto.

wxSizer

Aunque se trate de una clase abstracta, que no se puede usar directamente para crear objetos, dispone de los métodos comunes al resto de clases sizer que derivan de ella, por lo que es importante conocer algunos de sus métodos:

  • El método Add, que está sobrecargado para admitir diferentes tipos de argumentos. Permite añadir espaciadores, sizers o ventanas, es decir, controles.
  • El método AddSpacer(), que es un atajo que permite añadir un espacio separador fijo.
  • El método AddStretchSpacer(), que es un atajo que permite añadir un espacio separador extensible. Estos espacios cambian su tamaño dependiendo del tamaño de la ventana, de modo que al redimensionar la ventana, se ajustan también.

Además, en todas las clases de sizers también disponemos de métodos para manipular elementos: extraerlos del sizer, obtener una lista, buscarlos, contarlos, insertarlos en una posición concreta, eliminarlos. O para mostrar u ocultar el sizer, etc.

wxBoxSizer

Los wxBoxSizer son la clase de sizer más simple, y permite disponer los controles u otros sizer en una única fila o columna. Como los sizer pueden anidarse, usando solo estos sizers podemos salir adelante en la mayoría de los casos.

Además de los métodos heredados o sobrescritos de la clase wxSizer, esta clase tiene algunos métodos propios, como por ejemplo:

  • El constructor wxBoxSizer(), que requiere un parámetro que define su orientación, wxVERTICAL o wxHORIZONTAL.
  • El método AddSpacer(), que es un atajo que permite añadir un espacio separador en la dirección del sizer.

wxStaticBoxSizer

La única diferencia entre un wxBoxSizer y un wxStaticBoxSizer es que éste último añade un control estático wxStaticBox alrededor de los controles que contiene.

Dispone de dos constructores. Uno se usa cuando se quiere usar una caja estática ya existente y el otro cuando queremos que el sizer cree su propia caja estática, para la que, opcionalmente, podremos indicar una etiqueta. En cualquier caso, el sizer es el propietario del control estático y se encargará de destruirlo cuando sea necesario.

wxStaticBoxSizer *staticBoxSizer = new wxStaticBoxSizer(wxHORIZONTAL, this, "&Etiqueta");

wxStdDialogButtonSizer

Se trata de un sizer del mismo tipo que los anteriores, pero orientado a colocar botones estándar de diálogos siguiendo el estándar de la plataforma para la que se compila la aplicación.

Con estos sizers no podemos elegir la orientación, que generalmente será horizontal, ya que en los diálogos el estándar se suelen colocar los botones horizontalmente en la parte inferior.

Este sizer, en las plataformas para las que está definido, puede modificar el texto y el orden de los botones para ajustarse al estándar.

Hay un par de diferencias importantes. Por una parte, los botones se insertan usando el método AddButton, y por otra, una vez insertados los botones hay que llamar al método Realize() para ajustar las posiciones y etiquetas de cada botón.

    wxStdDialogButtonSizer* boxVertical2 = new wxStdDialogButtonSizer();
    BtnAbout = new wxButton(this, wxID_HELP, wxT("&Sobre"));
    BtnQuit = new wxButton(this, wxID_CLOSE, wxT("S&alir"));
    boxVertical2->AddButton(BtnAbout);
    boxVertical2->AddButton(BtnQuit);
    boxVertical2->Realize();

La clase wxDialog dispone de tres métodos específicos para añadir un sizers para botones: CreateButtonSizer, CreateSeparatedSizer y para botones estándar CreateStdDialogButtonSizer que nos evitan mucho del código anterior.

    wxSizer* boxVertical2 = CreateStdDialogButtonSizer(wxHELP | wxCLOSE);

El inconveniente de este segundo método es que no podemos especificar los textos de las etiquetas de los botones, que por defecto están en inglés. En un capítulo posterior veremos cómo usar la clase wxLocale para cambiar el idioma de los mensajes y etiquetas.

wxWrapSizer

Los sizers wxWrapSizer funcionan como los anteriores, con algunas diferencias. Cuando no queda espacio para insertar controles en la dirección requerida, se añade una nueva fila o columna.

El tamaño total del sizer no depende exclusivamente de los elementos que contenga, de hecho, el tamaño obtenido casi nunca será el correcto, de modo que casi siempre tendremos que cambiar el tamaño de la ventana para que se muestren todos los elementos del sizer.

La distribución de los elementos dentro de un sizer de este tipo se ajustará cada vez que se redimensione la ventana.

Esto se ilustra en el programa de ejemplo de este capítulo. El diálogo principal de la aplicación consta de un sizer box horizontal, cuyo primer elemento es un control estático con un mapa de bits, y el segundo un sizer wrap con varios botones. Si no modificamos el tamaño de la ventana, la altura de la ventana estará impuesta por la altura del mapa de bits, y la segunda columna de botones no se mostrará. La anchura de la ventana depende de la anchura del mapa de bits y de la del botón más ancho de la primera columna.

Si usamos el método SetInitialSize, el tamaño de la ventana se ajustará para que todos los botones sean visibles en una única columna.

Se ha añadido un borde que nos permite modificar las dimensiones del diálogo, de modo que si disminuimos la altura y aumentamos la anchura podemos ver cómo los controles se distribuyen de modo que se añadan columnas cuando la altura no sea suficiente.

wxGridSizer

Un wxGridSizer dispone sus hijos en una cuadrícula en la que todas las casillas tienen el mismo tamaño: la anchura del hijo más ancho y la altura del más alto. Se puede establecer cuantas filas o columnas contiene la cuadrícula, y las separaciones entre las casillas.

Aunque dispone de varios constructores, solo se diferencian en los tipos de parámetros que aceptan, pero todos son equivalentes.

wxFlexGridSizer

Un wxFlexGridSizer, como en el caso anterior dispone a sus hijos en una tabla bidimensional con todos los campos de la tabla en una fila con la misma altura y todos los campos en una columna con la misma anchura, pero todas las filas o todas las columnas no son necesariamente de la misma altura o anchura.

wxGridBagSizer

Un wxGridBagSizer puede disponer elementos en una rejilla virtual como un wxFlexGridSizer pero además se permite el posicionamiento explícito de los elementos utilizando wxGBPosition, y los elementos pueden abarcar opcionalmente más de una fila y/o columna utilizando wxGBSpan.

Si consideramos la rejilla como un array de dos dimensiones, se pueden especificar las coordenadas de cada elemento mediante un objeto wxGBPosition, y las casillas que ocupará mediante un objeto wxGBSpan. Esto nos da mayor flexibilidad a la hora de insertar controles con tamaños diferentes en anchura o altura.

Esta clase redefine los métodos Add para poder especificar la posición y las casillas que debe ocupara cada elemento en la rejilla. Por lo demás, se usan igual que el resto de sizers.

Otras clases relacionadas

Cualquiera de los ítems insertados en un sizer puede ser manejado mediante un objeto de la clase wxSizerItem o con un objeto de la clase wxGBSizerItem en el caso de wxGridBagSizer.

Ejemplo 4

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 4 wx004.zip 2024-12-02 22953 bytes 6

Validadores

Muchos de los controles que veremos permiten especificar un objeto de la clase wxValidator, o mejor dicho, de una de las clases derivadas de ella en sus constructores. También es posible asignar un validador a un control de forma dinámica, después de construirlo.

Los validadores tienen varias funciones:

  • Transferir datos desde una variable C++ o almacenamiento propio hacia y desde un control. Es decir, establecer o recuperar el valor del control.
  • Validar datos en un control, y mostrar un mensaje de error apropiado. Esto nos permitirá establecer un dominio para los datos que el usuario puede introducir en un control, evitando entradas que no tengan sentido en nuestra aplicación.
  • Filtrar eventos (como pulsaciones de teclas), cambiando así el comportamiento del control asociado. De este modo podemos añadir macros, o personalizar el comportamiento de un control.

De este modo podemos, por ejemplo, hacer que un control de edición solo permita introducir números, o determinadas cadenas.

Clase wxValidator

Es habitual que cada familia de clases disponga de una o varias clases base que serán útiles cuando sea preciso aplicar polimorfismo.

En el caso de los validadores, la clase base es wxValidator. Esta es una clase abstracta que no podremos usar para crear nuestros controles.

Entre los métodos virtuales, que serán definidos en las clases derivadas, tenemos:

  • TransferToWindow: se encarga de transferir los datos desde la aplicación al control.
  • TransferFromWindow: se encarga de transferir los datos desde el control a la aplicación.
  • Validate: este es el método que se encarga de verificar si los datos en el control son válidos, y eventualmente impedir que se introduzcan datos inválidos.

Casi nunca tendremos que invocar estos tres métodos, ya que son utilizados por la ventana o diálogo al que pertenecen los controles automáticamente.

Dos métodos muy útiles son:

  • SuppressBellOnError: cuando el validador detecta datos no válidos por defecto emite un sonido. Este método sirve para evitar que eso ocurra, o volver a activar el sonido, si se usa el valor true como parámetro.
  • IsSilent: devuelve true si el sonido de error está activo.

Clase wxGenericValidator

La clase validadora más simple es wxGenericValidator, que de hecho, no dispone de métodos de validación de datos, solo dispone de la funcionalidad para transferir datos a y desde el control. Los podemos usar en controles estáticos y botones, aunque en esos casos no tienen demasiada utilidad.

Son útiles para establecer un valor inicial en controles y para recuperar los datos introducidos por el usuario cuando no sea necesario validarlos de ninguna forma concreta.

Dispone de varios constructores, para aplicar el validador a diferentes tipos de controles o diferentes tipos de datos. Podemos usar punteros a objetos wxString, bool, int, float, double, arrays de enteros, wxDateTime o wxFileName.

Clase wxNumValidator

wxNumValidator es una plantilla para la validación de números. Esta clase tampoco se usa directamente, en su lugar usaremos una e sus clases derivadas.

En cualquier caso, tanto esta clase como sus derivadas permiten establecer un rango de valores permitidos, de modo que solo sea posible introducir números entre un valor máximo y mínimo.

Para establecer ese rango disponemos de tres métodos:

  • SetMax: establece el valor máximo del rango.
  • SetMin: establece el valor mínimo del rango.
  • SetRange: establece los valores máximo y mínimo del rango.

También existen los métodos simétricos para recuperar ese rango: GetMax, GetMin y GetRange.

Existen dos clases derivadas de esta, una para valores enteros y otra para valores en coma flotante.

Clase wxIntegerValidator

La clase wxIntegerValidator también es una plantilla, derivada de wxNumValidator, que permite crear validadores para valores enteros de cualquier tipo, short, int, long y long long con o sin signo. Si no se especifica un rango, por defecto se establecerán los valores mínimo y máximo del tipo elegido.

Disponemos de dos constructores, uno que usa el rango por defecto y otro que puede establecer el rango directamente.

También hay que especificar el estilo mediante una combinación de banderas del tipo enumerado wxNumValidatorStyle.

long numero; // El valor debe estar entre -10000 y 10000 y se añadirán separadores de miles
wxIntegerValidator<long> val(&numero, -10000, 10000, wxNUM_VAL_THOUSANDS_SEPARATOR);

Clase wxFloatingPointValidator

La clase wxFloatingPointValidator también es una plantilla, derivada de wxNumValidator, que permite crear validadores para valores en coma flotante de tipo float o double. Si no se especifica un valor para la precisión se usará el valor por defecto, 6 para float y 15 para double.

También se puede establecer el número de dígitos después del punto decimal mediante el valor de precisión.

También disponemos de dos constructores, uno que usa el valor de precisión por defecto y otro que permite establecer esa precisión directamente.

También se puede establecer un valor para factor. Este valor se multiplica por valor a verificar antes de ser mostrado y se divide por él cuando se recupera su valor del control. Por defecto, el factor es 1, por lo que el valor real no se ve afectado por él, pero se puede establecer en, por ejemplo, 100, para mostrar el valor en porcentajes sin dejar de almacenarlo como valor absoluto.

double numero; // El valor estará entre -100 y 200, con tres valores decimales
wxFloatingPointValidator<double> val(3, &numero, wxNUM_VAL_NO_TRAILING_ZEROES);
val.SetRange(-100.0, 200.0);
// La precisión se puede asignar también: val.SetPrecision(3);

wxTextValidator

La clase wxTextValidator está diseñada para validar texto.

Además de las funcionalidades que poseen todos los validadores de transferir valores a y desde controles, este validador nos permite crear listas de caracteres y cadenas permitidas y prohibidas.

El constructor nos permite establecer una bandera de estilo mediante una o una combinación de banderas del tipo enumerado wxTextValidatorStyle, que establece el tipo de filtro, y que por defecto es wxFILTER_NONE, es decir, sin filtro. También podemos modificar el estilo después de haber creado el validador usando el método SetStyle.

Para añadir caracteres excluidos tendremos que especificar el estilo wxFILTER_EXCLUDE_CHAR_LIST y usar el método SetCharExcludes al que pasaremos como parámetro una cadena wxString con los caracteres a excluir, también se pueden añadir caracteres a los ya excluidos mediante el método AddCharExcludes(). Estos caracteres no podrán ser introducidos por el usuario.

De forma similar, para añadir caracteres permitidos tendremos que especificar el estilo wxFILTER_INCLUDE_CHAR_LIST y usar los métodos SetCharIncludes o AddCharIncludes. Hay que tener presente que la lista de caracteres excluidos tiene prioridad sobre la de incluidos.

Análogamente, para incluir o excluir cadenas hay que especificar el estilo wxFILTER_INCLUDE_LIST o wxFILTER_EXCLUDE_LIST y se usan los métodos SetIncludes o AddInclude() y SetExcludes o AddExclude(), respectivamente, usando como parámetro un array de cadenas mediante un objeto wxArrayString.

El usuario podrá introducir cadenas excluidas, pero se producirá un error de validación, y nos dará la oportunidad de corregir la entrada no válida.

El resto de estilos se pueden considerar como listas de inclusión de caracteres predeterminadas: ascii, dígitos, números, etc.

Ejemplo:

wxString texto("Valor inicial");
wxTextValidator* ValTexto = new wxTextValidator(wxFILTER_EXCLUDE_CHAR_LIST, &texto);
ValTexto->SetCharExcludes("xwz");

wxNumericPropertyValidator

El validador wxNumericPropertyValidator pretende ser una clase de validador más completa.

En el único constructor podemos especificar el tipo de número a validar mediante un parámetro del constructor que puede ser Signed, Unsigned o Float, y además se puede especificar la base de numeración, que por defecto es 10.

Por ejemplo:

wxNumericPropertyValidator* val1 = new wxNumericPropertyValidator(Signed, 2);
wxNumericPropertyValidator* val2 = new wxNumericPropertyValidator(Float);
wxNumericPropertyValidator* val3 = new wxNumericPropertyValidator(Unsigned, 16);

Validadores a medida

Imaginad que tenemos que capturar datos y asegurarnos de que son válidos antes de trabajar con ellos o almacenarlos. Por ejemplo, en el artículo sobre fórmulas de validación vimos que existen fórmulas para verificar si ciertos datos tienen el formato correcto o no: DNI, números de cuenta, números de tarjetas de crédito, etc.

A menudo es interesante no permitir abandonar un cuadro de edición con datos no válidos, informar al usuario de los errores y permitirle corregirlos.

Otras veces querremos que simplemente los datos sean transferidos a y desde nuestro diálogo a la estructura de datos donde los almacenamos.

Con wxWidgets podemos crear nuestros propios validadores para usarlos en nuestros programas. Para ello los derivaremos de la clase virtual wxValidator.

Como los diseñaremos a nuestra conveniencia, podemos hacer que sólo se encarguen de transferir los datos de nuestras variables al control y viceversa, de validar los datos que contiene el control o ambas.

Ciertos métodos, como TransferFromWindow y TransferToWindow se pueden anular, de modo que no realicen tareas de inicializar o leer los datos del control. O se pueden sobrescribir, para que lo hagan de la manera que nosotros queramos.

El método Validate también se puede anular, para que los datos se acepten incondicionalmente, o sobrescribir, para que nuestros diálogos puedan verificar que los datos son válidos.

Tendremos que crear el constructor, y el método Clone, que se usa en el constructor copia y en el operador de asignación.

Esta clase contiene un dato miembro no documentado, m_validatorWindow. Este dato contiene un puntero al control cuyos datos estamos validando, y necesitaremos usarlo si queremos validar o transferir datos a y desde el control.

Veamos cómo funciona esto con un ejemplo sencillo.

Supongamos que queremos un validador para un control de edición (que veremos en el siguiente capítulo), para que únicamente permita introducir valores pares y positivos.

Nuestra clase tendrá esta forma:

#include <wx/validate.h>

class paresValidator : public wxValidator {
public:
    paresValidator(int *n=nullptr);
    virtual wxObject* Clone() const { return new paresValidator(*this); }
    virtual bool TransferFromWindow();
    virtual bool TransferToWindow();
    virtual bool Validate(wxWindow * parent);
private:
    int *m_numeropar;
    DECLARE_DYNAMIC_CLASS( paresValidator )
    DECLARE_EVENT_TABLE()
};

Como en todos los validadores, tendremos que pasarle un puntero al dato que queremos validar, en este caso es un valor entero, así que usaremos un int*. Eso se aplica al constructor y al puntero privado m_numeropar. Ese puntero se usa para pasar el valor a editar desde su origen al control de edición y viceversa. Debido al modo en que funcionan estas clases, es importante que dispongan de un constructor por defecto, es decir, sin parámetros o con todos sus parámetros con valores por defecto, como es el caso del ejemplo.

Podemos prescindir del destructor, ya que nuestra clase no usa memoria dinámica.

El método Clone es necesario, ya que el programa invocará el constructor copia y posiblemente el operador de asignación, que hacen uso de este método. Es importante que este método tenga el modificador const. Se limita a hacer una copia del objeto, no se necesita obtener nueva memoria para el objeto apuntado por m_numeropar, ya que en el objeto original es un puntero a una dirección de memoria que contiene el dato a validar.

Sobrecargaremos los métodos que nos interesan, en este ejemplo, TransferFromWindow , TransferToWindow y Validate . Es importante mantener los prototipos con los mismos valores de retorno y parámetros, de otro modo no funcionarán correctamente.

Finalmente añadimos las macros DECLARE_DYNAMIC_CLASS y DECLARE_EVENT_TABLE, necesarias para poder crear objetos de esta clase dinámicamente y para que respondan a eventos.

Para la definición tenemos:

#include "paresvalidator.h"

IMPLEMENT_DYNAMIC_CLASS( paresValidator, wxValidator )
BEGIN_EVENT_TABLE( paresValidator, wxValidator )
END_EVENT_TABLE()

Usamos la macro IMPLEMENT_DYNAMIC_CLASS que se utiliza en un archivo de implementación de C++ para completar la declaración de una clase.

También definimos la tabla de eventos a los que debe responder, que en este ejemplo está vacía.

paresValidator::paresValidator(int *n) : wxValidator(), m_numeropar(n) {}

Nuestro constructor inicializa la clase base y el dato miembro con el puntero a los datos.

bool paresValidator::TransferFromWindow()
{
    wxString cad;
    try {
        wxTextCtrl *tc = dynamic_cast<wxTextCtrl*>(m_validatorWindow);
        cad = tc->GetValue();
        cad.ToInt(m_numeropar);
    }
    catch (...) {
        wxFAIL_MSG( _T("paresValidator sólo funciona con wxTextCtrl"));
        return false;
    }
    return true;
}

Para transferir el dato desde el control de edición al puntero que lo almacena intentaremos obtener un puntero de la clase wxTextCtrl a la ventana asociada con el validador. Si tiene éxito quiere decir que este validador está realmente asociado a un control de texto, y en consecuencia podremos obtener el valor que contiene, convertirlo a tipo entero, y actualizar el objeto apuntado por el puntero m_numeropar retornando el valor true.

Si fracasa se genera un assert de error y retorna con false.

bool paresValidator::TransferToWindow()
{
    wxString cad = wxEmptyString;
    try {
        wxTextCtrl *tc = dynamic_cast<wxTextCtrl*>(m_validatorWindow);
        cad << *m_numeropar;
        tc->SetValue(cad);
    }
    catch(...) {
        wxFAIL_MSG( _T("paresValidator sólo funciona con wxTextCtrl"));
        return false;
    }
    return true;
}

Para transferir el dato desde el puntero que lo almacena al control de edición actuaremos de forma similar. Intentaremos obtener un puntero de la clase wxTextCtrl a la ventana asociada con el validador. Si tiene éxito podremos convertir el valor entero a cadena y asignar esa cadena al control.

Si fracasa se genera un assert de error y retorna con false.

bool paresValidator::Validate(wxWindow* parent)
{
    if (!m_validatorWindow->IsEnabled()) return true;

    wxString cad;
    int v;
    bool retval;

    try {
        wxTextCtrl *tc = dynamic_cast<wxTextCtrl*>(m_validatorWindow);
        cad = tc->GetValue();
    }
    catch (...) {
        wxFAIL_MSG( _T("paresValidator sólo funciona con wxTextCtrl"));
        return false;
    }

    cad.ToInt(&v);
    retval = v>=0 && !(v%2); // Mayor de 0 y no impar
    if(!retval) {
        wxString errmsg = wxT("Se debe introducir un valor positivo par");
        wxMessageDialog dlg(parent,errmsg,wxT("Entrada incorrecta"),wxOK);
        dlg.ShowModal();
    }

    return retval;
}

Para validar el dato primero verificamos si está activada la validación, y si no es así regresamos con true, indicando el el dato es válido.

Después capturamos el contenido del control, igual que hicimos anteriormente.

Convertimos el contenido a un valor entero y verificamos si cumple las condiciones: mayor que cero y par.

Si no cumple las condiciones mostraremos un mensaje de error mediante un diálogo de mensaje.

Retornaremos true si el dato es válido o false si no lo es.

Para usar este validador en nuestro diálogo crearemos el control de edición usando ese validador:

m_textCtrl = new wxTextCtrl(this, idTextCtrl, wxEmptyString, wxDefaultPosition, 
    wxSize(200, wxDefaultCoord), 0, paresValidator(&data.enteropar));

Esto se encargará de añadir la cadena con el valor de data.enteropar al control.

Para recuperar y validar el dato tendremos que procesar el evento de pulsación del botón de "Aceptar".

void dialogo::OnOk(wxCommandEvent& event) {
    if ( Validate() && TransferDataFromWindow() ) EndModal(wxID_OK);
}

El método Validate de la clase base wxWindow invocará a los métodos Validate de cada validador asociado a un control. Si cualquiera de ellos fracasa el cuadro de diálogo no se cerrará y el usuario podrá corregir cualquier error.

El método TransferDataFromWindow de la clase base wxWindow intentará recuperar los datos desde todos los controles con validadores.

Si ambos métodos tienen éxito, el cuadro de diálogo se cerrará y los datos obtenidos serán válidos.

En próximos capítulos usaremos validadores a medida para otros controles que no disponen de ellos en wxWidgets.

Ejemplo 4b

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 4b wx004b.zip 2025-02-18 6144 bytes 6