Dibuja o renderiza un formulario de Windows directamente sobre el fondo de pantalla, detrás de los íconos del escritorio en Windows.
Introducción
Quienes lean este artículo probablemente conozcan DreamScene, la función de Windows Vista que permite renderizar secuencias de vídeo (en formato .dream) como fondo de escritorio. También existe una herramienta llamada Rainmeter que permite colocar widgets, gadgets o cualquier otro elemento en el escritorio, uno encima del otro, arriba y abajo. También existe Winamp, el primer programa donde vi el renderizado de DirectX en acción detrás de los iconos del escritorio.
Estas herramientas tienen una cosa en común: no pueden hacerlo en Windows 8, al menos la parte de “dibujar debajo de los íconos del escritorio”.
Cómo funcionaba antes
Existe el árbol de ventanas. Este árbol contiene todas las ventanas que se muestran u ocultan en el escritorio actual. Además, existe una herramienta llamada Spy++ (Visual Studio -> Herramientas -> Spy++), que permite visualizar y navegar por dicho árbol. Esta herramienta forma parte de Visual Studio.
El Código
Obtener la dirección del administrador del programa
Primero, buscamos el identificador de la ventana Progman. Podemos usar la función FindWindow proporcionada por la API de Windows para realizar esta tarea.
IntPtr progman = W32.FindWindow("Progman", null);
Enviar mensaje al administrador del programa
Para activar la creación de una ventana WorkerW entre los iconos del escritorio y el fondo de pantalla, debemos enviar un mensaje al Administrador de programas. Este mensaje no está documentado, por lo que no tiene un nombre especial en la API de Windows, excepto 0x052C. Para enviarlo, usamos el método . de la API de Windows SendMessageTimeout.
IntPtr result = IntPtr.Zero;
W32.SendMessageTimeout(progman,
0x052C,
new IntPtr(0),
IntPtr.Zero,
W32.SendMessageTimeoutFlags.SMTO_NORMAL,
1000,
out result);
Obtener el control de la ventana recién creada
Ahora, necesitamos obtener un identificador para la ventana WorkerW recién creada. Dado que hay más de una ventana con el título "" y la clase " WorkerW", debemos recorrer el árbol de ventanas secuencialmente. Esto se puede hacer usando la función EnumWindows.
EnumWindows realiza llamadas EnumWindowProc para cada ventana de nivel superior. Desde ahí, podemos comprobar si la ventana actual contiene un elemento secundario llamado “SHELLDLL_DefView”, lo que indica que representa los iconos del escritorio. A continuación, tomamos el siguiente elemento secundario de esa ventana.
// Spy++ output
// .....
// 0x00010190 "" WorkerW
// ...
// 0x000100EE "" SHELLDLL_DefView
// 0x000100F0 "FolderView" SysListView32
// 0x00100B8A "" WorkerW <-- Esta es la instancia
// 0x000100EC "Program Manager" Progman
IntPtr workerw = IntPtr.Zero;
W32.EnumWindows(new W32.EnumWindowsProc((tophandle, topparamhandle) =>
{
IntPtr p = W32.FindWindowEx(tophandle,
IntPtr.Zero,
"SHELLDLL_DefView",
IntPtr.Zero);
if (p != IntPtr.Zero)
{
// Gets the WorkerW Window after the current one.
workerw = W32.FindWindowEx(IntPtr.Zero,
tophandle,
"WorkerW",
IntPtr.Zero);
}
return true;
}), IntPtr.Zero);
Prueba 1: Dibujar gráficos entre iconos y fondos de pantalla
Con el workerw obtenido, empieza la diversión. La primera prueba trata sobre cómo usar las clases System.Drawing para dibujar algo.
Esta prueba dibuja un rectángulo en la esquina superior izquierda del escritorio. Si usas varios monitores, ten en cuenta que el área del escritorio ocupa un rectángulo en todos ellos. Por lo tanto, asegúrate de que el monitor izquierdo esté encendido y que la ubicación del monitor asigne la esquina superior izquierda a un monitor, en caso de tener cuatro, uno encima de los otros tres.
IntPtr dc = W32.GetDCEx(workerw, IntPtr.Zero, (W32.DeviceContextValues)0x403);
if (dc != IntPtr.Zero)
{
using (Graphics g = Graphics.FromHdc(dc))
{
g.FillRectangle(new SolidBrush(Color.White), 0, 0, 500, 500);
}
W32.ReleaseDC(workerw, dc);
}
Prueba 2: Colocar un formulario de Windows detrás de los iconos del escritorio
Esta prueba muestra cómo colocar un formulario Windows Form normal detrás de los iconos del escritorio. En esencia, esto se puede lograr asignando a nuestra ventana WorkerW el elemento principal de un formulario Windows Form. Para ello, podemos usar la función SetParent de la API de Windows.
Form form = new Form();
form.Text = "Prueba Window";
form.Load += new EventHandler((s, e) =>
{
form.Width = 500;
form.Height = 500;
form.Left = 500;
form.Top = 0;
Button button = new Button() { Text = "Catch Me" };
form.Controls.Add(button);
Random rnd = new Random();
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
timer.Interval = 100;
timer.Tick += new EventHandler((sender, eventArgs) =>
{
button.Left = rnd.Next(0, form.Width - button.Width);
button.Top = rnd.Next(0, form.Height - button.Height);
});
timer.Start();
W32.SetParent(form.Handle, workerw);
});
Application.Run(form);
Conclusión
Un solo mandato para gobernarlos a todos 😄
W32.SendMessageTimeout(W32.FindWindow("Progman", null),
0x052C,
new IntPtr(0),
IntPtr.Zero,
W32.SendMessageTimeoutFlags.SMTO_NORMAL,
1000,
out result);
