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

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.

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.

image

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 método miembro adecuado. De este modo incluso podremos tener varias instancias 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);
}

Está claro lo que hemos comentado, aunque el código esté un poco apretado. Lo que hacemos es tomar el parámetro lParam convirtiéndolo a un puntero a la clase que representa el cuadro de diálogo, y luego invocamos la función miembro no estática EnumChildProc(); de esta forma estamos retomando el puntero pasado a la clase a través de lParam (quizás en alguna entrada futura profundice en todo esto).

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!"));
        btnCancel.SetWindowText(TEXT("¡Era Cancel!"));
        label.SetWindowText(TEXT("¡He cambiado!"));
        return TRUE;
    }
    return CDialog::OnCommand(wParam,lParam);
}

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

Visual C++ 2008 Feature Pack

Dicen que lo prometido es deuda, y que las deudas deben pagarse… Y eso es lo que ha hecho Microsoft, por lo menos dentro del mundo C++.

En su momento se habló de que se iban a mejorar drásticamente las MFC una vez hubiera salido Visual Studio 2008… y así ha sido.

Hace un rato que lo han publicado en el blog del Visual Studio: Visual C++ 2008 Feature Pack en fase beta. La descarga se puede hacer desde aquí, y son apenas 300 meguillas de nada.

¿Qué trae esta actualización? Pues un enorme avance dentro de las MFC soportando el aspecto visual del propio Visual Studio y sus facilidades de acoplar ventanas junto con una preview de la TR1, que está a punto de ser estandarizada.

El autor de eso, como no quiere quedarse tirado, se ha bajado la actualización y ha jugado un rato. Como viene siendo habitual, en apenas media hora le han salido tres bugs, pero se los perdonamos porque no son graves y el producto está en fase beta.

El resultado de mis juegos son las dos imágenes anexas, que muestran lo que genera el asistente con el añadido de pintar “RFOG WAS HERE” (En la segunda imagen se ve cómo lo he hecho).

Bueno, eso es todo.

captura

captura1

C++/CLI: Bloqueos y punteros interiores

Bloqueos
El lector habitual de este blog debe estar ya acostumbrado a leer aquí sobre el montículo manejado y el hecho de que las referencias en .NET son punteros móviles, es decir, apuntan y siguen al objeto en cuestión a lo largo de todos sus movimientos dentro del citado montículo, ya que uno de los elementos más potentes del .NET es la compactación de objetos asignados y la recolección de los que se han liberado, ya sea de forma automática al salir de ámbito o bien mediante su marcado manual para liberación (uso de delete en C++/CLI y de Dispose() en C#).


Pues bien, en esta entrada vamos a hablar sobre cómo bloquear un elemento dentro de dicho montículo y cómo acceder mediante un puntero a su interior.


Una referencia a un objeto no es más que una variable almacenada en la pila y que en su interior contiene los datos suficientes y necesarios para que el sistema de ejecución del .NET pueda actualizar la posición que el propio objeto ocupa dentro del montículo manejado.


Por ejemplo, la sentencia



   1: Object ^o=gcnew Object();

reserva en la pila manejada y en el contexto actual un bloque de bytes con la etiqueta “o” que a su vez contiene un puntero a otro bloque de memoria situado en el montículo manejado, y que se corresponde a la representación física de lo que quiera que sea un Object.


Cuando ejecutamos la línea



   1: o->ToString();

el compilador implementa una serie de instrucciones que miran en “o” dónde está realmente el objeto en el montículo manejado y ejecutan el método ToString() sobre dicho objeto.


De igual forma, cuando el método o función en donde se ha declarado “o” termina su ejecución, “o” deja de existir, pero no así el bloque dentro del montículo manejado. Lo que en un lenguaje como C++ sería una fuga de memoria muy seria, en C++/CLI y dentro del universo .NET no es más que un paso perfectamente válido para que, al dejar huérfano lo que venía representado por “o”, quede marcado para su futura destrucción cuando se produzca una pasada del recolector de basura.


Supongamos ahora que seguimos con nuestro objeto dentro de ámbito y que se ha producido una pasada de compactación de memoria. ¿Qué ha ocurrido? Pues muy posiblemente, que el objeto situado en el montículo haya sido desplazado a otra parte, y a su vez se haya actualizado la estructura “o” guardada en la pila para que apunte a la nueva localización.


Pero quizás nos interese bloquear la posición que el objeto ocupa en el montículo, es decir, fijar su dirección física para, por ejemplo, pasar dicha dirección a una función de código nativo, que por supuesto no entiende ni de recolectores de basura ni de movimientos no controlados de memoria ni de nada de eso. De momento, el único lenguaje que permite esto es C++/CLI mediante el uso de pin_ptr. La forma en la que otros lenguajes .NET permiten hacer algo parecido es mediante el Interop por atributos, pero desde luego no de forma tan directa.


Veámoslo con un ejemplo sencillo.



   1: #include «stdafx.h»
   2:  
   3: using namespace System;
   4:  
   5: #pragma unmanaged
   6: double nativoMedia(int *p,int tam)
   7: {
   8:     int suma=0;
   9:     for(int i=0;i<tam;i++)
  10:         suma+=p[ i];
  11:     return suma/tam;
  12: }
  13: #pragma managed
  14:  
  15: ref class Mates
  16: {
  17:     array<int>^m_numeros;
  18: public:
  19:     void Incializa(int cantidad)
  20:     {
  21:         m_numeros=gcnew array<int>(cantidad);
  22:         for(int i=0;i<cantidad;i++)
  23:             m_numeros[ i]=i;
  24:     }
  25:     property array<int>^Array
  26:     {
  27:         array<int>^get(void) {return m_numeros;}
  28:     }
  29: };
  30:  
  31: int main(array<System::String ^> ^args)
  32: {
  33:     Mates ^mates=gcnew Mates;
  34:     mates->Incializa(100);
  35:  
  36:     pin_ptr<int> p=&mates->Array[0];
  37:     int *pp=p;
  38:     Console::WriteLine(«La media {0}»,nativoMedia(pp,100));
  39:     Console::ReadKey();
  40:     return 0;
  41: }

El primer bloque de código es una función de código nativo que realiza la media de un array de enteros. Para asegurarnos de que es una función nativa, la englobamos mediante las dos directivas #pragma. Esto no es necesario cuando estamos construyendo código para producción, ya que el compilador se encargará de hacerlo de la mejor manera posible, pero para nuestro ejemplo debemos asegurarnos de que dicha función es completamente nativa. Es de observar que dentro de la función, estamos usando un puntero como un array. Si queremos hacerlo con sintaxis de punteros, una forma de código podría ser:


double nativoMedia(int *p,int tam)



   1: double nativoMedia(int *p,int tam)
   2: {
   3:     int suma=0;
   4:     for(int i=0;i<tam;i++)
   5:         suma+=*p++;
   6:     return suma/tam;
   7: }

Luego definimos una clase que contiene un array manejado de enteros y que posee un método público que se encarga de llenar el array con una serie de valores consecutivos. Lo siguiente es una propiedad que nos devuelve una referencia al propio array interno, lo que en un código de producción no es una buena idea (la de exponer las interioridades de una clase), pero para nuestro ejemplo nos sirve perfectamente.


Ya en main() creamos un objeto del tipo Mates y lo inicializamos con los 100 primeros números. Y entonces viene lo interesante. La línea



   1: pin_ptr<int> p=&mates->Array[0];

está declarando un elemento p que es un puntero bloqueado al primer valor del array interno de la clase Mates. Lo que internamente hace es marcar el array m_numeros como bloqueado e inamovible, y luego devuelve la dirección real del primer elemento dentro del montículo manejado. Esto requiere la explicación de que los arrays .NET están almacenados de forma contigua dentro del montículo manejado, es decir, cuando nosotros creamos un array manejado, el sistema reserva un bloque de memoria manejada capaz de contener todo el array de una sola vez (de ahí que si necesitamos cambiar su tamaño, debemos crear un nuevo array con el nuevo tamaño y copiar el antiguo sobre el nuevo). Esto es así por definición.


Pero un pin_ptr<int> no es un puntero nativo a un entero, sino que se trata de un tipo nuevo que almacena una dirección que apunta a algo bloqueado dentro del montículo manejado. Para convertirlo a un int* debemos asignarlo a una variable de este tipo, que es lo que hacemos en la siguiente línea. El resto es trivial.


Pero si esto fuera código de producción habríamos cometido un error bastante serio. ¿Se da alguien cuenta de cuál es? Que el lector piense antes de seguir leyendo.


El error consiste en que hemos dejado bloqueado un elemento dentro del montículo manejado y sólo se libera a la hora de cerrar el programa, que en nuestro caso da igual, pero en otras situaciones podría tener serias consecuencias de rendimiento y podría incluso llegar a impedir la asignación de nuevos bloques de memoria dentro del montículo manejado. El motivo es muy sencillo: el compactador, asignador y liberador de memoria del montículo manejado ha de tener completa libertad a la hora de poder mover todo lo que haya en él. El autor ignora si posee algún tipo de optimización para lidiar con elementos bloqueados (imagina que sí), pero desde luego no será tan potente como el asignador nativo (que se usa cuando hacemos new o malloc), que es capaz de buscar el bloque más óptimo de entre todos los posibles –de ahí que la asignación de memoria en .NET sea algo más rápida que su equivalente nativo en determinadas circunstancias.


La solución a este problema pasa por hacer algo como lo expuesto más abajo o por mantener dicho puntero bloqueado el menor tiempo posible.



   1: int main(array<System::String ^> ^args)
   2: {
   3:     Mates ^mates=gcnew Mates;
   4:     mates->Incializa(100);
   5:     {
   6:         pin_ptr<int> p=&mates->Array[0];
   7:         int *pp=p;
   8:         Console::WriteLine(«La media es {0}»,nativoMedia(pp,100));
   9:     }
  10:     Console::ReadKey();
  11:     return 0;
  12: }

NOTA: Al rodear un bloque de código mediante llaves dentro de una función, forzamos al compilador a que cree un nuevo contexto local, siendo equivalente a una llamada a función, de modo que p y pp son variables locales dentro de este bloque, y quedan liberadas en la llave de cierre, de forma que el bloqueo de nuestra variable apenas ha durado lo necesario.


Punteros interiores
¿Se puede optimizar el código de arriba de alguna forma? ¿Podemos evitar bloquear un objeto? La respuesta directa es que la situación comentada consiste en la mejor solución posible, pero dependiendo de circunstancias, podemos evitarnos el bloqueo de todo un objeto mediante el uso de punteros interiores, aunque esta vez no podamos salirnos del código manejado.


Retomemos el código anterior y modifiquémoslo un poco:



   1: #include «stdafx.h»
   2:  
   3: using namespace System;
   4:  
   5: ref class Mates
   6: {
   7:     array<int>^m_numeros;
   8: public:
   9:     void Incializa(int cantidad)
  10:     {
  11:         m_numeros=gcnew array<int>(cantidad);
  12:         for(int i=0;i<cantidad;i++)
  13:             m_numeros[ i]=i;
  14:     }
  15:     property array<int>^Array
  16:     {
  17:         array<int>^get(void) {return m_numeros;}
  18:     }
  19: };
  20:  
  21: int main(array<System::String ^> ^args)
  22: {
  23:     Mates ^mates=gcnew Mates;
  24:     mates->Incializa(10);
  25:     for(int i=0;i<10;i++) Console::Write(«{0} «,mates->Array[ i]);
  26:     Console::WriteLine(«»);
  27:  
  28:     interior_ptr<int>medio=&mates->Array[5];
  29:     *medio=99;
  30:     for(int i=0;i<10;i++) Console::Write(«{0} «,mates->Array[ i]);
  31:  
  32:     Console::ReadKey();
  33:     return 0;
  34: }

La clase Mates es la misma, pero el interior de main() ha cambiado por completo. El código que nos interesa está en las líneas



   1: interior_ptr<int>medio=&mates->Array[5];
   2: *medio=99;

que nos muestra cómo obtener un puntero interior. Aunque el ejemplo no demuestra ninguna utilidad, podemos observar cómo se hace, pero tiene una serie de ventajas que pasamos a explicar.


Un puntero interior permite semántica de punteros para objetos de tipo .NET siempre y cuando no nos salgamos del interior de un objeto (de ahí su nombre). Y tienen la ventaja de que son objetos manejados –es decir, siguen siendo móviles y no afectan para nada al recolector ni al asignador-.


Un puntero interior se puede aplicar a cualquier elemento situado dentro del montículo manejado, aunque tiene ciertas limitaciones: no puede ser miembro de una clase, sólo se puede declarar en la pila y no se puede obtener su dirección, es decir, no podemos obtener un puntero interior de un puntero interior, y tampoco se puede pasar como puntero a un bloque de código nativo.


¿Entonces, para qué me sirve? Buena pregunta. Sigamos.


Podemos obtener un puntero interior a un elemento de un array, bloquearlo conforme hemos visto en la sección anterior a través de un pin_ptr y pasarlo a un bloque de código nativo. Como en este caso estamos bloqueando una parte de un objeto secuencial, realmente estamos bloqueando el objeto completo, por lo que poco o nada hemos ganado respecto a lo de antes. ¿Entonces qué?


En primer lugar, si dicho objeto fuera un elemento agregado que contuviera referencias a otros elementos del montículo, al obtener un puntero interior a un elemento agregado y fijándolo después, sólo bloquearíamos el objeto secundario, no el primario, mejorando y optimizando en cierta medida la cantidad de memoria no móvil.


Y en segundo lugar, imaginemos que tenemos una biblioteca o un código fuente que funciona a las mil maravillas con C++ y que trabaja, para desgracia nuestra, con punteros normales. Si disponemos del código fuente, podemos adaptar su funcionamiento sólo cambiando la firma de los métodos sin necesidad de tocar nada del cuerpo de los mismos. Tomando el ejemplo anterior, sólo tenemos que cambiar la firma del método



   1: double nativoMedia(int *p,int tam)

por la de



   1: double nativoMedia(interior_ptr<int> p,int tam)

y dejar el cuerpo del mismo sin tocar. De este modo nos evitamos modificar lo que realmente resulta peligroso dentro del código fuente: los algoritmos y las implementaciones.


Ya para terminar, pongamos el ejemplo completo:



   1: #include «stdafx.h»
   2:  
   3: using namespace System;
   4:  
   5: double nativoMedia(interior_ptr<int> p,int tam)
   6: {
   7:     int suma=0;
   8:     for(int i=0;i<tam;i++)
   9:         suma+=*p++;
  10:     return suma/tam;
  11: }
  12: ref class Mates
  13: {
  14:     array<int>^m_numeros;
  15: public:
  16:     void Incializa(int cantidad)
  17:     {
  18:         m_numeros=gcnew array<int>(cantidad);
  19:         for(int i=0;i<cantidad;i++)
  20:             m_numeros[ i]=i;
  21:     }
  22:     property array<int>^Array
  23:     {
  24:         array<int>^get(void) {return m_numeros;}
  25:     }
  26:  
  27: };
  28:  
  29: int main(array<System::String ^> ^args)
  30: {
  31:     Mates ^mates=gcnew Mates;
  32:     mates->Incializa(100);
  33:  
  34:     interior_ptr<int>p=&mates->Array[0];
  35:     Console::WriteLine(nativoMedia(p,100));
  36:  
  37:     Console::ReadKey();
  38:     return 0;
  39: }

MFC: Obtener un control de un cuadro de diálogo

Estrenemos año con una nueva sección: trucos y cosas para MFC. La verdad es que me he pensado muy seriamente iniciar una sección sobre este tema, ya que casi cualquier cosa que queramos saber, ya está escrita por algún sitio en internet, pero tras reflexionar me he dado cuenta de que en general todo está en inglés. Con esto no pretendo dedicarme a traducir cosas existentes, o al menos no es esa la idea de igual modo que no lo ha sido para la sección de C++/CLI (que sigue viva) y la de bugs (pronto explicaré mi primer bug encontrado en Visual Studio 2008 con C++ nativo); eso no quiere decir que alguna vez, si lo considero interesante, traduzca o parafrasee algo, pero en ese caso, y como viene siendo habitual en mi, así lo diré.

La verdad es que lo que voy a contar en esta entrada es bastante chorra, pero es una de esas cosas que nunca se me queda en la cabeza y siempre tengo que estar buscándolo y recordándolo, así que lo pongo y listo.

Y sin más preámbulo vamos al tajo.

Obtener un control de un cuadro de diálogo
La forma habitual de trabajar con cuadros de diálogo en MFC es derivar una clase de CDialog que toma el valor del ID del recurso del diálogo en cuestión, cosa que suele hacer el asistente adecuado. Dentro de esa clase pondremos nuestras variables y utilizaremos el DDX o intercambio dinámico de datos, para mapear cada control del cuadro con variables que representen a dicho control; también suele hacerlo automáticamente el asistente desde la edición del plantilla, y es una forma muy sencilla de mantener actualizados los controles, casi idéntica a una ficha de Windows Forms.

Pero a veces puede resultarnos inadecuado montar toda la parafernalia del DDX para un control al que solamente le vamos a cambiar algo durante la creación del diálogo. O quizás estemos creando el diálogo de forma dinámica a partir de una plantilla o, como es mi caso actual, cuanta menos memoria ocupe, mejor.

Cada variable DDX es un objeto derivado de CWnd, y ocupa su memoria, aparte de tener datos enlazados en las estructuras globales de la aplicación (quizás más adelante explique cómo funciona eso, aunque ya hay algo publicado por ahí), por lo que en determinadas circunstancias podríamos no querer tener una variable completa.

Entonces, ¿cómo podemos acceder al control? Hay que obtener un puntero a una clase que lo represente. ¿Cómo? Usando GetDlgItem(), pero no la función global de WIN32, sino el método miembro de CDialog (que realmente no está definido en CDialog, sino en CWnd). Este método recibe como parámetro el ID del control o de la ventana hija y devuelve un puntero a CWnd que representa dicho control.

Una vez obtenido el puntero, podemos utilizar los métodos miembro de CWnd o de sus hijas para enviarle mensajes y cambiar su aspecto y/o comportamiento.

Por ejemplo, el código

   1: GetDlgItem(IDC_BTNCONFMS)->EnableWindow(false);

situado dentro de una clase derivada de CDialog deshabilitará en control IDC_BTNCONFMS, que en el caso que nos ocupa es un botón normal y corriente. Lo que hace el código de arriba es dejarlo deshabilitado, de modo que no se pueda presionar.

Pero ¿qué ocurre si nuestro control es, por ejemplo, un CheckBox y queremos marcarlo o desmarcarlo? GetDlgItem() devuelve un puntero a un objeto CWnd, pero CWnd no tiene ningún método para marcar o desmarcar una casilla de verificación.

Una opción perfectamente válida es la de enviarle un mensaje, en concreto un BM_SETCHECK mediante SendMessage() o PostMessage(), ya que ambos métodos están definidos en CWnd, pero resulta una forma bastante bruta de hacerlo.

La forma MFC consiste en convertir el resultado de GetDlgItem() en el tipo de variable que nos interese y luego usar dicha variable:

   1: CButton *ckb=(CButton *)GetDlgItem(IDC_INSTALLKB);
   2: ckb->SetWindowText(TEXT("Remove Keyboard Driver"));
   3: ckb->SetCheck(BST_CHECKED);

Lo acabamos de ver, aunque el ejemplo es bastante atípico pero válido: estamos cambiando el estado de una caja de verificación (esos a los que se les pone una x al hacer clic sobre ellos) pero que tiene la presencia visual de un botón, es decir, se queda resaltado cuando está marcado.

De todos modos hay que andar con cuidado a la hora de realizar este tipo de moldeos, ya que podríamos hacerlo sobre una clase no válida y entonces el resultado podría ser cualquiera, en general el disparo de alguna aserción dentro del código de MFC.