Enlace via código de grillas en ASP.NET

Lunes, 03 may, 2010 @ 14:28 | Por Dario Krapp | ASP.NET

Prólogo e inicios

En un artículo previo denominado Enlace de elementos en controles de lista en ASP.NET vimos las posibilidades que ASP.NET ofrecía para enlazar orígenes de datos a controles de lista y realizamos enlaces vía código y vía los elementos SqlDataSource y ObjectDataSource sin codificar ni una línea en los dos últimos casos.

En este artículo, daremos un próximo paso y no solo mostraremos información de un origen de datos en un control ASP.NET, sino que también veremos la forma de transferir los cambios realizados por el usuario sobre dichos datos nuevamente hacia el origen de datos. Para realizar estas tareas la funcionalidad proporcionada por los controles de lista no nos será suficiente, por lo que aprovecharemos la oportunidad para conocer uno de los controles más populares de ASP.NET, el control GridView el cual renderizará, como no debería sorprendernos, una grilla con datos.

Debido a que el control GridView es extremadamente flexible y ofrece una considerable variedad de posibilidades, como no queremos que el artículo se extienda demasiado, nos ocuparemos ver todos estos temas mencionados realizando el enlace vía código, ya que no hay tantos ejemplos (o al menos eso me parece), como los que pueden encontrarse contra enlaces a los objetos SqlDataSource y ObjectDataSource.

Para comenzar, dado que el artículo trata sobre enlace, diremos que en nuestro escenario contaremos con:

  • Un origen de datos (datos a enlazar).
  • Un control ASP.NET (GridView) destinado a enlazar y mostrar
    en la interfaz de usuario a los elementos del origen de datos.

Nuestro interés será en esta etapa simplemente enlazar el origen de datos al control GridView.
Para poder realizar esta tarea, el control GridView ofrecerá una propiedad denominada DataSource en la cual deberá establecerse el origen de datos y todo quedará casi listo, un detalle a considerar es que podrán enlazarse elementos que implementen algunas de las siguientes interfaces: ICollection, IEnumerable o IListSource.
Seguramente una duda razonable será como se renderizará el control al efectuarse el enlace. En el caso de enlazar un objeto que implemente ICollection generará una fila por cada elemento de la colección y una columna por cada propiedad pública que el elemento posea.

A continuación y a modo de ejemplo enlazaremos una lista de elementos personalizados a un control GridView, utilizaremos como origen de datos un elemento del tipo List

<asp:GridView runat="server" ID="GV1" />
public class MiElem
    {
        public string Valor1 { set; get; }
        public int Valor2 { set; get; }
        public DateTime Valor3 { set; get; }
        public bool Valor4 { set; get; }
        public string Valor5 { set; get; }
        public string Valor6 { set; get; }
    }

protected override void OnPreRender(EventArgs e)
{
    base.OnPreRender(e);
    BindGrid(GV1);
}

private void BindGrid(GridView Grd)
{
    List<MiElem> Lst = new List<MiElem>();
    Lst.Add(new MiElem()
    {
         Valor1 = "V1",
         Valor2 = 1,
         Valor3 = DateTime.Now.AddDays(1),
         Valor4 = true,
         Valor5 = "imagen1.gif",
         Valor6 = @"~\Imagenes\imagen1.gif"
     });
     Lst.Add(new MiElem()
     {
         Valor1 = "V2",
         Valor2 = 2,
         Valor3 = DateTime.Now.AddDays(2),
         Valor4 = true,
         Valor5 = "imagen1.gif",
         Valor6 = @"~\Imagenes\imagen1.gif"
     });
     Lst.Add(new MiElem()
     {
         Valor1 = "V3",
         Valor2 = 3,
         Valor3 = DateTime.Now.AddDays(3),
         Valor4 = true,
         Valor5 = "imagen1.gif",
         Valor6 = @"~\Imagenes\imagen1.gif"
     });
     Grd.DataSource = Lst;
     Grd.DataBind();
 }

Como puede verse:



El control GridView ha renderizando una grilla y ha generando, como anticipamos, una fila por cada elemento enlazado y una columna por cada propiedad pública del mismo.

Un detalle a mencionar es que el método DataBind() será quien realizará el traspaso de datos desde el origen de datos hacia el control, si el mismo es obviado los datos no serán transferidos y la grilla quedará vacía. Por otra parte cualquier modificación que se realice sobre la lista “Lst” posterior a la invocación al método DataBind() no será transferida al control y no será reflejada en el mismo.

Volviendo al ejemplo, cabe mencionar que lo que ha hecho el control es utilizar reflection para conocer las propiedades públicas de los elementos que componen la lista Lst (en realidad del primer elemento de la lista) y ha autogenerado una columna para cada una de ellas, además ha enlazado a cada elemento en una fila (ubicando el valor de cada propiedad en la columna correspondiente).

Luego de la impresión inicial, es claro que este comportamiento no nos será demasiado útil en la mayoría de los casos, al menos eso me parece, ya que posiblemente si deseamos enlazar un grupo de elementos, en muchos casos se deseará visualizar solo algunas de sus propiedades (públicas).

En un artículo previo vimos que los controles de lista poseían la propiedad DataTextField para indicar que propiedad (o campos en el caso de enlazar el control contra un DataSet, un DataTable o un DataReader) mostraría el control. Pero el caso del GridView, que como ya hemos visto, permitirá enlazar varias propiedades o campos en forma simultánea, el esquema será más complejo y más aún si consideramos otros factores como por ejemplo, el orden de las columnas. Es entonces esperable que exista una manera de definir cómo será la visualización de las propiedades a mostrar en las columnas de la grilla que considere todos estos factores.

VN:F [1.7.3_972]
Rating: 7.9/10 (9 votos cast)

Enlace de elementos en controles de lista en ASP.NET

Miércoles, 24 mar, 2010 @ 23:43 | Por Dario Krapp | ASP.NET

Estoy seguro que será difícil encontrar en la actualidad algún programador que no se haya visto en la necesidad enviar información a la interfaz de usuario, y eventualmente manejar las modificaciones que un usuario haya realizado en la misma. La intención de este artículo es dar un primer paso a los conceptos de enlace en ASP.NET (una tecnología que nos permitirá resolver este tipo de problemas) para un escenario muy especifico pero frecuente que es cuando se requiere mostrar en un control ASP.NET un grupo de elementos como una lista. ASP.NET ofrecerá para este tipo de casos un grupo de controles (que llamaremos controles de lista para identificarlos de una forma sencilla) que nos permitirán realizar esta tarea en forma simple y clara.

En la década de los noventa, con ASP, lo que se acostumbraba hacer era identificar un control HTML adecuado para el grupo de datos que se deseaba mostrar y escribir los tags HTML intercalados junto con los datos para crear un fragmento de código HTML con la funcionalidad deseada, esta forma de trabajo, si bien funcionaba, daba como resultado un código poco claro y difícil de mantener.
ASP.NET y el enlace a datos permitirá obtener un resultado similar con un código de rápido desarrollo y de bajo costo de mantenimiento.

Antes que nada creo que lo más importante es definir quienes serán los actores que intervendrán en un escenario de enlace de datos y sin importar cuánto avancemos o donde comencemos, nos encontraremos con dos actores principales, el primero se estará compuesto por el grupo de datos con los que deseamos trabajar (al cual, para utilizar la nomenclatura habitual, llamaremos “Origen de datos” o “Data Source”) y el segundo por el elemento que permitirá que los mismos sean mostrados en la interfaz de usuario, y eventualmente sean modificados desde la misma, aunque esta opción quedará fuera del alcance de este artículo.
En el caso de ASP.NET ese elemento será un control y los controles que se ajustarán a las necesidades propuestas en este articulo serán los siguientes:

• BulletedList
• CheckBoxList
• DropDownList
• ListBox
• RadioButtonList

En WebControls y el control HtmlSelect en HtmlControls.

Aunque cada uno de ellos renderizará la lista de elementos a mostrar de una forma diferente, el enlace se efectuará de la misma forma para todos, por lo que en nuestros ejemplos optaremos por utilizar un control DropDownList.

Para comenzar, seremos sumamente triviales y supondremos que nuestro origen de datos es un grupo de elementos simples, por ejemplo, un grupo de cadenas de caracteres compuesta por los valores “I1”, “I2” e “I3” y nuestra intención será mostrar dichos valores en un control DropDownList.

El enlace

En nuestra primera suposición ya hemos conocido a ambos actores (el origen de datos compuesto por la cadena de caracteres “I1”, “I2” e “I3” y el control ASP.NET DropDownList), Deberemos finalmente realizar el enlace entre ambos.
Esta tarea será muy sencilla ya que los controles de lista contarán con la siguiente propiedad:

• DataSource

La propiedad DataSource representará al grupo de elementos (u origen de datos) que se enlazará al control, claro está que el grupo de elementos a enlazar no podrá ser tan arbitrario como deseemos ya que de alguna forma ASP.NET deberá saber de qué manera acceder a los elementos que componen dicho grupo, por lo que éste deberá implementar alguna de las siguientes interfaces: IEnumerable, ICollection o IListSource.
Sabemos ahora que para nuestro ejemplo, lo que llamábamos “grupo” deberá implementar alguna de las interfaces mencionadas.

Creo que de todas las comentadas, la interfaz ICollection nos será la más familiar y, para quien no la recuerde, sin usar mucho la imaginación, seguramente le será un sinónimo de Colecciones (o Collections). En base a esto podríamos reformular el problema para intentar enlazar una colección compuesta por las cadenas “I1”, “I2” e “I3” con un control DropDownList.

Los pasos para esta tarea constarán de crear una colección, de crear el control ASP.NET y establecer su propiedad DataSource, veamos un ejemplo concreto:

<asp:DropDownList runat="server" ID="DDL1"/>
List<string> Lst = new List<string>();
Lst.Add("I1");
Lst.Add("I2");
Lst.Add("I3");
DDL1.DataSource = Lst;

Si ejecutamos este código veremos que el enlace no funciona, esto se debe a que aún nos falta un detalle más, si modificamos el código de la siguiente forma:

List<string> Lst = new List<string>();
Lst.Add("I1");
Lst.Add("I2");
Lst.Add("I3");

DDL1.DataSource = Lst;
DDL1.DataBind();

Veremos que todo funciona según lo esperado, el método DataBind es el que ejecutará el pasaje de datos desde el origen de datos (Lst) hacia el control (DDL1). Es más si realizamos la siguiente prueba:

List<string> Lst = new List<string>();
Lst.Add("I1");
Lst.Add("I2");
Lst.Add("I3");

DDL1.DataSource = Lst;
DDL1.DataBind();

Lst.Add("I5");
Lst.Add("I6");

Notaremos que los valores “I5” e “I6” no serán transferidos al control.
En este primer ejemplo hemos realizado el enlace que nos habíamos propuesto y con muy poco esfuerzo.

Los controles de lista contarán también con la propiedad DataTextFormatString que permitirá establecer el formato de los datos a mostrar, para continuar con el ejemplo que ya habíamos comenzado, reemplazaremos la lista de cadenas de caracteres por una lista de fechas, el ejemplo es tan simple como antes, una opción es utilizar las siguientes líneas de código:

List<DateTime> Lst = new List<DateTime>();
Lst.Add(DateTime.Now);
Lst.Add(DateTime.Now.AddHours(1));
Lst.Add(DateTime.Now.AddHours(2));
DDL1.DataSource = Lst;
DDL1.DataBind();

donde al ejecutarlo veremos que en el DropDownList DDL1 las fechas se desplegarán como esperábamos, aunque para darle un poco de emoción al ejemplo y poner en practica lo que comentamos hace apenas unos parrafos, podríamos desear mostrar solamente la fecha pero no la hora en el formato dia/año/mes. De más está decir que una opción es modificar el código para realizar este ajuste programáticamente, pero no es la idea ya que la propiedad DataTextFormatString nos permitirá ahorrarnos las líneas de código.
A continuación se muestra el ejemplo:

<asp:DropDownList runat="server" ID="DDL1" DataTextFormatString="{0:dd/yyyy/MM}" />

Me parece que una pregunta que podríamos formularnos en este punto es ¿por qué el control está mostrando los valores adecuadamente?, si prestamos atención veremos que en ambos casos, tanto como cuando utilizamos una lista de cadenas de caracteres o una lista de fechas el control DropDownList ha podido mostrar el valor esperado sin que nosotros hayamos tenido que hacer ningún tipo de especificación, podríamos preguntarnos ¿qué pasará si utilizamos una lista de otro tipo de elementos?, por ejemplo un tipo de datos creado por nosotros mismos, como el siguiente:

public class MiElem
{
   public string Valor1 { set; get; }
   public int Valor2 { set; get; }
}

Si modificamos nuevamente el código previo de la siguiente forma

List<MiElem> Lst = new List<MiElem>();
Lst.Add(new MiElem() { Valor1 = "V1", Valor2 = 1 });
Lst.Add(new MiElem() { Valor1 = "V2", Valor2 = 2 });
Lst.Add(new MiElem() { Valor1 = "V3", Valor2 = 3 });

DDL1.DataSource = Lst;
DDL1.DataBind();

Notaremos la magia ya no funciona y que el DropDownList en este caso está mostrando por cada elemento un valor que no se encuentra en ninguna de sus propiedades. Lo que está haciendo el control es mostrar el valor de la representación de cadena de caracteres del elemento, o sea, simplemente está mostrando el resultado del método ToString() de cada elemento que carga. Si sobreescribimos el método ToString() de la clase MiElem nos convenceremos rápidamente de este hecho.
Ahora queda claro porque con cadenas de caracteres y fechas todo funcionaba sin problemas pero también está claro que con otros tipos de datos nos podríamos encontrar con complicaciones, ya que no siempre podremos ajustar el método ToString() a nuestra conveniencia. En este punto toma importancia la propiedad DataTextField de los controles de lista. DataTextField indicará el nombre de la propiedad o el campo (veremos más adelante a que nos referimos con campo) que el control mostrará. Si modificamos el código de la siguiente forma:

List<MiElem> Lst = new List<MiElem>();
Lst.Add(new MiElem() { Valor1 = "V1", Valor2 = 1 });
Lst.Add(new MiElem() { Valor1 = "V2", Valor2 = 2 });
Lst.Add(new MiElem() { Valor1 = "V3", Valor2 = 3 });

DDL1.DataSource = Lst;
DDL1.DataTextField = "Valor1";
DDL1.DataBind();

Notaremos que el control DDL1 mostrará el valor de la propiedad Valor1 de cada instantancia de MiElem.

De forma similar, la propiedad DataValueField indicará el nombre de la propiedad o el campo que el control utilizará como valor para cada ítem mostrado, ya que es frecuente el hecho de tener la necesidad de mostrar un dato, pero utilizar otro dato del elemento para realizar alguna tarea.
Si eventualmente se desea utilizar la misma propiedad para mostrarla en control tanto como para utilizar su valor, puede omitirse el uso de DataValueField, que es un detalle que permite mantener el código más simple.

Aunque en nuestro ejemplo hemos utilizado un control DropDownList enlazado a un objeto List podríamos haber utilizado cualquier otro control de lista (como mencionamos previamente), tanto como cualquier otra clase que implementase la interfaz ICollection.

Si volvemos a la propiedad DataSource, recordaremos que también pueden enlazarse objetos que implementan la interfaz IListSource, y podríamos preguntarnos ¿por qué?, la interfaz IListSource quizás no nos sea tan familiar como ICollection, pero es todo un indicio comentar que tanto la clase DataTable, como DataSet la implementan. Sabiendo esto quizás podríamos preguntarnos:
¿será entonces posible enlazar un DataTable o un DataSet a uno de estos controles?

La respuesta, como no podría ser de otra manera, es que si es posible, en tal caso en las propiedades DataTextField y DataValueField se deberá establecer el nombre de los campos a enlazar del DataTable, de esta forma el esquema de funcionamiento se mantendrá inalterable y el enlace podrá utilizarse para clases que implementen la interfaz ICollection tanto como IListSource. Por otra parte la interfaz IEnumerable (que era la tercera interfaz soportada por la propiedad DataSource) permitirá el enlace a objetos como por ejemplo un SqlDataReader donde los datos se van obteniendo de a uno a la vez. A continuación se muestra un ejemplo de enlace contra un DataSet, un DataTable y un SqlDataReader:

<asp:DropDownList
runat="server"
ID="DDL1"
DataTextField="Descripcion">
</asp:DropDownList>
<asp:DropDownList
runat="server"
ID="DDL2"
DataTextField="Descripcion">
</asp:DropDownList>
<asp:DropDownList
runat="server"
ID="DDL3"
DataTextField="Descripcion">
</asp:DropDownList>
private void DataReaderBinding()
{
        string cnstr = ...;
        SqlConnection SCon = new SqlConnection(cnstr);
        SqlCommand SCom = new SqlCommand("SELECT top 11 * FROM Datos1", SCon);
        SCom.Connection.Open();
        SqlDataReader SRead = SCom.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
        DDL1.DataSource = SRead;
        DDL1.DataBind();
}

private void DataSetAndDataTableBinding()
{
        string cnstr = ...;
        SqlConnection SCon = new SqlConnection(cnstr);
        SqlCommand SCom = new SqlCommand("SELECT top 11 * FROM Datos1", SCon);

        SqlDataAdapter SDa = new SqlDataAdapter(SCom);
        DataSet DSet = new DataSet();
        SDa.Fill(DSet, "Datos1");

        DDL2.DataSource = DSet.Tables[0] ;

        DDL3.DataSource = DSet;
        DDL3.DataMember = "Datos1";  

        DataBind();
}

En el ejemplo puede observarse el uso de la propiedad DataMember para el caso del enlace contra un objeto DataSet, la propiedad DataMember deberá utilizarse cuando sea necesario indicar el nombre del DataTable que se enlazará al control. También en el ejemplo puede observarse el uso de un método existente en la página denominado DataBind() que ejecutará todos los bindeos necesarios automáticamente ahorrandonos un poco de código.

VN:F [1.7.3_972]
Rating: 9.4/10 (9 votos 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: 9.0/10 (9 votos 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: 9.2/10 (11 votos cast)