Pre-Introducción

Tras la escritura de este documento, Rodrigo Corral me informó de que las declaraciones adelantadas sí que funcionan con Forms en C++/CLI, pero que el tema es algo complicadillo y hay que hacerlo con truco, por lo que ha escrito una entrada sobre ello en su blog.

 

Introducción

Este documento surge como consecuencia de una duda planteada en el grupo de noticias del Visual C++ de Microsoft, y pretende ser una guía rápida para solventar el tema de las dependencias circulares que se presentan en el mundo C++/CLI al no estar permitidas las variables globales.

 

Otros lenguajes CLI lo solucionan permitiendo dicho tipo de dependencias, pero el C++/CLI, dadas sus características únicas, no debe permitirlas, y de hecho no lo hace.

 

El problema

Imaginemos una situación como la siguiente: tenemos una ventana padre y otra hija, no en el sentido de la herencia del C++, sino en el de que la ventana padre posee a la hija, y la controla.

 

La ventana padre ha de conocer la definición de la hija, por lo que se debe incluir el fichero cabecera correspondiente.

 

Pero podemos necesitar que, al igual que la ventana padre controle a la hija, ésta también deba manejar a su propietario. Para ello, también debemos incluir la definición de la clase padre en la hija, con lo que generamos la dependencia circular.

 

El motivo de esta necesidad circular se puede observar claramente si estuviéramos construyendo un editor de textos. La ventana principal del editor ha de abrir otra ventana, que quedará no modal y en la que el usuario tecleará el texto a buscar. Cuando seleccione “buscar” en la ventana hija, ésta ha de comunicarle a la principal que realice las tareas pertinentes, por lo que debe conocerla. Y es aquí donde se produce la dependencia circular.

 

La solución clásica

La solución clásica es muy sencilla, a la hija se le pasa el handle de ventana padre (o este handle es global), y cuando la primera necesita comunicarle algo, le envía un mensaje a través de PostMessage o SendMessage.

 

Dicho mensaje deberá estar en el rango de WM_USER, y la ventana padre deberá poder capturarlo e interpretarlo.

 

Puesto con código fuente, la solución quedaría más o menos como sigue:

 

//En “stdafx.h” o en otro lugar común a ambas ventanas.

#define WM_USER_SEARCH     WM_USER+1

 

//En la ventana padre, dentro del bucle de mensajes:

case WM_USER_SEARCH:

                //Código de búsqueda

                break;

 

//En la ventana hija, en el mensaje WM_COMMAND, procesando

//el clic de “buscar”

PostMessage( hwnd_ventanaPadre, WM_USER_SEARCH,

                (WPARAM)m_tipoBusqueda, (LPARAM)m_pCadenaBuscar);

 

La solución MFC es muy similar a esta. Así, aparte de hacer ambas ventanas completamente independientes una de otra, evitamos que la ventana hija tenga que conocer toda la ventana padre, haciendo que toda la comunicación entre ellas se lleve a cabo mediante una variable del tipo Handle de Ventana.

 

La solución CLI

En el mundo .NET el sistema de paso de mensajes desparece de la vista del programador y queda subsumido dentro del marco de aplicación, liberando así de cierta complejidad, pero a su vez generando otra nueva.

 

La solución CLI pasa por el uso de delegados, o mejor todavía, de eventos, aunque aquí sólo vamos a explicar la primera de las dos formas, siendo la segunda trivial de implementar conociendo la anterior.

 

Como ejemplo vamos a tener dos ventanas, al hacer clic en la principal se nos abrirá la hija, y al hacer clic sobre esta, aparecerá en la padre un mensaje.

 

El primer paso consiste en definir el delegado de forma global y que sea visible para ambas ventanas. En nuestro caso será un método al que se le pasará una cadena, que será mostrada en un componente de etiqueta. La firma del método será, pues:

 

delegate void MuestraMensaje(String ^msg)

 

Esta línea se colocará en el fichero cabecera de la ventana hija; como este fichero estará incluido en el fichero cabecera de la ventana padre, ambas ventanas conocerán el delegado.

 

El siguiente paso será añadir una variable del tipo anterior en la ventana hija, que contendrá una referencia al método de la ventana padre, y que se ejecutará cuando así lo desee la ventana hija.

 

Para el programador de C++ tradicional, el concepto equivalente es sencillo: la ventana hija guarda un puntero a un método de la ventana padre, método que será ejecutado cuando dicha variable sea llamada.

 

Veamos el código:

 

public: MuestraMensaje ^m_muestra;

 

private: void ChildForm_Click(Object^  sender, EventArgs^  e)

            {

                        m_muestra("hola");

            }

 

La primera línea define la variable m_muestra, que es una variable miembro referencia del delegado MuestraMensaje. El equivalente C++ sería el puntero al método. En justa medida dicha variable miembro debería ser privada o protegida, y ser asignada mediante un método público o una propiedad, pero la hemos puesto pública aquí para no liar mucho el código.

 

Ya sólo nos queda ver la parte de la ventana padre. Lo primero que tenemos que hacer es incluir el fichero cabecera de la ventana hija para que la ventana padre la conozca, a ella y al delegado.

 

#includeChildForm.h

 

//Este es el método que la hija va a llamar

public: void Mensaje(String ^msg)

            {

                        label1->Text=msg;

            }

 

//Aquí construimos la ventana hija y asignamos el delegado.

private: void Form1_Click(Object^  sender, System::EventArgs^  e)

            {

                        ChildForm ^f=gcnew ChildForm();

                        f->m_muestra=gcnew MuestraMensaje(this,&Form1::Mensaje);

                        f->Show();

            }

 

El primer método implementa la función que va a ser llamada por la ventana hija sobre la padre cuando así lo queramos. En nuestro caso se trata simplemente de poner un texto en un label, pero podría haber sido cualquier cosa.

 

El siguiente método es la respuesta a hacer clic sobre la ventana padre; creamos una ventana hija, le asignamos el delegado, y la mostramos. La línea central del método lo que hace es crear un enlace entre el método Mensaje de la clase padre y almacenarlo en la variable m_muestra de la hija.

 

Y con eso hemos terminado. El asunto funciona de la siguiente manera. Se crea la ventana padre, y cuando el usuario hace clic sobre ella, dispara el evento Form1_Click, que crea una nueva instancia de la clase ChildForm, le asigna un delegado que se corresponde con el método Mensaje, y muestra la ventana.

 

Tantas veces como hagamos clic sobre la ventana padre, tantas ventanas hijas independientes se crearán, todas ellas almacenando el delegado correspondiente. Los puristas de C++, como yo, afirmarán que estamos dejando sueltas muchas referencias. La respuesta es que sí, pero como se trata de objetos CLI, éstos se autodestruirán durante la recolección de basura, sin tener que preocuparnos nosotros de ello.

 

Ahora, cuando un usuario haga clic en cualquier ventana hija, disparará su evento ChildForm_Click correspondiente, que ejecutará el delegado m_muestra, quien a su vez lanzará el método Muestra de la ventana padre, habiendo conseguido nuestro propósito a la manera .NET.

 

Final

El ejemplo se puede bajar de http://rfog.cmact.com/test.zip. El que quiera adaptar esto a otro lenguaje CLI, que me lo diga y lo haga (y me diga dónde lo publica o si lo publico yo).