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.

3 comentarios sobre “MFC: Obtener un control de un cuadro de diálogo”

  1. Muy buenas, me parece muy interesante el blog que has decidido abrir. Precisamente porque como comentas gran parte de la documentación acerca de la MFC está en inglés y hay que rebuscar para encontrarla.

    Aquí te propongo un comentario/sugerencia/pregunta: tengo que hacer un cuadro de diálogo dinámico (basado en una plantilla HTML) para lo cual he de usar la clase CDHtmlDialog que hereda de CDialog.

    Primeramente debe leer un archivo y en base a la información de ese archivo, añadir opciones a un menú, algunas de las cuales deben abrirme un cuadro de dialogo HTML. En definitiva, no se como conseguir ese «dinamismo» para crear el cuadro de dialogo.

    Un saludo y mil gracias de antemano

  2. No es trivial lo que quieres, no.

    Lo primero de todo es que tienes que conocer los ID de cada control para poder subclasificarlos, es decir, algo así:

    CButton button;
    HWND hwnd;
    GetDlgItem(ID_CONTROL,&hwnd);
    button.Attach(hwnd);

    Luego debes de haber definido los eventos que quieras coger respecto al botón, y cuando termines, en el evento OnDestroy de tu cuadro de diálogo heredado de CDHtmlDialog,

    button.Detach();

    Incluso puedes hacerlo por DDX después de subclasificar el control.

    Otra forma es sobreescribir el bucle de mensajes de tu clase y controlar los mensajes a mano como si fuera en Win32.

    Respecto al menú, GetSystemMenu te da acceso al menú del sistema para que añadas lo que quieras (mirate el evento OnInitDialog() de cualquier programa con cuadro de diálogo como ventana principal hecho con el asistente).

  3. Gracias por la pronta contestación.

    He estado documentándome sobre todo lo que mencionas, y ya tengo todo más o menos «cuadrado». Sin embargo, me encuentro ante un gran problema, que es justamente lo primero que me dices.

    ¿Que ocurriría si no sé de antemano los IDs?. Resulta que dependiendo de la opción escogida, abrire un dialogo HTML u otro, y claro, cada uno tienes sus propios controles. La información de éstos controles, viene en un XML, que leo y guardo en una clase particular que me creo.

    He estado mirando y remirando, pero no hay ningún método que me permita obtener la ID del control. Como dices, una vez que obtenga la ID, todo se hace sencillo, pues a través de GetDlgItem(…) accedo al elemento.

    Gracias y Felicitaciones de nuevo, por este gran blog

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *