miércoles, septiembre 08, 2010

Cómo espiar los mensajes de un control

Esto es lo que se ve al espiar ciertas ventanasSigo con trucos necesarios para implementar un sistema de gestión de ventanas más o menos complejo. Imagine que tiene un botón, y que debe responder a los clics del mismo. Ni lo piense: desde la ventana, o desde la clase que contiene al botón, añade un manejador al evento Click del botón. Esta es una ventaja que tiene .NET y su sistema de eventos multicast sobre el viejo y más sencillo sistema de eventos de Delphi nativo. En Delphi nativo solamente podíamos asociar un único manejador a cada evento. En .NET, el evento puede disparar cuantos manejadores necesitemos.
La gran pregunta: ¿y qué ocurre si lo que quiero interceptar es un mensaje que no tiene un evento de "alto nivel" asociado? La solución "clásica" es crear una clase derivada a partir del botón. Pero, ¿y si no es posible hacerlo? Por ejemplo, puede que le toque lidiar con un botón que usted no ha creado (la culpa siempre es de otro). En mi caso, no se trata de un mero botón, sino de la mismísima ventana principal. Estoy escribiendo un gestor genérico de ventanas, y necesito interceptar el mensaje WM_ACTIVATEAPP. Podría obligar al programador que utilice mi gestor a derivar su ventana principal de una clase base mía, debidamente retocada. Sería chapucero, no obstante.
Este es un problema bastante general del modelo de programación orientada a objetos que terminó imponiéndose en nuestros lenguajes de programación: el tipo de objeto va unido indisolublemente a su entidad. Un coche de turismo no se puede convertir dinámicamente en una ambulancia... aunque en la vida real, los turismos puedan utilizarse así. Este problema hace complicado trabajar con bases de datos orientadas a objetos cuyo modelo de objetos sea parecido al de C++, Java o C#. No me malinterprete: el modelo con tipos estáticos es estupendo para escribir cierto tipo de programas, pero es malo para las bases de datos, en las que un objeto de la clase Contacto puede fácilmente en un Cliente, y no podemos perder las referencias al contacto durante la metamorfosis.
Lo interesante es que existe una solución muy conocida para este problema... en el API de bajo nivel de Windows; si busca window subclassing encontrará montones de páginas sobre esta técnica. La pregunta es, ¿cómo conseguir lo mismo utilizando Windows Forms?
La respuesta la tiene la clase NativeWindow, que nos evita tener que usar PInvoke o cosas peores. ¿Hay que interceptar los mensajes que recibe un control? Pues creamos una clase derivada de NativeWindow, sobrescribimos su método virtual WndProc y, para asociarla al puñetero botón, usamos el método AssignHandle de NativeWindow para copiar en una instancia de la nueva clase el identificador de ventana del botón a vigilar. Los mensajes de ventana que normalmente recibiría el botón serán ahora recibidos también por nuestra instancia de la clase derivada de NativeWindow.
Es cierto que seguimos necesitando crear una clase por herencia. Si necesitamos realizar esta operación de vigilancia con N tipos diferentes de botones, ¿significa que tendremos que crear N clases derivadas de NativeWindow? Resulta que no: podemos definir una sola clase derivada de NativeWindow sin tener conocimiento del control concreto que vigilaremos y las acciones concretas que queremos ejecutar. Lo que haríamos en su WndProc, sencillamente, sería llamar a una variable de tipo delegate que especificaríamos al crear las instancia de dicha clase. Y sería ese delegado donde implementaríamos el código necesario:
private sealed class PeepingTom : NativeWindow, IDisposable
{
public delegate void WindowProc(ref Message m);

private WindowProc tommy;

public PeepingTom(Control ladyGodiva, WindowProc tommy)
{
this.tommy = tommy;
this.AssignHandle(ladyGodiva.Handle);
}

public void Dispose()
{
this.ReleaseHandle();
}

protected override void WndProc(ref Message m)
{
tommy(ref m);
base.WndProc(ref m);
}
}
Los nombres, por supuesto, hacen referencia a la historia de Lady Godiva, que se paseó desnuda a caballo por todo Coventry para obligar a su marido a bajar los impuestos (¡que no se le ocurra la idea a la Sonsoles, please!), y a un tal Peeping Tom, o Tomasito el Mirón, que desobedeciendo las órdenes del cabreado marido, se escondió para regocijarse con la visión de la damita en pelotas.
Suponga ahora que tenemos un formulario con un botón, y que queremos saber cuándo el ratón pasa por encima del botón. Para asignarle un espía al botón, dado el diseño de nuestra clase, tenemos que esperar al evento Load del formulario, para asegurarnos de que el identificador de ventana del botón haya sido creado. En ese momento, hacemos algo parecido a esto:
private void Form1_Load(object sender, EventArgs e)
{
new PeepingTom(button1,
delegate(ref Message m)
{
if (m.Msg == 0x02A1)
this.Text = "Mouse hover";
else if (m.Msg == 0x02A3)
this.Text = "Mouse leave";
});
}
El ejemplo no es muy bueno, porque para esta tarea en particular, ya tenemos eventos en la clase Button, pero espero que sirva para que se haga una idea.

Etiquetas: , , ,

1 Comments:

Blogger forever said...

I'm appreciate your writing skill.Please keep on working hard.^^

sábado, octubre 23, 2010 2:45:00 p. m.  

Publicar un comentario

<< Home