Archivo de la categoría 'Sistemas Operativos'


Barra de progreso en el ícono de la aplicación en la barra de tareas de Windows® 7

Martes, 15 Sep, 2009 @ 23:46 | Por Gustavo Cantero (The Wolf) | WPF, Windows 7, Windows Forms

Los que tienen la posibilidad de utilizar Windows 7 habrán notado que trae varias novedades como la vista en miniatura de las aplicaciones minimizadas al pasar el mouse sobre ellas, que algunas de éstas, como el Windows Media Player, en esa vista poseen botones, y otras como el Internet Explorer utilizan el ícono de la barra de tareas como indicador de progreso (en el IE se utiliza para mostrar el progreso de las descargas). Todas estas características y algunas más que aquí no nombro (por ejemplo, hay una característica gracias a la cual, con un dispositivo de tal sólo u$s 30, se puede medir la luz ambiental y, si lo programamos, se puede cambiar el tema de pantalla dependiendo de la misma) son programables a través de las API del sistema operativo para brindarle a nuestras aplicaciones todas las posibilidades que posee. En este artículo hablaré de la posibilidad de mostrar el progreso de una operación como fondo del ícono de la misma en la barra de tareas.
Las aplicaciones que corren sobre Windows 7 pueden mostrar el progreso de una tarea que están ejecutando en el ícono de la barra de tareas, evitando de esta manera que el usuario tenga que abrir la ventana de la misma para verificar el estado del proceso. A continuación detallo los posibles estados que se pueden mostrar:

Aplicación sin barra de progreso Aplicación sin barra de progreso
Aplicación con estado normal Aplicación con estado normal y 50% de progreso
Aplicación con estado pausado Aplicación con estado pausado y 50% de progreso
Aplicación con error Aplicación con error y 50% de progreso
Aplicación con estado indeterminado Aplicación con estado indeterminado

Para modificar el estado y el valor de progreso de nuestras aplicaciones podemos utilizar la interfaz ITaskbarList3 como se muestra a continuación

ITaskbarList3* pTL; //creado anteriormente
HRESULT hr = pTL->SetProgressState(hwnd, TBPF_NORMAL); //hwnd es el puntero de la ventana
pTL->SetProgressValue(50, 100); //Establecemos un 50% de progreso

Para los que desarrollamos en .NET este código en C no nos sirve de mucho, entonces podemos referenciar las clases e interfaces COM o, mejor aún, podemos utilizar las librerías “Windows® API Code Pack for Microsoft® .NET Framework” que ya tienen resuelto este tema. Estas librerías, junto con su código fuente y ejemplos, las podemos bajar de la siguiente dirección: http://code.msdn.microsoft.com/WindowsAPICodePack. Para cambiar el estado y progreso de nuestra aplicación debemos utilizar los métodos SetProgressState y SetProgressValue de la clase Microsoft.WindowsAPICodePack.Taskbar.TaskbarManager respectivamente.
A continuación muestro un ejemplo en WPF de un proyecto en el cual se cambia el progreso de la aplicación modificando el valor de un control Slider, y el estado de la misma a través de un ComboBox. El XAML de éste es el siguiente:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="TaskBarProgress.Window1"
    Title="Ejemplo de barra de progreso en la barra de tareas" Height="170" Width="400"
    WindowStartupLocation="CenterScreen" WindowStyle="SingleBorderWindow" MaxHeight="170"
    MinHeight="170" MinWidth="320" Icon="Scientia.ico">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" MinWidth="178" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition Height="Auto" MinHeight="36" />
        </Grid.RowDefinitions>
        <TextBlock HorizontalAlignment="Right" VerticalAlignment="Center" FontSize="15" FontWeight="Bold" Margin="5" Text="Estado de la aplicación:" />
        <ComboBox x:Name="ComboBox1" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Stretch" Margin="5" SelectionChanged="ComboBox_SelectionChanged">
            <ComboBoxItem>Normal</ComboBoxItem>
            <ComboBoxItem>Pausado</ComboBoxItem>
            <ComboBoxItem>Con error</ComboBoxItem>
            <ComboBoxItem>Indeterminado</ComboBoxItem>
            <ComboBoxItem IsSelected="True">Sin progreso</ComboBoxItem>
        </ComboBox>
        <TextBlock HorizontalAlignment="Right" FontSize="15" FontWeight="Bold" Grid.Row="1" VerticalAlignment="Center" Text="Porcentaje de progreso:" Margin="5" />
        <Slider x:Name="Slider1" Grid.Column="1" Grid.Row="1" VerticalAlignment="Center" Margin="5" SmallChange="1" Maximum="100" LargeChange="10" TickPlacement="Both" TickFrequency="5" ValueChanged="Slider_ValueChanged" />
        <TextBlock Grid.Row="2" Grid.ColumnSpan="2" HorizontalAlignment="Center" Margin="10"><Run Text="Desarrollado por "/><Hyperlink NavigateUri="http://www.scientia.com.ar" RequestNavigate="Hyperlink_RequestNavigate"><Run Text="Scientia® Soluciones Informáticas"/></Hyperlink></TextBlock>
    </Grid>
</Window>

Y el código C# es el que muestro a continuación:

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.WindowsAPICodePack.Taskbar;

namespace TaskBarProgress
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        private TaskbarManager windowsTaskbar = TaskbarManager.Instance;

        public Window1()
        {
            if ((Environment.OSVersion.Version.Major < 6) ||
               (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor < 1))
            {
                MessageBox.Show("Esta aplicación de ejemplo requiere Windows® 7 o superior para correr",
                    "Scientia® Soluciones Informáticas",
                    MessageBoxButton.OK,
                    MessageBoxImage.Error);
                Close();
            }
            else
                InitializeComponent();
        }

        private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
        {
            Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
            e.Handled = true;
        }

        private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (ComboBox1.SelectedIndex != 4)
                windowsTaskbar.SetProgressValue(Convert.ToInt32(e.NewValue), 100, this);
        }

        private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            switch ((sender as ComboBox).SelectedIndex)
            {
                case 0: //Normal
                    windowsTaskbar.SetProgressState(TaskbarProgressBarState.Normal, this);
                    break;
                case 1: //Pausado
                    windowsTaskbar.SetProgressState(TaskbarProgressBarState.Paused, this);
                    break;
                case 2: //Con error
                    windowsTaskbar.SetProgressState(TaskbarProgressBarState.Error, this);
                    break;
                case 3: //Indeterminado
                    windowsTaskbar.SetProgressState(TaskbarProgressBarState.Indeterminate, this);
                    return;
                case 4: //Sin progreso
                    windowsTaskbar.SetProgressState(TaskbarProgressBarState.NoProgress, this);
                    return;
            }

            windowsTaskbar.SetProgressValue(Convert.ToInt32(Slider1.Value), 100, this);
        }
    }
}

Obviamente todo el código mostrado funciona solamente sobre Windows® 7 por cual, si queremos hacer una aplicación que también sea compatible con versiones anteriores del sistema operativo, nos conviene verificar la versión de éste antes de utilizar estas características. Esto se puede hacer de la siguiente forma:

if ((Environment.OSVersion.Version.Major > 6) ||
    (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor >= 1)) {
        //Es Windows 7 o superior
} else {
        //Es un sistema operativo anterior a Windows® 7
}

Proyecto de ejemplo de la utilización de la API de Windows 7 para cambiar el estado y progreso de una aplicaciónPor último les dejo una solución con un proyecto WPF y otro con Windows Forms donde se muestra como utilizar esta funcionalidad.

VN:F [1.7.3_972]
Rating: 0.0/10 (0 votos cast)

Efecto “Aero Glass” con Windows Presentation Foundation

Lunes, 01 Dic, 2008 @ 23:50 | Por Gustavo Cantero (The Wolf) | WPF, Windows Vista

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.

Efecto Glass en Windows Media Player


Ejemplo de Glass en Internet Explorer

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));

Ejemplo de Glass

Por último, en el siguiente enlace les dejo un proyecto de ejemplo funcionando para que puedan probarlo.
Proyecto de ejemplo de implementación del efecto Glass

VN:F [1.7.3_972]
Rating: 9.0/10 (5 votos cast)