Hace unos meses estudiando para rendir la certificación «.NET Framework 3.5, Windows Presentation Foundation Application», encontré en uno de los libros que leí un apartado que mostraba un ejemplo de cómo utilizar el efecto «Glass» de Windows Vista en nuestras aplicaciones WPF, el cual me pareció interesante por lo cual voy a contar como realizarlo.
Para quienes no saben que es el «Aero Glass» les comento que es el efecto que utiliza Windows Media Player en la parte inferior del reproductor para mostrar los controles sobre una sección «semitransparente» del formulario, o el Internet Explorer para hacer lo mismo sobre la sección superior, donde está el menú y la barra de direcciones.
Antes de comenzar voy a comentar que el Windows Vista posee un servicio llamado DWM (Desktop Windows Manager), el cual es el encargado de crear la imagen que se ve en el escritorio del sistema operativo cuando éste tiene seleccionado el tema «Aero». En Windows Vista cuando no está seleccionado este tema (al igual que en todas las versiones anteriores de Windows) cada aplicación era la encargada de escribir su contenido en el buffer de la pantalla cada vez que el sistema operativo le enviaba el mensaje WM_PAINT. Con DWM el sistema posee una composición off-screen de la imagen de cada aplicación y la copia al buffer de la pantalla cuando es necesario, evitando así los problemas de «arrastre» que se producían cuando una aplicación estaba ocupada y el usuario la movía por el escritorio. Gracias al DWM también se puede ver una «vista en miniatura» de la aplicación al pasar el mouse sobre éste en la barra de tareas, dibujar todas las ventas en 3D para seleccionar la deseada al pulsar la tecla Win+Tab, y el mencionado efecto Glass (el cual voy a mostrar como crearlo a continuación).
Lo primero que hay que saber para realizar este efecto es que hay que utilizar la API de DWM, la cual se llama (obviamente) DwmApi.dll, y posee un método llamado DwmExtendFrameIntoClientArea que es el que se utiliza para generar el efecto Glass. Este efecto en si lo que logra es expandir el marco de la ventana que ya se ve con el efecto Glass hacia el interior de la misma. Para lograrlo este método utiliza dos parámetros: el primero es del tipo IntPtr y es el puntero Win32 de nuestra ventana, y el segundo es una estructura donde le pasamos los nuevos márgenes de nuestra ventana. Estos márgenes, como en cualquier función de WPF, no deben estar en píxeles sino que deben estar en una medida independiente, por lo cual si queremos convertir la cantidad de pixeles a esa medida deben multiplicarla por los DPIs del monitor, los cuales se pueden obtener a través de un objeto Graphics, como se muestra a continuación:
//Obtengo los DPIs del sistema operativo Graphics g = Graphics.FromHwnd(windowHandle);
Si a cualquiera de los márgenes los establecemos en -1 toda la ventana va a tener el efecto Glass.
A continuación muestro el código de la clase estática que hice para aplicar el efecto a una ventana.
using System; using System.Drawing; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Interop; using System.Windows.Media; namespace GlassEffect { /// <summary> /// Clase estática para el manejo del DWM /// <summary> public static class DWMHelper { /// <summary> /// API para generar el efecto Glass /// </summary> /// <param name="hwnd">Puntero a la ventana</param> /// <param name="pMarInset">Márgenes nuevos</param> [DllImport("DwmApi.dll")] private static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref Margins pMarInset); /// <summary> /// Estructura con los márgenes nuevos /// </summary> [StructLayout(LayoutKind.Sequential)] private struct Margins { public int cxLeftWidth; public int cxRightWidth; public int cyTopHeight; public int cyBottomHeight; } /// <summary> /// Aplica el efecto Glass a una ventana /// </summary> /// <param name="win">Ventana</param> /// <param name="left">Cantida de pixeles de la izquierda</param> /// <param name="right">Cantida de pixeles de la derecha</param> /// <param name="top">Cantida de pixeles de arriba</param> /// <param name="bottom">Cantida de pixeles de abajo</param> public static void ApplyGlass(Window win, int left, int right, int top, int bottom) { //Obtengo el puntero Win32 a la ventana WindowInteropHelper windowInterop = new WindowInteropHelper(win); IntPtr windowHandle = windowInterop.Handle; HwndSource.FromHwnd(windowHandle).CompositionTarget.BackgroundColor = Colors.Transparent; //Obtengo los DPIs del sistema operativo Graphics g = Graphics.FromHwnd(windowHandle); //Establezco los márgenes Margins margins = new Margins(); margins.cxLeftWidth = Convert.ToInt32(left * (g.DpiX / 96)); margins.cxRightWidth = Convert.ToInt32(right * (g.DpiX / 96)); margins.cyTopHeight = Convert.ToInt32(top * (g.DpiY / 96)); margins.cyBottomHeight = Convert.ToInt32(bottom * (g.DpiY / 96)); // Extend the glass frame. if (DwmExtendFrameIntoClientArea(windowHandle, ref margins) < 0) throw new NotSupportedException("Error en la operación"); } } } //Establezco los márgenes Margins margins = new Margins(); margins.cxLeftWidth = Convert.ToInt32(left * (g.DpiX / 96)); margins.cxRightWidth = Convert.ToInt32(right * (g.DpiX / 96)); margins.cyTopHeight = Convert.ToInt32(top * (g.DpiY / 96)); margins.cyBottomHeight = Convert.ToInt32(bottom * (g.DpiY / 96));
Por último, en el siguiente enlace les dejo un proyecto de ejemplo funcionando para que puedan probarlo.