Taskbar de Windows 7 desde WPF

Ahora que se acerca el lanzamiento de Windows 7 tenemos que intentar que nuestras aplicaciones se integren de la mejor manera con Windows 7, y esto pasa por hacer que nuestras aplicaciones se lleven bien con la nueva Taskbar.

Como comentaba en mi artículo anterior http://geeks.ms/blogs/luisguerrero/archive/2009/04/05/windows-7-taskbar.aspx se pueden trabajar con varias características de la Taskbar, nosotros vamos a ver cómo crear una tarea personalizada y como añadir un pequeño botón a la pre visualización de la ventana como la del Windows Media Player. Todo esto lo vamos a hacer para nuestra aplicación de WPF, así que como programadores de .net no tenemos una API directamente para hacer esto así que tenemos que hacer P/Invoke desde .NET. Pero estamos de suerte porque Microsoft ha creado varias librerías para que los programadores de .NET usemos las nuevas características del SO directamente desde nuestro lenguaje favorito de .NET.

Para ello tenemos que descargar dos librerías

Nosotros vamos a trabajar con la librería de la Taskbar, pero está referencia al proyecto de Vista Bridge Library. Dentro del proyecto de la Taskbar encontramos una clase llamada Windows7Taskbar que nos va a permitir hacer una serie de cosas básicas con la taskbar como:

  • Habilitar o deshabilitar la pre visualización de miniatura de nuestra aplicación
    • public static void EnableCustomWindowPreview(IntPtr hwnd)
    • public static void DisableCustomWindowPreview(IntPtr hwnd)
  • ·Establecer el appId de nuestra aplicación para que así se agrupen las ventanas en la Taskbar:
    • public static void SetCurrentProcessAppId(string appId)
  • Establecer el appId para una de las ventanas de nuestra aplicación
    • public static void SetWindowAppId(IntPtr hwnd, string appId)

También vamos a poder trabajar con los JumpList y con los ThumbButton.

JumpList

Para trabajar con los JumpList tenemos una clase dentro del proyecto de Windows 7 Taskbar llamada JumpListManager, aquí podemos ver qué métodos, propiedades y eventos tiene.

Para crear una instancia de esta clase tenemos dos constructores en el que se nos pide o el HANDLE de la ventana o el AppId de la ventana.

image

Obtener el Handle de una ventana en Windows Forms es muy sencillo solo tenemos que acceder a la propiedad Handle de la clase Form y ya lo tenemos, pero ¿cómo se accede al manejador de una ventana de WPF?, si intentamos buscar esa propiedad en la clase System.Windows.Window de WPF observamos que no tenemos esa propiedad, es más, que ningún control de WPF tiene una propiedad Handle.

Esto es así porque como ya sabemos WPF es una tecnología nueva en la que no se utiliza nada de lo anterior para trabajar, y de hecho no es como Windows Forms que es un wrapper muy grande de la API de Win32, aquí en WPF se ha empezado desde cero. Pues bien tiene que haber alguna manera de por lo menos, obtener el Handle de la ventana porque de lo que estamos seguros es de que por mucho que el contenido de una ventana sea WPF o WF al final se llamada a la función CreateWindowEx que en alguno de los casos nos devolverá el Handle de la ventana.

Para obtener el Handle de la ventana en WPF tenemos que ir hasta el namespace System.Windows.Interop para encontrarnos con la clase WindowInteropHelper que una vez creada una instancia, pasándole por argumento la ventana (System.Windows.Window) podemos obtener el Handler (IntPtr) de la ventana y incluso su propietario Owner (o establecerlo). Una vez superado esta pequeña traba podemos continuar con nuestra integración de la Taskbar.

El siguiente problema que se nos plantea es cuando es el mejor momento para crear la instancia de esta clase, porque si lo hacemos antes de que el Handle de la ventana se haya construido, la llamada a la función fallará. Tenemos que elegir el mejor momento para hacerlo, ese momento puede ser cuando se lanza el evento Loaded de la ventana porque es cuando ya se ha construido el árbol visual, se ha hecho measure, arrange y se va a mostrar la ventana al usuario. Pero también el mejor momento puede ser cuando la Taskbar nos avista de que se va a proceder a colocar el icono de la aplicación en la propia Taskbar, este es el momento justo para hacerlo.

Ese momento del que estoy hablando es cuando la aplicación recibe el mensaje “TaskbarButtonCreated” que previamente ha sido registrado llamado a RegisterWindowMessage(L” TaskbarButtonCreated”). Teniendo en cuenta que ya hemos llamado a esa función como podemos escuchar los mensajes de la API de Win32 desde una aplicación WPF. En Windows Forms es muy sencillo porque tenemos que sobrescribir la función virtual WndProc en la que se nos pasa por referencia el Mensaje que se está procesado en ese momento, pero de la misma manera si vamos a WPF vemos que no tenemos esa función disponible para sobrescribir.

La cosa es un poco más complicada porque si alguno recuerda de otros post anteriores, en WPF tenemos un objeto llamado Dispatcher (System.Windows.Threading) que se encarga de procesar los mensajes de la API de Win32, pero de alguna manera queremos que después de que este Dispatcher procese los mensajes lo hagamos nosotros, básicamente lo que queremos hacer en un WindowHook, pero un poco más elegante. No tenemos que llamar a SetWindowHookEx sino que tenemos que explorar de nuevo System.Windows.Interop para encontrar la clave.

La clase que estamos buscando ahora es: HwndSource, que según la documentación presenta el contenido de WPF en una ventana de Win32, que es justamente lo que estamos buscando, pero el caso es que esta clase ya deberá de esta creada porque ya tenemos nuestra ventana creada, así que tenemos que obtener el HwndSource para nuestra ventana actual.

h = new WindowInteropHelper(Application.Current.MainWindow);

HwndSource source = HwndSource.FromHwnd(h.Handle);

Esta clase tiene un método llamado AddHook que acepta por parámetro un delegado que será la función que se llamará cada vez que haya un mensaje disponible para procesarse. Esta es la signatura de la función a ejecutar:

image

public IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)

Lo último que nos queda es filtrar todos los mensajes que nos van a llegar por el mensaje que nos enviara la Taskbar diciendo que ya está lista. Podemos ver el código completo de todo esto aquí.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Windows7.DesktopIntegration;
using System.Windows;
using System.Windows.Interop;
using System.Diagnostics;
using System.Drawing;


namespace MainClient
{
    public class Taskbar
    {
        private static Taskbar instance = new Taskbar();
        public static Taskbar Instance
        {
            get
            {
                return instance;
            }
        }
        private IntPtr idWindow;
        private JumpListManager manager;
        private ThumbButtonManager thumbButtonManager;
        private WindowInteropHelper h;
        public Taskbar()
        {
            HwndSourceHook hook = new HwndSourceHook(HwndSourceHook);
            h = new WindowInteropHelper(Application.Current.MainWindow);
            HwndSource source = HwndSource.FromHwnd(h.Handle);
            source.AddHook(hook);
        }

        public IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            if (msg == Windows7Taskbar.TaskbarButtonCreatedMessage)
            {
                manager = new JumpListManager(h.Handle);
                manager.UserRemovedItems += new EventHandler<UserRemovedItemsEventArgs>(OnUserRemoveItems);
                manager.Refresh();
                if (manager.CustomDestinationCategories.Count() == 0)
                {
                    // create list
                    manager.AddUserTask(new ShellLink()
                    {
                        Title = "Enviar mensaje",
                        Path = Process.GetCurrentProcess().MainModule.FileName,
                        Arguments = " /send"
                    });
                }

                manager.Refresh();

                thumbButtonManager = new ThumbButtonManager(h.Handle);
                ThumbButton button = thumbButtonManager.CreateThumbButton(101, SystemIcons.Information, "No molestar!!");
                button.Clicked += new EventHandler(OnChangeNotificationAlerts);
                thumbButtonManager.AddThumbButtons(button);
            }
            if (thumbButtonManager != null)
            {
                System.Windows.Forms.Message m = new System.Windows.Forms.Message();
                m.HWnd = hwnd;
                m.LParam = lParam;
                m.Msg = msg;
                m.WParam = wParam;
                thumbButtonManager.DispatchMessage(ref m);
            }
            return IntPtr.Zero;
        }

        void OnChangeNotificationAlerts(object sender, EventArgs e)
        {
            Services.Instace.IsNotificationEnabled = !Services.Instace.IsNotificationEnabled;
            if (!Services.Instace.IsNotificationEnabled)
            {
                Windows7Taskbar.SetTaskbarOverlayIcon(h.Handle, SystemIcons.Error, "Sin notificaciones");
            }
            else
            {
                Windows7Taskbar.SetTaskbarOverlayIcon(h.Handle, null, null);
            }
        }


        void OnUserRemoveItems(object sender, UserRemovedItemsEventArgs e)
        {

        }
    }
}

 

Una vez que ya tenemos la instancia del JumpListManager ya podemos agregar tareas a la Taskbar y asociarles una acción. En el código de arriba podemos ver que se añade una acción que es enviar mensaje que, básicamente, ejecuta la aplicación actual pero con el parámetro /send.

Otra posibilidad que hay con la Taskbar es poder insertar botones dentro de la previsualización de la ventana, como hace el Windows Media Player. A través de la clase ThumbButtonManager podemos agregar botones a la Taskbar y realizar una acción cuando se haga clic. En el ejemplo de arriba podemos ver como añadimos un botón configuramos el Icono a mostrar y nos suscribimos al evento Click.

Para terminar el ejemplo tenemos que “alimentar” al ThumbButtonManager con los mensajes que recibimos de Win32, para que pueda realizar sus tareas, es por eso que tenemos que llamar al método DispatchMessage para que pueda comprobar que se ha hecho clic en un botón.

4 comentarios en “Taskbar de Windows 7 desde WPF”

  1. Muy bueno comentario realmente creo que tenemos que nos actualizar pues el futuro de las WPF yá estan en la puerta, como comentaste, la tecnologia xaml no tiene nada que ver con la antigua manera de trabajar y realmente toda la ayuda que nos podamos brindar siempre nos ayudará a avanzar mas rapido, gracias por tu aporte…

  2. Hola Luis

    Me has dejado con una inquietud. Nuestras aplicaciones la mayoría de ellas siguen siendo VB6, eso si, con codigo .NET incrsutado a base de Interops ¿Sabes si a través de interops podríamos hacer que nuestras aplicaciones sigan el comprtamiento marcado en Windows 7?

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *