Apunte de Visual C++
Por: Demian Panello demianpanello@yahoo.com.ar
Capítulo VI
Indice rápido del capítulo 6:
INTERCAMBIO Y VALIDACIÓN DE DATOS EN UN CUADRO DE DIÁLOGO:
Siempre que se asocien variables miembro a controles mediante ClassWizard se genera automáticamente un código para efectuar el intercambio de valores de las variables desde y hacia los controles. Este código se encuentra en la función DoDataExchange() del cuadro de diálogo. Esta función es responsable de transferir los datos en ambas direcciones, desde y hacia los controles del cuadro de diálogo.
El proceso de transferencia se denomina Data Exchange (Intercambio de datos) y se inicia mediante una llamada a la función UpdateData(). A UpdateData() se le puede pasar un parámetro Boolean, (TRUE o FALSE) denominado bSaveAndValidate. Se puede pasar o bien FALSE para dar valor a los controles a partir de las variables miembro, o bien TRUE para transferir a las variables miembro los valores actuales de los controles. UpdateData() es invocada automáticamente por la función OnInitDialog() de la clase CDialog y la función OnOK() del botón Aceptar que AppWizard pone por defecto en nuestras aplicaciones también llama de forma automática a UpdateData().
Cuando uno utiliza UpdateData() pasando TRUE en el parámetro bSaveAndValidate en cualquier función de cualquier control; se hace un llamado a DoDataExchange(). Si los datos eran válidos, UpdateData() valdrá TRUE caso contrario se emite el aviso oportuno para el usuario y UpdateData() valdrá FALSE.
Para ver esto cree un nuevo proyecto MFC basado en diálogos y llámelo ValidarDatos.
Objetivo del proyecto: La aplicación siguiente constará de 2 diálogos, uno es el que crea el AppWizard más otro que agregaremos. Del principal se llamará al nuevo formulario, allí habrá dos cuadros de edición donde se ingresará un Apellido y una edad. Al momento de presionar Aceptar se validará si la edad está entre 0 y 110 y si no es 30; de ser válida se pasan los parámetros a unas etiquetas (cuadro de texto estático) del diálogo principal.
Ya que conoce el objetivo del programa continuaremos con el diseño de las pantallas:
- ID= IDC_NOMBRE, CAPTION= en blanco y en STYLE tilde SUNKEN.
- ID=IDC_ANIOS y en STYLE tilde READ ONLY.
- Pulse con el derecho sobre IDC_NOMBRE y seleccione la opción ClassWizard del menú contextual. Luego elija la solapa Member Variables, marque IDC_NOMBRE y pulse el botón ADD VARIABLE. Especifique en el campo nombre m_strNombre, CATEGORY value y TYPE CString. Acepte todas al ventanas.
- Realice la misma operación para IDC_ANIOS pero como nombre de variable escriba m_nAnios, CATEGORY value y TYPE int.

Antes de escribir el código del botón "Llamar a otra ventana" agregaremos el nuevo diálogo.
Diseñemos ahora el diálogo DatosDlg.

Asóciele a los cuadros de edición variables:
Bien, ahora regresemos al diálogo principal para codificar el botón que llama a IDD_DATOS.
Vamos a llamar a nuestro nuevo diálogo de manera tal que por defecto los cuadros de edición tengan valores. Escriba en OnLlama:
void CValidarDatosDlg::OnLlama()
{
// TODO: Add your control notification handler code here
CDatosDlg dlgDatos(this); (1)
//antes de mostrar el formulario le asigno a las variables
//de los cuadros de texto valores iniciales
dlgDatos.m_strApellido = "Francescoli"; (2)
dlgDatos.m_Edad = 34; (3)
dlgDatos.DoModal (); (4)
}
Llamaremos al diálogo de forma Modal, por lo tanto creamos el objeto dlgDatos derivado de CDatosDlg (1), entonces antes de usar DoModal(), le asignamos a las variables miembro de los cuadros de edición valores por defecto (2) y (3). La transferencia de los valores desde la variables a los controles se realizará, en este caso, de forma automática cuando el flujo del programa pase por OnInitDialog() del diálogo que acaba de ser invocado.
Si prueba el programa, verá cuando llama al nuevo diálogo que los controles de edición muestran los valores por defecto.
Nos queda escribir el código que valide la edad y que pase los datos de los cuadros de edición del diálogo IDD_DATOS a los controles del principal, al presionar Aceptar.
Cuando se añaden cierto tipo de variables miembro a un cuadro de diálogo mediante ClassWizard, se especifica además del nombre, una categoría, un tipo y algunas validaciones, como por ejemplo para m_Edad se especificó que el valor mínimo sería 0 y el máximo 110. Estas validaciones generan código en la función DoDataExchange(), (del diálogo correspondiente, en este caso IDD_DATOS), con líneas como esta:
DDV_MinMaxInt(pDX, m_Edad, 0, 110);
Verá que esa línea está escrita en un color gris, pues nos permite saber que fueron generadas por ClassWizard.
Esta es una función DDV (función de validación de datos) propia de VC++ que en este caso en particular verifica que el contenido de la variable m_Edad se encuentre entre 0 y 110.
CÓMO CREAR FUNCIONES DE VALIDACIÓN PERSONALIZADAS:
Es posible crear nuestras propias funciones de validación personalizadas para comprobar casos especiales escribiendo una función del estilo DDV. Nuestra función debería admitir el puntero del objeto CDataExchange (pDX), la variable miembro que se desea validar y otros parámetros adicionales que consideremos necesarios.
La primera condición que tiene que verificar nuestra función es que el objeto CDataExchange sea guardar y validar (m_bSaveAndValidate); esto se hace comprobando que pDX->m_bSaveAndValidate sea TRUE, pues esto implica que ocurrió una llamada UpdateData(TRUE) que transfiere los datos de los controles a las variables. Luego deberíamos validar nuestra variable en sí, de acuerdo al criterio elegido.
Si es válida la variable, entonces la función puede retornar normalmente; en caso contrario será preciso visualizar un mensaje aclaratorio para el usuario y llamar a la función Fail() de DoDataExchange() para indicarle a la función UpdateData() que haya llamado que ha fracasado.
Bueno, en nuestro ejemplo queríamos comprobar que la edad esté entre 0 y 110 pero que no sea 30, entonces haga click con el botón derecho del mouse sobre la clase CdatosDlg y seleccione Add Member Function y en la ventana que aparece escriba en Function Type: void y en Function Declaration: ValidarEdad(CDataExchange *pDX, int& Edad), acepte y se presentará en pantalla la función. Allí escriba:
void CDatosDlg::ValidarEdad(CDataExchange *pDX, int& Edad)
{
//Esta función me valida la edad
//Si va a guardar los datos validando y Edad == 30...
if (pDX->m_bSaveAndValidate && Edad ==30)
{
AfxMessageBox("La edad debe estar entre 0 y 110 pero no igual a 30!");
pDX->Fail(); //Reseteo el UpdateData()
}
}
Falta hacer la llamada a esta función; que la ubicaremos en DoDataExchange() del diálogo IDD_DATOS. Allí escriba:
void CDatosDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CDatosDlg)
DDX_Text(pDX, IDC_APELLIDO, m_strApellido);
DDV_MaxChars(pDX, m_strApellido, 20);
DDX_Text(pDX, IDC_EDAD, m_Edad);
DDV_MinMaxInt(pDX, m_Edad, 0, 110);
//}}AFX_DATA_MAP
ValidarEdad(pDX, m_Edad); //Llamo a la función que valida la edad
}
Puede probar el programa y comprobará que cuando llama al diálogo IDD_DATOS por defecto trae en los cuadros de diálogos los valores iniciales que habíamos especificado Apellido=Francescoli y edad = 34. Además podrá verificar que si ingresa una edad fuera del rango 0 a 110 o igual a 30 mostrará el mensaje: "La edad debe estar entre 0 y 110 pero no igual a 30!".
Para terminar la aplicación debemos escribir el código que permita pasar los datos a los controles del formulario principal. Teóricamente sería asignarle a las variables de los controles del diálogo principal, el contenido de las variables de los controles del diálogo IDD_DATOS, luego que hayan sido validados. Bastaría agregar un else en el if de la función ValidarDatos(), pero el problema consiste en cómo hacer referencia a variables de otro diálogo.
La solución es pasarle un puntero de la clase del diálogo principal al constructor del diálogo IDD_DATOS, para que luego desde cualquier gestor de mensajes del diálogo llamado se pueda acceder a través del puntero a cualquier función o dato miembro del diálogo "llamador".
Parece un poco complejo, pero siga estos pasos:
CDatosDlg::CDatosDlg(CWnd* pParent /*=NULL*/)
: CDialog(CDatosDlg::IDD, pParent)
El parámetro pParent es un puntero a la clase CWnd (ventana) y efectivamente es una referencia a la ventana predecesora, pero lo que nos interesa a nosotros es pasar un puntero a CValidarDatosDlg entonces donde CWnd escribimos CValidarDatosDlg. Y además agregamos m_pPrincipal(pParent). El constructor quedaría:
CDatosDlg::CDatosDlg(CValidarDatosDlg* pParent /*=NULL*/)
: CDialog(CDatosDlg::IDD, pParent), m_pPrincipal(pParent)
(en azul lo modificado y añadido).
// CDatosDlg dialog
class CDatosDlg : public CDialog
{
// Construction
public:
void ValidarEdad(CDataExchange* pDX, int &Edad);
CdatosDlg(CWnd* pParent = NULL); // standard constructor
....
por razones de espacio se omite el resto del código.
Aquí hay que hacer la misma modificación que en la definición; donde dice CWnd* pParent escriba CValidarDatosDlg* y para que la clase datos reconozca la clase predecesora se agrega antes del comentario //CDatosDlg dialog la línea #include "ValidarDatosDlg.h" que permite incluir el archivo de cabecera que contiene la definición de la clase CValidarDatosDlg.
Resumiendo: