Archivo de la categoría 'WPF'


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)

Hipervínculo en WPF

Lunes, 14 Sep, 2009 @ 23:37 | Por Gustavo Cantero (The Wolf) | WPF

Muchas veces a las aplicaciones de escritorio queremos darle algún diseño o detalle parecido al de las páginas web, por ejemplo, un hipervínculo que abra una página. Para esto WPF tiene un elemento llamado Hyperlink que facilita esta tarea, el cual puede ir dentro de un Paragraph o TextBlock. Un ejemplo de la utilización de este elemento se muestra a continuación:

<TextBlock>
<Hyperlink NavigateUri="http://www.scientia.com.ar">Scientia® Soluciones Informáticas</Hyperlink>
</TextBlock>

El inconveniente que posee este elemento es que fue pensado para utilizarse cuando está dentro de un NavigationWindow, Frame o corriendo en un XBAP dentro de un navegador. Si utilizamos este elemento sin alguno de estos contenedores, al pulsar sobre el enlace simplemente no hará nada. Para solucionar este pequeño problema podemos utilizar el evento RequestNavigate para hacer que al pulsar sobre él se abra el navegador por defecto en la dirección ingresada. El código a continuación muestra cómo quedaría el XAML:

<TextBlock>
<Hyperlink NavigateUri="http://www.scientia.com.ar"  RequestNavigate="Hyperlink_RequestNavigate">Scientia® Soluciones Informáticas</Hyperlink>
</TextBlock>

y acá muestro el handler del evento:

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

Espero que les sea de utilidad.

VN:F [1.7.3_972]
Rating: 5.0/10 (1 voto cast)

Notificación de cambios en enlace a Data Objects con WPF

Martes, 18 Ago, 2009 @ 18:07 | Por Dario Krapp | WPF

En un artículo previo llamado “Enlace a Data Objects en WPF” hemos comentado de que manera es posible enlazar un objeto de datos con uno o varios elementos WPF, hemos utilizado el markup Binding y modificado las propiedades Mode y UpdateSourceTrigger para controlar la transferencia de datos entre los objetos origen y destino.
Retomaremos desde el punto en el que habíamos quedado.
En nuestro articulo previo poseíamos la clase

public class Empl
{
   public int IDEmpleado { set; get; }
   public long? DNI { set; get; }
   public string Apellidos { set; get; }
   public string Nombres { set; get; }
   public DateTime FechaIngeso { set; get; }
}


La siguiente porción de XAML:

<StackPanel x:Name="stackPanelEmpl">
   <TextBox x:Name="txtIDEmpleado" Text="{Binding IDEmpleado}">   </TextBox>
    <TextBox x:Name="txtDNI" Text="{Binding DNI}"></TextBox>
    <TextBox x:Name="txtApellidos" Text="{Binding Apellidos}"> </TextBox>
    <TextBox x:Name="txtNombres" Text="{Binding Nombres}"></TextBox>
   <TextBox x:Name="txtFechaIngreso" Text="{Binding FechaIngeso}"></TextBox>
</StackPanel>


Y las siguientes líneas de código

 objE = new Empl()
   {
      Apellidos = "Perez",
      DNI = 29111222,
      FechaIngeso = new DateTime(2000, 11, 19),
      IDEmpleado = 123,
      Nombres = "Juan"
   };

    stackPanelEmpl.DataContext = objE;


El resultado de la ejecución de estas líneas era el enlace del objeto objE a los TextBoxes contenidos en el elemento stackPanelEmpl, pero este enlace tenía un problema potencial, si alguna propiedad del objeto objE era modificada externamente, el cambio no sería transferido a la interfaz gráfica. Un detalle que no habíamos comentado previamente es que la transferencia de datos desde el objeto origen hacia el objeto destino es siempre inmediato, la única excepción es que el Mode haya sido definido como OneTime, es importante recordar que la propiedad UpdateSourceTrigger (como su nombre da a entender) definirá como se actualizará la transferencia de datos en sentido inverso, o sea desde el objeto destino hacia el objeto origen, pero como ya mencionamos hace apenas dos líneas de texto la transferencia de datos desde el objeto origen hacia el objeto destino es siempre inmediata.

En este artículo nos centraremos en resolver el problema de avisar a la interfaz gráfica que una propiedad ha sido modificada y dejar que WPF haga el trabajo.

Quizás, aunque no lo parezca inicialmente, este sea un buen momento para mencionar que son las dependency properties. El uso de propiedades en los lenguajes de programación modernos no es para nada nuevo, se han venido utilizando en tecnologías previas desde hace varios años, pero WPF ha llegado con un nuevo tipo de propiedades denominadas dependecy properties, la diferencia fundamental es que en las dependency properties el valor de las mismas podría depender valores definidos en otros objetos, o sea provenir de diversas fuentes, por ejemplo de definiciones implícitas en el elemento, tanto como de la definición de las mismas en Templates, en Styles, en Resources, en Bindings, en herencia de sus elementos padre, etc. Al ser el valor de las propiedades de los elementos dependientes de tantos factores, no es una mala idea llevar a las propiedades a la mesa de dibujo e intentar redefinir una implementación que sea más eficiente en este contexto y agregarle de paso algunas características extra, como notificación de cambios, validación, etc.

Al utilizarse dependency properties se notará que se emplean como propiedades convencionales, esto no debe engañarnos, en WPF para cada dependency property existirá una propiedad convencional que funcionará como un wrapper y permitirá simplificar su utilización, pero no existe ninguna similitud. Por empezar las dependency properties serán manejadas y mantenidas por WPF, las mismas deberán declararse en un campo estático, público y de solo lectura. Además deberán registrarse, el siguiente ejemplo muestra la declaración de una dependency property en nuestra clase de ejemplo Empl destinada a almacenar los nombres del empleado:

public class Empl
   {
      public static readonly DependencyProperty NombresProperty = DependencyProperty.Register("Nombres", typeof(string), typeof(Empl));


La Dependency property ha sido llamada NombresProperty (el sufijo Property es una convención de WPF) y se ha registrado utilizando el método estático Register de la clase DependencyProperty, los parámetros pasados serán el nombre de la propiedad, el tipo de la misma y el tipo de la clase que registrará la propiedad. Como mencionamos previamente WPF se encargará de manejar y mantener el estado de las dependency properties, por ese motivo las mismas deberán registrarse (en el sistema de propiedades de WPF) y ser accesibles a nivel de clase (readonly asegurará que la dependency property no sea reasignada).

Por el momento solo hemos definido la dependency property, debemos ahora dar el proximo paso que consiste en brindar la posibilidad de establecer y leer su valor, en este punto toma importancia una clase llamada DependencyObject que representará a un objeto que formará parte del sistema de propiedades de WPF y nos brindará métodos para manejar (establecer o leer valores) la dependency property que ya hemos creado. Nuestra clase tomará la siguiente forma:

public class Empl: DependencyObject
   {
      public static readonly DependencyProperty NombresProperty = DependencyProperty.Register("Nombres", typeof(string), typeof(Empl));
      public string Nombres
      {
         set
            {
                SetValue(NombresProperty, value);
            }
            get
            {
                return GetValue(NombresProperty) as string;
            }
        }

	..

	....


Hemos heredado de la clase DependencyObject y hemos utilizado los métodos SetValue y GetValue que la misma nos ha proporcionado para manejar los valores de la dependency property, tambien hemos creado una propiedad wrapper denominada Nombres para manejar los valores de nuestra dependency property NombresProperty en forma más amigable.

La pregunta después de tanto código podría ser por que estuvimos hablando de dependency properties e hicimos todo esto. Pero en este caso (no como siempre sucede) hay un buen motivo, seguramente a estas alturas será de suponerse que utilizar
dependency properties nos permitirá resolver el problema que habíamos dejado pendiente, y efectivamente sucede de esa forma.
Con estas modificaciones la clase Empl podrá informar a elementos de la interfaz gráfica que su propiedad Nombres ha sido modificada, si en este momento la propiedad es modificada externamente, la interfaz gráfica notará el cambio inmediatamente. Ahora solo se deberá aplicar esta solución al resto de las propiedades.

Quienes hayan leído el artículo previo quizás recordarán que al mencionar las dependency properties comentamos que durante su definición era posible establecer los valores por defecto para las propiedades Mode y UpdateSourceTrigger utilizadas en el markup Binding.
Para poder definir estos valores, el método Register que ya hemos utilizado previamente cuenta con una sobrecarga que tomará un parámetro del tipo PropertyMetadata dónde podremos definir (junto con una infinidad de opciones), estas propiedades (en realidad utilizaremos un objeto del tipo FrameworkPropertyMetadata que heredará de PropertyMetadata), el código a continuación muestra una posibilidad para establecer estos valores.

public class Empl : DependencyObject
   {
        public static readonly DependencyProperty NombresProperty;

        static Empl()
        {
            FrameworkPropertyMetadata meta = new FrameworkPropertyMetadata()
            {
                BindsTwoWayByDefault = true,
                DefaultUpdateSourceTrigger = UpdateSourceTrigger.LostFocus
            };
            NombresProperty = DependencyProperty.Register("Nombres", typeof(string), typeof(Empl), meta);
        }

	..

	....


Parecería que todo ha quedado resuelto, pero no estará de más preguntarse que pasaria si no nos fuera posible hacer que nuestra clase Empl heredará de DependencyObject (lo cual no es una suposición muy inesperada). En este caso contaremos con otra posibilidad que nos permitirá obtener el mismo resultado, pero esta vez utilizando una interfaz.

La interfaz llamada INotifyPropertyChanged nos brindará un evento denominado PropertyChanged que deberemos invocar al momento de informar que propiedad ha cambiado, en este caso no utilizaremos dependency properties, solamente deberemos invocar al evento pasándole por parámetros el nombre de la propiedad que desearemos informar que ha sido modificada y una referencia al objeto que informará la modificación.
El código para nuestro caso será el siguiente:

public class Empl : INotifyPropertyChanged
    {
        string strNombre;
        public event PropertyChangedEventHandler PropertyChanged;

        public string Nombres
        {
            set
            {
                if (value != strNombre)
                {
                    strNombre = value;
                    if (PropertyChanged != null)
                        PropertyChanged(this, new PropertyChangedEventArgs("Nombres"));
                }
            }
            get
            {
                return strNombre;
            }
        }


Nuevamente será necesario extender la funcionalidad a cada propiedad que deseemos informar que ha sido modificada.

Finalmente cabe mencionar que existe una tercera posibilidad, la cual no requiere herencia ni interfaces. En este caso se deberá crear un evento a partir del delegado EventHandler y dispararlo cuando la propiedad haya sido modificada, en nuestro caso deberíamos utilizar las siguientes líneas de código:

    public class Empl
    {
        string strNombre;
        public event EventHandler NombresChanged = delegate { };

        public string Nombres
        {
            set
            {
                if (value != strNombre)
                {
                    strNombre = value;
                    NombresChanged(this, new EventArgs());
                }
            }
            get
            {
                return strNombre;
            }
        }


Un detalle fundamental es que para que el cambio sea informado el nombre del evento no podremos definirlo a nuestro gusto, el mismo deberá llamarse: nombre de propiedad + “Changed”, de aquí que en nuestro caso nos vimos obligados a definirlo como NombresChanged, que a decir verdad, no suena de lo mejor.

Como es de costumbre, espero que este artículo haya sido de utilidad y complemente los puntos pendiente del artículo anterior.

VN:F [1.7.3_972]
Rating: 8.0/10 (1 voto cast)

Enlace a Data Objects en WPF

Martes, 14 Jul, 2009 @ 12:28 | Por Dario Krapp | WPF

En WPF existe el concepto y la funcionalidad de enlace a datos, al igual que sucedía en tecnologías previas de Microsoft, aunque estos conceptos han evolucionado aun más permitiendo el enlace entre elementos, el enlace a recursos, a objetos ADO.NET, a Linq y a objetos de datos (también conocidos como DataObjects) en este articulo veremos el enlace a este último tipo de objeto.

Un objeto de datos representará una entidad la cual tendrá interacción con la interfaz gráfica, es muy común que dichas entidades sean persistentes, esto significa que sin importar el mecanismo de acceso a datos que se emplee, esta información terminará guardándose y recuperándose de una base de datos, aunque este es un detalle que quedará más allá del objeto de datos (o al menos así debería ser). El objeto de datos inicialmente será no más que una clase con propiedades que modelará a una entidad.
Estoy convencido que será una buena idea crear una clase lo más sencilla posible que utilizaremos como objeto de datos desde aquí en adelante.

Definiremos entonces una clase llamada Empl, que pretenderá con mucha imaginación representar a un empleado.

public class Empl
{
 public int IDEmpleado { set; get; }
 public long? DNI { set; get; }
 public string Apellidos { set; get; }
 public string Nombres { set; get; }
 public DateTime FechaIngeso { set; get; }
}

En nuestra intención de simplificar le hemos agregado solamente unos pocos campos, un identificador, un número de documento o cédula, sus nombres y apellidos y la fecha de ingreso.

En primera instancia utilizaremos esta clase para que los datos de un empleado puedan ser mostrados en la interfaz gráfica. Inicialmente intentaremos mostrar solo un dato, utilizaremos entonces un elemento de XAML denominado TextBox, que definiremos a continuación.

<TextBox x:Name="txtApe" Text="Apellido"></TextBox>

Esta porción de XAML definirá un elemento que mostrará el texto “Apellido” lo cual, como estarán convencidos, está bastante alejado de lo que deseamos hacer, pero sirve para hacer algunas observaciones, la primera es que XAML es un lenguaje basado en XML y diseñado para definir interfaces gráficas (espero que esto no sea una sorpresa), de aquí podemos deducir que la definición de la interfaz grafica estará constituida por nodos y atributos, el valor de cada atributo en el XAML definirá el valor de una propiedad del elemento que se está definiendo, en el ejemplo el atributo Text definirá que el elemento (un TextBox) con nombre txtApe tomará el valor “Apellido” en su propiedad Text.

Esto funcionando de esta forma estará perfectamente bien definido, pero será insuficiente debido a que todas las propiedades deberían tomar valores fijos, lo que haría a XAML muy poco práctico, en nuestro caso para dar un ejemplo deseábamos mostrar un apellido que dependerá del valor de una propiedad de un objeto y con lo que hemos visto hasta ahora hacer esto no es factible.

Es de esperarse entonces que exista una forma de definir valores de atributos dinámicos, WPF utilizará los caracteres “{” y “}” para indicar que el valor del atributo no es fijo, este concepto es denominado “Markup” y existe una amplia variedad de markups en XAML y WPF. Para nuestro caso estaremos interesados en definir un markup que indicará que la propiedad Text de nuestro TextBox estará enlazada a la propiedad Apellidos de un objeto que suministraremos posteriormente, el markup capaz de efectuar dicha operación es el siguiente:

<TextBox x:Name="txtApe" Text="{Binding Path=Apellidos}" />

Lo que indicamos en la línea previa es que deseamos asignar a la propiedad Text del elemento txtApe, un enlace a la propiedad Apellidos de un objeto. Una forma simplificada de definir lo mismo es la siguiente:

<TextBox x:Name="txtApe" Text="{Binding Apellidos}" />

Con esto hemos resuelto una parte del problema, debemos luego llevar a cabo el enlace con el objeto, en este momento es donde la propiedad DataContext se vuelve importante, la propiedad DataContext tomará el objeto (fuente de datos) que se enlazará con el elemento, de esta forma al escribir el código :

Empl objE = new Empl()
{
 Apellidos = "Perez",
 DNI = 29111222,
 FechaIngeso = new DateTime(2000, 11, 19),
 IDEmpleado = 123,
 Nombres = "Juan"
};
txtApe.DataContext = objE;

El objeto objE se enlazará al elemento txtApe (cuyo markup se ha definido como “{Binding Apellidos}”), el resultado final será que el valor definido en la clase para la propiedad Apellidos será enlazado a la propiedad Text del elemento txtApe.

En este punto tenemos que tomar en cuenta otra consideración y es que la propiedad DataContext es una dependency property, y tratando de no entrar en muchos detalles sobre este punto destacaremos que en WPF existen un tipo de propiedades denominadas dependency properties, y la diferencia fundamental con las propiedades convencionales es que sus valores podrían ser (además de accesibles por el propio elemento que las define) ser accesibles y por todos sus elementos hijos en el árbol lógico de elementos.

Recordemos que XAML está basado sobre XML y que cada documento XML podría representarse como un árbol, en el caso de XAML, un documento XAML definirá entre otras cosas un árbol lógico de elementos, por ejemplo la definición:

<Grid x:Name="e1">
  <StackPanel x:Name="e2">
    <TextBox x:Name="e3" ...></TextBox>
    <TextBox x:Name="e4" ...></TextBox>
  </StackPanel>
</Grid>

Indicará que el elemento e1 posee un hijo denominado e2 el cual a la vez posee los hijos e3 y e4, si la propiedad DataContext es definida en el elemento e1, su valor será accesible para el elemento e2, tanto como para los elementos e3 y e4. Las dependency properties son muy comunes en WPF y su objetivo es simplificar el código y mejorar la performance.
Por lo visto anteriormente podríamos hacer los siguientes reemplazos:

<StackPanel x:Name="stackPanelEmpl">
  <TextBox x:Name="txtApe" Text="{Binding Apellidos}"></TextBox>
  </StackPanel>
stackPanelEmpl.DataContext = objE;

Y obtendríamos el mismo resultado, ya que el DataContext definido en el elemento stackPanelEmpl es accedido por el elemento txtApe.
No caben dudas de adonde queríamos llegar con estos pasos, ahora definir el resto de los campos es muy sencillo, solo deberemos agregar más elementos y enlazar las propiedades adecuadamente.

<StackPanel x:Name="stackPanelEmpl">
  <TextBox x:Name="txtIDEmpleado" Text="{Binding IDEmpleado}"></TextBox>
  <TextBox x:Name="txtDNI" Text="{Binding DNI}"></TextBox>
  <TextBox x:Name="txtApellidos" Text="{Binding Apellidos}"></TextBox>
  <TextBox x:Name="txtNombres" Text="{Binding Nombres}"></TextBox>
  <TextBox x:Name="txtFechaIngreso" Text="{Binding FechaIngeso}"></TextBox>
</StackPanel>

Con esto hemos conseguido mostrar los valores lo cual no es poco, pero tampoco es suficiente, ya que frecuentemente desearemos que el camino sea de ida y vuelta, o sea que el usuario pueda modificar valores en la interfaz gráfica y los cambios sean transferidos a la clase subyacente.

Si se verifican los valores del objeto objE, se verá que cuando los valores son modificados en los elementos TextBox, la clase será automáticamente actualizada (hay un detalle sobre esta actualización que veremos posteriormente), para verificar este comportamiento deberá definirse un elemento Button que muestre los valores de objE al ser presionado, luego explicaremos la causa de esta imposición para mostrar los valores. Hay quienes podrían preguntarse como esta magia de actualización funciona, y lo bueno es que hay una explicación relativamente simple.

Cuando hemos definido el markup Binding, si bien todo funciona, nos han quedado algunas fichas fuera del tablero, la primera es el modo de Binding (Mode) y la segunda el modo de actualización (UpdateSourceTrigger).

Antes de ver estos términos es buena idea analizar a los actores que intervienen en un enlace, los mismos son un objeto origen, un objeto destino y un valor o datos que serán transferidos desde el objeto origen hacia el objeto destino, en nuestro ejemplo el objeto objE es la fuente y el elemento TextBox txtApellidos es el destino, el Valor “Perez” representará los datos que se transferirán desde el origen (objE) hacia el destino (txtApellidos), el modo de Binding que se definirá en el markup Binding bajo el atributo Mode, podrá definirse de la forma:

  • “de ida” (OneWay)
  • “de ida y vuelta” (TwoWay)
  • “de vuelta” (OneWayToSource)
  • “Solo una vez” (oneTime)
  • “Valor definido por defecto en la dependency property” (Default)

En el caso de “de ida” (cuando el atributo Mode toma el valor OneWay) los valores podrán transferirse solo desde el origen hacia el destino, en el caso “de ida y vuelta” (TwoWay) los datos podrán transferirse en ambos sentidos, y en el caso “de vuelta” (OneTimeToSource) los datos solo podrán transferirse desde destino hacia el origen.

Viendo esto último podemos deducir que alguien está definiendo que en el caso de los Textboxes el enlace será del tipo TwoWay, la deducción el correcta y ese alguien es justamente la propiedad Text del elemento TextBox que es también una dependency property, (de hecho solo las dependency properties aceptan el markup Binding) , y una vez más sin entrar en detalles sobre las dependency properties solo comentaremos que en una dependency property es posible definir el Mode por defecto que la misma asumirá durante un enlace, pero podemos redefinir el valor por defecto de la siguiente forma:

<TextBox Margin="3" x:Name="txtApellidos" Text="{Binding Apellidos, Mode=OneWay}" />

Luego de hacer este cambio, por más que modifiquemos el apellido en el TextBox txtApellidos, la propiedad Apellidos en el objeto objE no se verá afectada.

Finalmente nos queda por ver el modo de actualización del markup Binding y una buena forma es probar una porción de código para entender cómo funciona, Dijimos previamente que la propiedad Text de elemento TextBox estaba definida por defecto con Mode TwoWay y hemos comprobado que al modificar un valor en el TextBox y luego presionar un botón, el valor de la propiedad de objE era efectivamente modificado, entonces podríamos desear hacer la misma modificación pero esta vez vía código, elegiremos el TextBox txtNombres y ejecutaremos el siguiente código, (por ejemplo al presionar un botón):

objE = new Empl()
{
  Apellidos = "Perez",
  DNI = 29111222,
  FechaIngeso = new DateTime(2000, 11, 19),
  IDEmpleado = 123,
  Nombres = "Juan"
};

stackPanelEmpl.DataContext = objE;
txtNombres.Text = "Modificado";
MessageBox.Show(objE.ToString());

Para nuestra sorpresa veremos que la propiedad Nombres de objE no ha tomado el valor “Modificado” (para que el ejemplo funcione deberá sobrescribirse el método ToString() de la clase Empl para que los valores de sus campos sean devueltos). Pero modificaremos un poco el código y agregaremos un par de líneas extra.

objE = new Empl()
{
  Apellidos = "Perez",
  DNI = 29111222,
  FechaIngeso = new DateTime(2000, 11, 19),
  IDEmpleado = 123,
  Nombres = "Juan"
};

stackPanelEmpl.DataContext = objE;
txtNombres.Focus();
txtNombres.Text = "Modificado";
txtDNI.Focus();
MessageBox.Show(objE.ToString());

En este caso el valor será modificado. De esta prueba podríamos deducir que el valor es actualizado desde el destino hacia el origen cuando el elemento destino pierde el foco y es efectivamente de esa manera (ahora quizás se entienda por que impusimos que debiera tener que presionarse un Button para que los valores fueran mostrados luego de ser modificados en algún TextBox). La propiedad UpdateSourceTrigger definirá de que manera los datos serán modificados en el objeto origen, para que esta propiedad tenga sentido es claro que el Mode deberá ser TwoWay o OneWayToSource, ya que son los casos que admiten la modificación de objeto origen.

Las opciones de UpdateSourceTrigger son:

  • “Cuando el elemento pierde el foco” (LostFocus)
  • “Cuando la propiedad cambia” (PropertyChanged)
  • “Especificado vía código” (Explicit)
  • “Valor definido por defecto en la dependency property” (Default)

Si modificamos el XAML de la siguiente forma:

<TextBox Margin="3" x:Name="txtNombres" Text="{Binding Nombres, UpdateSourceTrigger=PropertyChanged}"></TextBox>

Veremos que ya no es necesario manejar el foco del elemento txtNombres para que la propiedad sea actualizada, nuevamente es la propiedad Text del elemento TextBox es quien define que el modo de actualización por defecto será LostFocus y nadie dudará que este caso la opción mas lógica.

Si se utiliza la opción “Explicit” por ejemplo en el TexBox DNI de la siguiente forma:

<TextBox Margin="3" x:Name="txtDNI" Text="{Binding DNI, UpdateSourceTrigger=Explicit}" />

Los cambios tomarán efecto recién cuando se ejecute por código lo siguiente:

txtDNI.GetBindingExpression(TextBox.TextProperty).UpdateSource();

Ya hemos podido resolver el problema que nos habíamos propuesto, enlazar un objeto de datos a un grupo de elementos de XAML y definir de qué forma deseamos que se comporte la actualización de datos. Dejamos para el final del artículo dos puntos que no habían sido mencionados previamente relacionados con el Mode OneTime y el Mode OneWayToSource, el Mode OneTime creará un enlace desde el objeto origen hacia el objeto destino pero con la particularidad que se transferirá solamente el primer valor que el objeto origen tome, posteriores cambios en el objeto origen serán ignorados, de allí el sugerente nombre (OneTime).

En cuanto al Mode OneWayToSource parecería en primera instancia que su existencia no tiene mucho sentido, ya que podría deducirse que si se intercambian (swap) los objetos origen por destino y se establece el Mode como OneWay no habría diferencias. En realidad no es exactamente de esta forma ya que como habíamos mencionado previamente la propiedad utilizada como destino del enlace deberá ser una dependency property, pero no la propiedad origen, (por ejemplo la propiedad Apellidos de la clase Empl no es una dependency property, la hemos definido como una propiedad convencional). Entonces este tipo de enlace permitirá modificar propiedades que no son dependency properties en un solo sentido, desde la propiedad destino hacia la propiedad fuente.

Todo este esquema que hemos desarrollado funcionará bien cuando la interacción sea exclusivamente entre la interfaz gráfica y un objeto de datos, pero en escenarios de mundo real es posible que un objeto de datos que se encuentra ya enlazado a la interfaz gráfica sea modificado externamente, una forma de simular este comportamiento es agregar un botón que modifique alguna propiedad del objeto objE luego que el enlace ha sido establecido, en este caso el objeto objE no será capaz de informar a la interfaz gráfica que una de sus propiedad ha sido modificada. Esto no es fatal, en el próximo artículo veremos que existen varias posibilidades para resolver este y otros tipos de problemas de enlace de datos. Espero como siempre que este articulo haya sido de utilidad.

VN:F [1.7.3_972]
Rating: 8.8/10 (6 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)