Blog de Fernando Machado Piriz

Artículos sobre transformación digital, arquitectura empresarial y temas relacionados

Denme un handle y moveré el mundo

with one comment

imageEl título de este artículo parafrasea una expresión atribuida a Arquimedes, quién dijo refiriéndose a las palancas “denme un punto de apoyo y moveré el mundo”.

Para los que desarrollamos aplicaciones para Windows desde hace algunos años, los handles eran la puerta de entrada a prácticamente cualquier recurso a través de la API de Win32. ¿Quieres cerrar o minimizar una ventana? Necesitas el handle de esa ventana. ¿Quieres escribir texto en una ventana? Necesitas un handle a la fuente, un handle al contexto del dispositivo, y un handle a la ventana. Y así con todo. Los handles eran como números mágicos que las funciones de la API de Win32 usaban para hacer su trabajo, aunque en realidad eran las direcciones de memoria donde estaban las estructuras de datos usadas por esas funciones.

Afortunadamente, en los tiempos que corren del .NET Framework, casi nadie se preocupa por tener que usar, y menos de saber qué es, un handle. El .NET Framework oculta la complejidad de manejar los recursos de Windows que tenía la API de Win32, y con ello, oculta también los handles.

Sin embargo, muy de vez en cuando, aparecen algunos problemas para los que no hay más remedio que conseguir un handle. He aquí un ejemplo.

Una de las características de Giving a Presentation es poder ocultar los íconos del escritorio durante una presentación, volviéndolos a mostrar nuevamente cuando la presentación termina. Mientras estaba programando esa característica, busqué infructuosamente en la web una forma de hacer esto. Hasta donde pude averiguar, no hay una función pública en la API de Windows para hacer esto; y la mayoría de los hacks que se encuentran en la web –este por ejemplo- usan handles y funciones de la API de Win32, pero no funcionan correctamente: inhabilitan el escritorio por completo, no sólo sus íconos; como consecuencia, no responde el menú desplegable al que se accede haciendo clic con el botón secundario del mouse, por ejemplo.

Hablando del menú desplegable del escritorio, lo que necesitaba era la funcionalidad del comando Show desktop icons.

image

Aquí fue donde los viejos conocimientos de handles y compañía fueron útiles nuevamente. Yo sabía que este comando del menú terminaba tarde o temprano siendo transformado en un mensaje WM_COMMAND al escritorio. Lo que necesitaba para emular este comando era saber los argumentos del mensaje WM_COMMAND y –como no podía ser de otra manera- el handle del escritorio. Cuando averiguara eso, podía enviar el mismo mensaje WM_COMMAND. El escritorio no podría saber si el mensaje lo estaba enviando el menú o si lo estaba enviando mi programa, por lo que debería procesarlo de la misma forma.

Para ambas cosas usé una vieja herramienta que todavía sigue estando incluida en Visual Studio: Spy++. Esta herramienta permite ubicar una ventana y, entre otras cosas, ver los mensajes que recibe. La imagen siguiente muestra el nodo en la lista de ventanas de Spy++ que corresponde al escritorio. Noten que el escritorio es una ventana de la clase SHELLDLL_DefView; luego veremos para qué sirve saber esto.

clip_image003

La siguiente imagen muestra los mensajes recibidos por esa ventana al ejecutar el comando Show desktop icons. Noten que el ID del comando enviado es el número 29698; también veremos luego para qué sirve esto:

clip_image004

La función para mostrar u ocultar los iconos del escritorio quedó finalmente así:

 

/// <summary>
/// Toggles desktop icons visibility by sending a low level command to desktop window in the same way as the popup menu "Show desktop
/// icons" does. Use <code>AreDesktopIconsVisible</code> to get current desktop icons visibility status.
/// </summary>
private static void ToggleDesktopIconsVisibility()
{
    IntPtr defaultViewHandle = FindShellWindow();
    UIntPtr resultArgument;
    IntPtr returnValue = NativeMethods.SendMessageTimeout(
        defaultViewHandle, 
        NativeMethods.WM_COMMAND, 
        new IntPtr(29698), 
        IntPtr.Zero,
        NativeMethods.SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
        1000, 
        out resultArgument);
}

 

La clase NativeMethods contiene todas las funciones y constantes importadas de la API de Win32; el código no está aquí por simplicidad, pero pueden descargarlo de CodePlex. La función SendMessageTimeout es usada para enviar el mensaje WM_COMMAND con ID 29698a la ventana cuyo handle se obtuvo con la función FindShellWindow.

Por su lado, la función FindShellWindow, se encarga de buscar y retornar el handle al escritorio, más precisamente, el handle de la ventana que contiene los íconos del escritorio, cuya clase es SHELLDLL_DefView. Encontrar esta ventana no es fácil. Vaya uno a saber porqué, en ciertos sistemas la ventana está en una parte de la jerarquía de ventanas y en otros sistemas en otro –siendo estos sistemas en ambos casos Windows 7 RTM de 32 bits con las últimas actualizaciones instaladas-. Debido a esto la funcionalidad de ocultar los íconos del escritorio de Giving a Presentation en algunos casos funcionaba y en otros no. Debo agradecer a mi colega MVP Elías Mereb por haberme ayudado a probar varias versiones hasta que dar con la solución.

 

/// <summary>
/// Called by EnumWindows. Sets <code>shellWindowHandle</code> if a window with class "SHELLDLL_DefView" is found during enumeration.
/// </summary>
/// <param name="handle">The handle of the window being enumerated.</param>
/// <param name="param">The argument passed to <code>EnumWindowsProc</code>; not used in this application.</param>
/// <returns>Allways returns 1.</returns>
private static int EnumWindowsProc(IntPtr handle, int param)
{
    try
    {
        IntPtr foundHandle = NativeMethods.FindWindowEx(handle, IntPtr.Zero, "SHELLDLL_DefView", null);
        if (!foundHandle.Equals(IntPtr.Zero))
        {
            shellWindowHandle = foundHandle;
            return 0;
        }
    }
    catch
    {
        // Intentionally left blank
    }

    return 1;
}

/// <summary>
/// Finds the window containing desktop icons.
/// </summary>
/// <returns>The handle of the window.</returns>
private static IntPtr FindShellWindow()
{
    IntPtr progmanHandle;
    IntPtr defaultViewHandle = IntPtr.Zero;
    IntPtr workerWHandle;
    int errorCode = NativeMethods.ERROR_SUCCESS;

    // Try the easy way first. "SHELLDLL_DefView" is a child window of "Progman".
    progmanHandle = NativeMethods.FindWindowEx(IntPtr.Zero, IntPtr.Zero, "Progman", null);

    if (!progmanHandle.Equals(IntPtr.Zero))
    {
        defaultViewHandle = NativeMethods.FindWindowEx(progmanHandle, IntPtr.Zero, "SHELLDLL_DefView", null);
        errorCode = Marshal.GetLastWin32Error();
    }

    if (!defaultViewHandle.Equals(IntPtr.Zero))
    {
        return defaultViewHandle;
    }
    else if (errorCode != NativeMethods.ERROR_SUCCESS)
    {
        Marshal.ThrowExceptionForHR(errorCode);
    }

    // Try the not so easy way then. In some systems "SHELLDLL_DefView" is a child of "WorkerW".
    errorCode = NativeMethods.ERROR_SUCCESS;
    workerWHandle = NativeMethods.FindWindowEx(IntPtr.Zero, IntPtr.Zero, "WorkerW", null);
    Debug.WriteLine("FindShellWindow.workerWHandle: {0}", workerWHandle);

    if (!workerWHandle.Equals(IntPtr.Zero))
    {
        defaultViewHandle = NativeMethods.FindWindowEx(workerWHandle, IntPtr.Zero, "SHELLDLL_DefView", null);
        errorCode = Marshal.GetLastWin32Error();
    }

    if (!defaultViewHandle.Equals(IntPtr.Zero))
    {
        return defaultViewHandle;
    }
    else if (errorCode != NativeMethods.ERROR_SUCCESS)
    {
        Marshal.ThrowExceptionForHR(errorCode);
    }

    shellWindowHandle = IntPtr.Zero;

    // Try the hard way. In some systems "SHELLDLL_DefView" is a child or a child of "Progman".
    if (NativeMethods.EnumWindows(EnumWindowsProc, progmanHandle) == 0)
    {
        errorCode = Marshal.GetLastWin32Error();
        if (errorCode != NativeMethods.ERROR_SUCCESS)
        {
            Marshal.ThrowExceptionForHR(errorCode);
        }
    }

    // Try the even more harder way. Just in case "SHELLDLL_DefView" is in another desktop.
    if (shellWindowHandle.Equals(IntPtr.Zero))
    {
        if (NativeMethods.EnumDesktopWindows(IntPtr.Zero, EnumWindowsProc, progmanHandle))
        {
            errorCode = Marshal.GetLastWin32Error();
            if (errorCode != NativeMethods.ERROR_SUCCESS)
            {
                Marshal.ThrowExceptionForHR(errorCode);
            }
        }
    }

    return shellWindowHandle;
}

En ciertas situaciones, aún en los tiempos del .NET Framework, todavía sigue siendo útil conseguir un handle: denme un handle y moveré el mundo. O al menos, ocultaré los íconos del escritorio, API de Win32 mediante. Hasta la próxima.

Written by fernandomachadopiriz

08/08/2010 a 23:52

Publicado en C# 4.0

Tagged with

Una respuesta

Subscribe to comments with RSS.

  1. Me encantó tu aporte….

    Te tengo una consulta, existe alguna forma en la que pueda cambiar la posición de los iconos del escritorio, o al menos, conocer su posicion xy y sus dimensaiones wh.

    Gracias, si me contestas.

    Esteban Andrade

    12/11/2010 at 00:58


Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: