Y por fin la tercera y última entrada, donde -por fin- utilizaremos todo lo que hemos aprendido anteriormente para realmente hacer algo útil :)
Ya vimos anteriormente los conceptos de parent y owner de un control, ahora vamos a ver cómo podemos forzar esos comportamientos en una aplicación por medio de los Handlers de la ventana y de llamadas a la API Win32.
Al final de la entrada se puede descargar el proyecto de visual studio 2005 utilizado para las capturas de pantalla de este post. La aplicación crea tres formularios simples: un reloj con un par de botones que simplemente muestran un aviso al ser pulsados, un formulario para guardar una imagen, y un formulario con diferentes botones de control para activar y desactivar las operaciones entre los anteriores formularios.
Botón 'Make Owner'
Al pulsar este botón provocará que el formulario 'ClockWindow' se convierta en el Owner del formulario 'PictureBox'. Por tanto éste último siempre será dibujado por encima del primero:
Para conseguir este efecto, usaremos la función SetWindowLongPtr(), que sirve para cambiar los atributos de una ventana. Si firma en P/Invoke es:
public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
Donde hWnd es el handle de la ventana a la que queremos cambiar un atributo, nIndex es una constante que define el atributo a cambiar, y dwNewLong sirve para establecer el nuevo valor.
En este caso para cambiar el owner de la ventana, tenemos que usar una constante con un desafortunado nombre: GWL_HWNDPARENT. Ésta constante está definida en winapi.h, y tiene el valor -8. Su nombre es desafortunado porque realmente no tiene nada que ver con establecer la clase padre (parent) y puede llevar a equívocos.
Para establecer, por ejemplo, que el formulario 'ClockWindow' sea el propietario de 'PictureBox' llamaremos a la función de la siguiente manera:
WinAPIDeclarations.SetWindowLongPtr( pictureWindowForm.Handle, -8, clockWindowForm.Handle);
Y para volver a dejar todo como estaba, lo que haremos será establecer el Handler del owner de 'PictureBox' como IntPtr.Zero, lo que equivale a establecer el escritorio como propietario:
WinAPIDeclarations.SetWindowLongPtr( pictureWindowForm.Handle, -8, IntPtr.Zero);
Botón 'Make Parent' y 'Make Parent to Button'
Seguimos con la función SetParent(), cuya firma para P/Invoke se define:
[DllImport("user32.dll")]
public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);Con esta función podemos forzar que un control cualquiera se convierta en el padre del otro, y por tanto provocará que el hijo se dibuje dentro del rectángulo de cliente del control padre, ya sea un formulario u otro control:
Hay que destacar que tanto esta operación como la anterior funcionan para cualquier ventana de la que podamos obtener el handle. Por tanto podemos conseguir cosas como esta:
Botón 'Disable refresh'
Esta operación permite activar o desactivar el repintado de una ventana. Utilizaremos la función SendMessage() para ello y el mensaje WM_SETREDRAW. La firma P/Invoke de la función y el valor del mensaje es:
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);public static readonly uint WM_SETREDRAW = 0x000B;
Para desactivar el repintado de una ventana haremos:
IntPtr result = WinAPIDeclarations.SendMessage( clockWindowForm.Handle, WinAPIDeclarations.WM_SETREDRAW, (IntPtr)0, IntPtr.Zero);Y para volver a activarlo:
IntPtr result = WinAPIDeclarations.SendMessage( clockWindowForm.Handle, WinAPIDeclarations.WM_SETREDRAW, (IntPtr)1, IntPtr.Zero);
Botón 'Disable Input'
Esta operación permite activar o desactivar el procesado de input para una ventana, es decir, podemos hacer que la ventana no responda a ningún tipo de entrada de datos (con ratón o teclado) de forma que en la práctica no se puede interactuar con ella. Para ello disponemos de otra función con un nombre no muy descriptivo: EnableWindow(), que tiene la firma:
[DllImport("user32.dll")]
public static extern bool EnableWindow(IntPtr hWnd, bool bEnable);Viendo los argumentos es bastante fácil de usar si eres capaz de intuir por el nombre lo que hace; para desactivar la entrada de input haremos: EnableWindow(clockWindowForm.Handle, false);y para desactivarla:
EnableWindow(clockWindowForm.Handle, true);
De nuevo, estas dos últimas operaciones pueden aplicarse a cualquier ventana sabiendo su handle, así que podemos desactivar el repintado o la entrada de datos de una ventana externa. Esto puede ser útil en ciertos casos pero hay que tener mucho cuidado: si olvidamos restablecer el estado anterior o nuestra aplicación se cierra inesperadamente podríamos dejar una aplicación externa sin respuesta, por lo que el usuario tendría que matar el proceso y recargarla, potencialmente perdiendo datos. Hay que ser muy cuidadoso al utilizar estas operaciones.
También hay que avisar a posibles usuarios de Mono de que al estar haciendo llamadas específicas a la API de Windows, este código no es portable.





