Twitter Facebook Google + RSS Feed

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

1
WPF

Martes, 18 de agosto de 2009 a las 18:07hs por Dario Krapp

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.


1 comentario »

  1. Jose dice:

    Hola! Muy bueno el articulo, en este momento voy a probar las tres formas que el estado del origen de los datos a cambiado. Por favor sigue escribiendo mas sobre WPF

Deja un comentario

Buscar