Monthly Archives: April 2009

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://www.luisguerrero.net/post/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.

Virtual Tech Days 09

El 1 de abril se celebro un evento mundial online en Microsoft llamado Virtual Tech Days 09, que duro 24 horas ininterrumpidamente desde diferentes localizaciones del planeta, una agenda cargada de contenidos para que podáis aprender sobre lo nuevo que viene en Microsoft.

Yo participé en este evento como conferenciante hablando de WPF, en concreto de los problemas que se puede encontrar los programadores de Windows Forms cuando quieren migrar a WPF. Hay que recordar que yo mantengo una lista de post llamados “WPF para programadores de WF” y justamente de eso hable, una casualidad que Microsoft me propusiera esa charla.

Aquí tenéis la grabación de la charla desde Msn Video,

WPF para programadores de Windows Form

también os dejo la presentación usada

image

y el enlace a la web oficial. 

http://www.msfttechdays.com/public/home.aspx

Espero que os guste.

Windows 7: Taskbar

Una de las características más novedosas de Windows 7 es la nueva Taskbar. Es una evolución del concepto de Taskbar que teníamos en las versiones previas de Windows pero que ahora se ha renovado. Veremos cuáles son esas características y cómo podemos aprovecharlas para nuestras aplicaciones.

Aquí podemos ver una captura de pantalla de las nueva Taskbar que viene con Windows 7:

image

Podemos observar que ahora la barra de acceso rápido (Quick Launch) ha desaparecido, y ahora en su lugar la barra de tareas es como una especie de mezcla entre ambos, pues como podemos observar tenernos en la Taskbar programas ejecutándose y programas que no se están ejecutando, además también podemos ver como algunos de los elementos tiene varias ventanas y están se agrupan (en el ejemplo podemos ver a Microsoft PowerPoint).

Con respecto a la agrupación ¿Cómo Windows 7 sabe que una ventana corresponde a un mismo programa, proceso o las tiene que agrupar?. Ahora las aplicaciones tiene un Application Id, que representa el identificador de la ventana para que la Taskbar sepa si tiene que agrupar o no, eso permite que dos ventanas del mismo proceso aparezcan virtualmente como dos aplicaciones diferentes y justo al revés, es decir, que dos ventanas de procesos diferentes aparezcan agrupadas. Esta propiedad es un string y está limitado a 128 caracteres.

Los programas actuales no están programados utilizando las características de Windows 7, así que como sabe el sistema como agrupar las ventanas, en principio utiliza en nombre del proceso.

Podemos tener varios tipos de procesos especiales en los que se tiene que tener en cuenta el nombre de la Taskbar:

· Varios ejecutable, la misma aplicación

· El mismo ejecutable (host), varias aplicaciones.

Ejemplos de estos, tenemos por ejemplo, el proceso de host de Visual Studio (vshost.exe) que es el host de una aplicación que depuramos dentro de VS con lo que en la Taskbar aparecerá el programa como vshost y no como el nombre de nuestro ejecutable. El otro ejemplo lo tenemos en Internet Explorer que ahora dentro del mismo proceso, tenemos diferentes ventanas y queremos que aparezcan como procesos diferentes dentro de la Taskbar.

Esta es la función que tenemos que llamar para poder establecer el nombre de todas las ventanas:

#include <windows.h>
#pragma comment (lib, "shell32.lib")
SetCurrentProcessExplicitAppUserModelId(L"Microsoft.Samples.AppId1");

Y si queremos específicamente establecer el Application Id para una ventana en concreto tenemos que hacer esto:

PROPVARIANT pv;
InitPropVariantFromString(L"Microsoft.Samples.AppId2", &pv);
IPropertyStore *pps;
HRESULT hr = SHGetPropertyStoreForWindow(hWnd, IID_PPV_ARGS(&pps));
pps->SetValue(PKEY_AppUserModel_ID, pv);

JumpList

Una de las nuevas características de la Taskbar es la inclusión de las JumpList en los menus contextuales de las aplicaciones que tenemos en la barra.

image

Aquí podemos ver una captura de esa característica, que nos permite ver una lista de los elementos recientes que hemos usado con ese programa. Ahora simplemente para abrir uno de esos ficheros tenemos que hacer clic sobre el icono y automáticamente se abrirá el fichero correspondiente.

Esta lista es generada a partir de varios orígenes, una de ellas es las lista de ficheros abiertos desde el explorador de Windows, lo que vienen a ser los últimos ficheros abiertos. Además, si dentro de nuestra aplicación usamos el Common File Dialog para abrir ficheros automáticamente el fichero abierto se añadirá a la lista de nuestro usuario. Además de eso disponemos de una API que nos permite acceder a los elementos de la lista y configurarlo a nuestra manera.

Además de lo que son archivos recientemente usados por nuestra aplicación también podemos insertar tareas dentro de los JumpList. Las tareas son comandos que desde la TaskBar se pueden invocar para nuestra aplicación y el usuario se puede comunicar con nuestra aplicación de una manera normalizada.

Aquí podemos ver un diagrama de las diferentes opciones de los JumpList.

image

En el que podemos observar varios grupos. Abajo del todo tenemos las tareas de la TaskBar, Cerrar Ventana, Despinear esta aplicación de la taskbar y lanzar de nuevo otra instancia de la aplicación. Encima tenemos, tareas definidas por el desarrollador de la aplicación. Un ejemplo de estas tareas pueden ser las tareas del Windows Media Player que incluye una tarea de reproducir toda la música mezclada y otra que es resumir la última lista reproducida. Esta última acción no siempre está disponible porque tenemos que haber reproducido una lista previamente para que nos aparezca esta tarea.

Además de eso también podemos tener otra serie de acciones disponibles para el usuario, que representan acciones previamente hechas en la aplicación. Siguiendo el ejemplo del Windows Media Player, podemos ver que tenemos una serie de acciones como reproducir el resultado de la búsqueda de música “The Pianist”, reproducir toda la música, reproducir todo el vídeo, reproducir la lista “RockBand”, reproducir el artista “The Alan Parsons Project” y ColdPlay y Pink Floyd. Toda esta lista son las acciones pineadas (Pinned) es decir acciones que el usuario (yo, en este caso) quiere tener disponibles para su aplicación.

image

Si ponemos como ejemplo otra aplicación como por ejemplo una aplicación para administrar la facturación de una empresa, podemos tener disponibles las últimas facturas a las que el usuario ha accedido en la aplicación, permitiendo así al usuario poder acceder y pinear las facturas más importantes de la aplicación.

Esta última parte es muy importante pues permite que las aplicaciones se integren perfectamente con la nueva Taskbar explotando toda la potencia de la nueva Experiencia de Usuario de Windows 7.

En el próximo artículo veremos cómo podemos acceder a toda esta información desde código, tanto nativo como administrado.