el Guille, la Web del Visual Basic, C#, .NET y más...

Obtener todos los controles de un cuadro de diálogo sin conocer su ID de recurso

 

Autor: RFOG (MVP de Visual C++)
Publicado: 12/Abr/2008
Actualizado: 12/Abr/2008

Cómo obtener todos los controles que haya en un cuadro de diálogo sin conocer ni sus HWND de ventana ni sus ID de recurso.




 

Introducción

Por aclamación popular –y porque me ha picado la curiosidad-, vamos a explicar cómo obtener todos los controles que haya en un cuadro de diálogo sin conocer ni sus HWND de ventana ni sus ID de recurso.

Al tajo

Así dicho a bote pronto parece toda una hazaña digna de Hércules, pero lo cierto es que resulta mucho más sencillo de lo que a primera vista parece si se cae en ello. Para que nadie se haga ilusiones, he de añadir que de esta forma no es posible controlar una aplicación que no sea la misma que ejecuta este código, o al menos no sin antes emplear otras técnicas de inyección de código que no voy a contar pero que están disponibles para aquellos que lean la documentación pertinente y adecuada. Y no me preguntéis: no lo voy a contar.

Para esta demostración he creado una aplicación MFC mediante el asistente de Visual Studio, que se puede bajar de aquí. Es una aplicación cuya ventana principal es un cuadro de diálogo, y vamos a ignorar todos los asistentes y a modificar a mano todo el código.

En primer lugar, abrimos la plantilla del cuadro de diálogo y añadimos un botón que vamos a llamar “Cambiar”, tal y como se muestra en la imagen.

MFC02_01.png

Como no nos interesan los ID, nos da igual qué ha hecho el diseñador. El siguiente paso consiste en asociar cada uno de los cuatro controles a una clase, por lo que en primer lugar tenemos que crearnos cuatro variables del tipo adecuado en el fichero cabecera de la clase:

CButton btnOk;
CButton btnCancel;
CButton btnCambiar;
CStatic label;

Ahora nos vamos al método OnInitDialog() y añadimos la línea

EnumChildWindows(m_hWnd,(WNDENUMPROC)staticEnumChildProc,(LPARAM)this);

Que es el núcleo de todo el asunto que nos ocupa. Claramente estamos utilizando una función de Win32 que mediante otra función de callback nos irá enumerando todas las ventanas hijas. m_hWnd es la variable miembro de la clase diálogo que contiene el Handle de ventana. El parámetro siguiente es la función de callback que irá siendo llamada ante cada ventana hija que se encuentre. Luego explico el tercer parámetro, que es algo que podemos pasarle al enumerador de ventanas.

 Debemos ahora crearnos una función estática dentro de la clase con la firma adecuada para que el código de EnumChildWindows() pueda llamarla. Dicha función ha de ser estática porque Win32 no entiende de punteros this y otras zarandajas y, o usamos una función global, o una estática dentro de la clase.

Pero esto tiene un problema, ya que desde dicha función no podremos llamar a ningún método miembro ni tocar ninguna variable miembro, y es aquí donde surge el tercer parámetro de EnumChikdWindows(): le estamos pasando un puntero a la propia clase, para que la función estática pueda ejecutar el { return ((CmfcDialogNoIDDlg *)lParam)->EnumChildProc(hwnd); }tancias de la misma clase sin preocuparnos de esa fea función estática, y es una de varias formas posibles de hacerlo. Veamos el código, que he añadido en el fichero cabecera:

static BOOL CALLBACK staticEnumChildProc(HWND hwnd,LPARAM lParam)
{
	return ((CmfcDialogNoIDDlg *)lParam)->EnumChildProc(hwnd);
}

Y finalmente el código que asocia cada control a su clase correspondiente:

BOOL EnumChildProc(HWND hwnd)
{
    TCHAR szTitle[255];
    if(::GetWindowText(hwnd,szTitle,255)!=0)
    {
        if(_tcscmp(szTitle,TEXT("OK"))==0)
            btnOk.Attach(hwnd);
        else if(_tcscmp(szTitle,TEXT("Cancel"))==0)
            btnCancel.Attach(hwnd);
        else if(_tcscmp(szTitle,TEXT("TODO: Place dialog controls here."))==0)
            label.Attach(hwnd);
        else if(_tcscmp(szTitle,TEXT("Cambiar"))==0)
            btnCambiar.Attach(hwnd);
    }
    return TRUE;
}

El código está claro: ante cada handle de ventana que recibimos, buscamos su título y dependiendo de cuál sea, se asocia al control adecuado.

Ahora tan sólo nos falta crear un evento de respuesta ante el mensaje WM_DESTROY y desasociar los controles para evitar que el programa proteste. Mediante el uso de la ventana de propiedades del cuadro de diálogo, creamos un nuevo evento y lo rellenamos con el código

void CmfcDialogNoIDDlg::OnDestroy()
{
    CDialog::OnDestroy();

    // TODO: Add your message handler code here
    btnCancel.Detach();
    btnOk.Detach();
    label.Detach();
    btnCambiar.Detach();
}

Si ahora ponemos un punto de interrupción dentro de EnumChildProc() veremos cómo se van asociando todos los controles a cada uno de los objetos adecuados.

Ejecutar eventos

Tan sólo nos queda poder ejecutar un código para el botón de “Cambiar” y que cambie los textos de todos los controles. Para ello tenemos que bucear un rato dentro del código fuente de MFC para encontrar la forma, ya que casi como siempre, la documentación está, pero hay que saber dónde encontrarla, así que lo más sencillo en este caso es mirar el código fuente y pensar un poco.

En un cuadro de diálogo los mensajes de los controles hijos se suelen controlar a nivel del padre, es decir, es la propia clase del diálogo, la que, mediante los mapas de mensajes, se encarga de reaccionar a cualquier evento de los hijos.

Una mirada al método WindowProc() de la clase CWnd nos da la pista: cuando se trata de un mensaje WM_COMMAND (lo generados por los controles hijos, entre otros), se controlan mediante el método virtual OnCommand(). Ahora sí, ya lo tenemos claro, debemos sobrescribir este método virtual y reaccionar con nuestro código. Un vistazo a la documentación nos lo aclara: lParam es el handle de ventana mientras que parte de wParam es el ID del control. Ya sabemos cómo reaccionar, por lo que añadimos el siguiente código al fichero cabecera:

virtual BOOL OnCommand(WPARAM wParam,LPARAM lParam)
{
    if((HWND)lParam==btnCambiar)
    {
        btnOk.SetWindowText(TEXT("¡Era OK!"));
        }
    return CDialog::OnCommand(wParam,lParam);
}

Comprueben el código de ejemplo. Y hemos terminado.


Ir al índice de los artículos de RFOG en el sitio del Guille




La fecha/hora en el servidor es: 22/12/2024 7:22:19

La fecha actual GMT (UTC) es: 

©Guillermo 'guille' Som, 1996-2024