Tomando contacto con el control DataGrid de Silverlight

Una nueva cuadrícula de datos entra en liza


Después de instalar el conjunto de herramientas necesarias para el desarrollo con Silverlight 2.0, una de las primeras características que empecé a probar fue el nuevo control DataGrid que incorpora el Cuadro de herramientas de Visual Studio 2008.



Desde que se liberaron las primeras CTPs de WPF-XAML (Avalon, en aquellos «lejanos» días), siempre he pensado que un control de cuadrícula de datos sería una de las aportaciones más útiles que se podrían incorporar al conjunto de controles existente, y dado que por fin ya lo tenemos disponible, en este artículo abordaremos las características esenciales para comenzar a trabajar con él.


Creación del proyecto Silverlight


Para ilustrar los diversos elementos básicos del control DataGrid comenzaremos por abrir Visual Studio 2008 y crear un nuevo proyecto de tipo «Silverlight Application».



Cuando Visual Studio detecta que estamos creando un proyecto de este tipo, se encarga automáticamente de prepararnos el entorno de ensayo en el que poder realizar las pruebas relacionadas con el contenido Silverlight que desarrollemos.  Dado que dicho contenido debe estar alojado en una página Web, se abre un cuadro de diálogo en el que tenemos que seleccionar dónde vamos a situar la aplicación Silverlight: un archivo HTML, un sitio Web o un proyecto ASP.NET.



Para este ejemplo elegiremos la creación de un sitio Web y aceptaremos el diálogo, creándose seguidamente una solución conteniendo el proyecto Silverlight y el sitio Web. En lo que respecta a este último, hallaremos un Web Form cuyo nombre tendrá el formato NombreProyectoPage.aspx.


Dentro del código de esta página encontraremos un control Silverlight que apunta a un archivo xap, el cual contiene el conjunto de recursos, ensamblados, etc., del proyecto Silverlight. Para más información acerca de este tipo de archivo, os recomiendo el siguiente post en el blog de Jorge Serrano. A continuación podemos ver un fragmento del código de esta página aspx.


<form id=»form1″ runat=»server» style=»height:100%;»>
<asp:ScriptManager ID=»ScriptManager1″ runat=»server» />

<div style=»height:100%;»>

<asp:Silverlight ID=»Xaml1″
runat=»server»
Source=»~/ClientBin/TomandoContactoDataGrid_CS.xap»
Version=»2.0″
Width=»100%» Height=»100%» />

</div>
</form>


En el caso de que hubiéramos optado por emplear un archivo HTML, el código generado para albergar el contenido Silverlight utilizaría una etiqueta <OBJECT> para situar la referencia a la página xap.


<head>
….
….
<script type=»text/javascript»>
     
            function onSilverlightError(sender, args) {
                if (args.errorType == «InitializeError»)  {
                    var errorDiv = document.getElementById(«errorLocation»);
                    if (errorDiv != null)
                        errorDiv.innerHTML = args.errorType + «- « + args.errorMessage;
                }
            }
        
</script>
</head>
….
….
<div id=»silverlightControlHost»>
<object data=»data:application/x-silverlight,» type=»application/x-silverlight-2-b1″
width=»100%» height=»100%»>
<param name=»source» value=»ClientBin/TomandoContactoDataGrid_CS.xap»/>
<param name=»onerror» value=»onSilverlightError» />
<param name=»background» value=»white» />

<a href=»http://go.microsoft.com/fwlink/?LinkID=108182″ style=»text-decoration: none;»>
<img src=»http://go.microsoft.com/fwlink/?LinkId=108181″
alt=»Get Microsoft Silverlight» style=»border-style: none»/>
</a>
</object>

<iframe style=’visibility:hidden;height:0;width:0;border:0px’></iframe>
</div>


Agregar y configurar el control DataGrid


A continuación pasaremos al proyecto Silverlight, situándonos en el archivo Page.xaml, que es el utilizado para definir la interfaz de usuario. Podemos observar en el código XAML la existencia de un UserControl como elemento raíz, y dentro de este un panel Grid, al que nosotros añadiremos un control DataGrid.


Esta operación provocará que el editor de código inserte la declaración de un nuevo espacio de nombres denominado my, junto al resto de declaraciones existentes dentro del elemento UserControl. El espacio de nombres my establece una referencia al ensamblado que contiene los controles relacionados con la manipulación de datos, como es el caso de la clase DataGrid.


Por nuestra parte añadiremos a la declaración del control DataGrid las siguientes propiedades:


-Width y Height. Para establecer el tamaño del control.


-RowBackground y AlternatingRowBackground. Para asignar el color de fondo para las filas normales y alternas de la cuadrícula.


-AutoGenerateColumns. Indica al control que genere automáticamente las columnas. Si queremos un mayor control sobre el modo de presentación de los datos, es posible crear manualmente cada una de las columnas del DataGrid, pero este es un aspecto que trataremos en un próximo artículo; en el ejemplo actual, para simplificar, dejaremos que sea el control quien se encargue de las columnas.


-Margin. Establece un margen de separación con el resto de controles de la página.


El código XAML de nuestro proyecto Silverlight quedaría en este momento como podemos ver seguidamente.


<UserControl xmlns:my=»clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data»
x:Class=»TomandoContactoDataGrid_CS.Page»
xmlns=»http://schemas.microsoft.com/client/2007″
xmlns:x=»http://schemas.microsoft.com/winfx/2006/xaml»
Width=»400″ Height=»300″>

<Grid Background=»LightYellow»>
<my:DataGrid x:Name=»grdDatos»
Width=»300″
Height=»250″
AutoGenerateColumns=»True»
RowBackground=»Goldenrod»
AlternatingRowBackground=»LightSkyBlue»
Margin=»5″ />
</Grid>


Rellenar de datos el control


La propiedad ItemsSource de la clase DataGrid será la que deberemos emplear para asignar la fuente de datos que visualizará este control. Dicha propiedad admite un tipo que implemente la interfaz IEnumerable, por lo que como ejemplo muy simple de uso podemos asignarle una colección compuesta por cadenas.


Esta operación la realizaremos durante la carga de la página, por lo que en el editor de código XAML añadiremos al elemento UserControl el atributo correspondiente a su evento Loaded, ofreciéndonos Intellisense la posibilidad de crear automáticamente el cuerpo del manipulador de este evento, como vemos en la siguiente imagen.



Pulsando Enter, se asignará el nombre al manipulador de evento.


<UserControl xmlns:my=»clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data»
….
Loaded=»UserControl_Loaded»>
….

Y en el code-behind se creará el esqueleto vacío del evento, que completaremos con el código que se encarga de crear la colección y asignarla a la propiedad ItemsSource.


private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
List<string> lstNombre = new List<string>();
lstNombre.Add(«Pedro»);
lstNombre.Add(«Ana»);
lstNombre.Add(«Juan»);
lstNombre.Add(«Elena»);
lstNombre.Add(«Miguel»);
lstNombre.Add(«Marta»);

this.grdDatos.ItemsSource = lstNombre;
}


Al ejecutar el proyecto, el control DataGrid se rellenará con esta colección de datos mostrando el siguiente aspecto.



Utilizando una clase propia como fuente de datos


Usar una colección simple para llenar el DataGrid no tiene mucho sentido, ya que existen controles como ListBox, más adecuados para ese propósito.


La principal finalidad del control DataGrid consiste en mostrar un conjunto de datos en formato tabular, donde cada fila estaría compuesta por un grupo de valores de la entidad que representa la tabla.


Teniendo esto en cuenta, vamos a crear una clase con diversas propiedades; para después instanciar varios objetos que añadiremos a una colección que actuará como origen de datos del DataGrid. El código de la clase, que llamaremos GrabacionMusical, podemos verlo a continuación.


public class GrabacionMusical
{
public GrabacionMusical(string sAutor, string sTitulo,
string sSelloDiscografico, int nNumeroTemas)
{
this.Autor = sAutor;
this.Titulo = sTitulo;
this.SelloDiscografico = sSelloDiscografico;
this.NumeroTemas = nNumeroTemas;
}

public string Autor { get; set; }

public string Titulo { get; set; }

public string SelloDiscografico { get; set; }

public int NumeroTemas { get; set; }
}


Volviendo de nuevo al evento Loaded de la página, crearemos una colección de tipos GrabacionMusical, a la que añadiremos varios objetos de esta clase, asignando la colección al DataGrid.


private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
List<GrabacionMusical> lstGrabaciones = new List<GrabacionMusical>();

lstGrabaciones.Add(new GrabacionMusical(«Pink Floyd», «Dark Side of the Moon», «EMI», 9));
lstGrabaciones.Add(new GrabacionMusical(«Rush», «Presto», «Atlantic», 11));
lstGrabaciones.Add(new GrabacionMusical(«Neil Young», «Harvest Moon», «Warner», 10));
lstGrabaciones.Add(new GrabacionMusical(«New Order», «Republic», «CentreDate», 11));
lstGrabaciones.Add(new GrabacionMusical(«Marillion», «Script for a Jester’s Tear», «EMI», 6));
lstGrabaciones.Add(new GrabacionMusical(«Genesis», «Foxtrot», «Charisma», 6));

this.grdDatos.ItemsSource = lstGrabaciones;
}


La siguiente imagen muestra el control resultante de esta operación.



 


Respecto a la edición de los datos contenidos en el control, haciendo doble clic o pulsando la tecla F2 en una de las celdas, entraremos en modo editable sobre la misma.



Manipulando los datos durante la navegación


Aparte de las propiedades que se encargan de los aspectos de presentación visual, la clase DataGrid dispone de otros miembros destinados a ayudarnos en el manejo de la información, durante todas aquellas operaciones de desplazamiento por las filas y celdas que realice el usuario. Comenzaremos por el evento SelectionChanged, que se produce cada vez que el usuario cambia la fila o conjunto de filas seleccionadas en el control. Para obtener los datos de la fila actual, haremos uso de la propiedad SelectedItem, que devuelve un tipo object, el cual tendremos que convertir al tipo GrabacionMusical, utilizado por la colección mostrada en nuestro DataGrid.


Para dotar a nuestro ejemplo de esta funcionalidad, vamos a realizar primeramente algunos cambios en la interfaz de usuario, que consistirán en declarar el evento SelectionChanged en el DataGrid; añadir dos filas al panel Grid, dentro de las cuales distribuiremos el DataGrid y un StackPanel que contendrá un control TextBlock, el cual emplearemos para mostrar los valores del registro seleccionado, obtenidos de la propiedad DataGrid.SelectedItem. A continuación vemos cómo quedaría el código XAML tras estas incorporaciones.


<UserControl
….
Width=»550″ Height=»400″
…. >

<Grid Background=»LightYellow»
VerticalAlignment=»Top»
HorizontalAlignment=»Left»>

<Grid.RowDefinitions>
<RowDefinition Height=»*» />
<RowDefinition Height=»125″ />
</Grid.RowDefinitions>

<my:DataGrid x:Name=»grdDatos»
Grid.Row=»0″
Width=»500″
Height=»175″
AutoGenerateColumns=»True»
RowBackground=»Goldenrod»
AlternatingRowBackground=»LightSkyBlue»
Margin=»10″
SelectionChanged=»grdDatos_SelectionChanged»/>

<StackPanel Grid.Row=»1″
Background=»LightBlue»
Orientation=»Horizontal»
Margin=»5″ >

<TextBlock x:Name=»txtRegistroGrabacion» Width=»200″ />
</StackPanel>
</Grid>
</UserControl>


Pasando después al code-behind, escribiremos el manipulador del evento SelectionChanged que vemos en el siguiente bloque de código.


private void grdDatos_SelectionChanged(object sender, EventArgs e)
{
GrabacionMusical oGrabacion= (GrabacionMusical)this.grdDatos.SelectedItem;

this.txtRegistroGrabacion.Text = «Registro seleccionado: n» +
oGrabacion.Autor + «n» +
oGrabacion.Titulo + «n» +
oGrabacion.SelloDiscografico + «n» +
oGrabacion.NumeroTemas;
}


Como resultado, al ejecutar ahora la aplicación, conforme el usuario se va desplazando por las filas, el TextBlock es actualizado con los valores de la fila actual, como vemos en esta imagen.



De igual forma que accedemos a la información del DataGrid cuando seleccionamos una fila, puede interesarnos recuperar los datos si son varios los registros que forman parte de la selección. Este aspecto lo podemos resolver empleando la propiedad SelectedItems del control, que nos devuelve una colección de estos registros.


Para ilustrar esta característica en nuestro ejemplo, vamos a añadir un ListBox en la página XAML.


<StackPanel Grid.Row=»1″
…. >
….
<ListBox x:Name=»lstTitulos» Width=»200″ />
</StackPanel>

Llenaremos dicho ListBox al detectar que el usuario ha seleccionado más de una fila en el DataGrid. Aprovecharemos el evento SelectionChanged para agregar esta lógica de funcionamiento.


private void grdDatos_SelectionChanged(object sender, EventArgs e)
{
//….
this.lstTitulos.Items.Clear();

if (this.grdDatos.SelectedItems.Count > 1)
{
foreach (GrabacionMusical oGM in this.grdDatos.SelectedItems)
{
this.lstTitulos.Items.Add(oGM.Titulo);
}
}
}


La siguiente imagen muestra este comportamiento que hemos incorporado al programa.



La siguiente situación que abordaremos – con la cual concluimos esta introducción al control DataGrid-consiste en la detección del cambio de celda actual, de forma que podamos, por ejemplo, cambiar el tamaño y formato del tipo de letra de la columna en la que estamos posicionados.


La clase DataGrid dispone para estos menesteres del evento CurrentCellChanged, que como su nombre indica, se desencadena al producirse el cambio de la celda que tiene el foco; y la propiedad CurrentColumn, que nos devuelve el objeto columna actual.


Dado que el objeto columna obtenido pertenece a la clase abstracta DataGridColumnBase, deberemos convertirlo al tipo adecuado antes de poder manipular las propiedades de la columna.


Existen varias clases derivadas de DataGridColumnBase, aunque en el proyecto que estamos desarrollando todas pertenecen al tipo DataGridTextBoxColumn. En un próximo artículo abordaremos los diferentes tipos de columna con los que el DataGrid puede trabajar.


El código que tendremos que añadir consistirá en la declaración del evento en el archivo XAML.


<my:DataGrid x:Name=»grdDatos»
….
CurrentCellChanged=»grdDatos_CurrentCellChanged» />

Y su lógica correspondiente en el code-behind.


// variable a nivel del módulo de clase para saber
// cuál es la columna que previamente estaba como actual
// al producirse el cambio de columna
private int nColumnaPrevia = 0;

private void grdDatos_CurrentCellChanged(object sender, EventArgs e)
{
// al comenzar la ejecución, tanto la variable nColumnaPrevia
// como la propiedad Index de la columna tienen cero,
// aplicarles los cambios visuales iniciales
if (nColumnaPrevia == 0 && this.grdDatos.CurrentColumn.Index == 0)
{
((DataGridTextBoxColumn)this.grdDatos.CurrentColumn).FontWeight = FontWeights.Bold;
((DataGridTextBoxColumn)this.grdDatos.CurrentColumn).FontSize = 14;
}

// al cambiar la celda, comprobar si también ha cambiado la columna,
// restaurar los valores visuales de la columna anterior y
// aplicar los nuevos a la columna actual
if (nColumnaPrevia != this.grdDatos.CurrentColumn.Index)
{
((DataGridTextBoxColumn)this.grdDatos.Columns[nColumnaPrevia]).FontWeight = FontWeights.Normal;
((DataGridTextBoxColumn)this.grdDatos.Columns[nColumnaPrevia]).FontSize = 11;

nColumnaPrevia = this.grdDatos.CurrentColumn.Index;

((DataGridTextBoxColumn)this.grdDatos.CurrentColumn).FontWeight = FontWeights.Bold;
((DataGridTextBoxColumn)this.grdDatos.CurrentColumn).FontSize = 14;
}
}


La siguiente imagen muestra el resultado de esta nueva funcionalidad aplicada al control.



Y con esto finalizamos el presente artículo introductorio sobre el control DataGrid. Esperamos seguir abordando otros aspectos de este interesante control en próximas entregas. Como material complementario, el proyecto de pruebas que hemos desarrollado está disponible en los siguientes enlaces para C# y VB. Por otro lado, como documentación adicional, el blog de Scott Morrison -jefe de proyecto de este control-dispone varios posts interesantes; así como la página de MSDN dedicada a la clase DataGrid.


Un saludo.

16 Comentarios

  1. anonymous

    Gracias por el articulo, para ¿cuando la proxima entrega? Ahora falta cómo guardar los cambios realizados 😉

  2. lmblanco

    Hola Amando

    Gracias por leer el artículo, y espero en próximas fechas escribir alguna cosilla más sobre este control, que parece ser promete bastante 8-).

    Un saludo,
    Luismi

  3. anonymous

    Saludos.

    Una pregunta, en el datagrid de web hay un delegado para manejar el bound de cada fila para determinar, teniendo un template, que control (una etiqueta por ejemplo) recibiria un valor formateado (usando la funcion FindControl) en silverlight solo he visto ejemplos que implican el Binding directo en el xaml como se podria hacer dicho binding por codigo como se puede hacer en el datagrid de web.

    Gracias.

  4. lmblanco

    Hola Edilberto

    Para personalizar la presentación de los datos de una determinada columna, el control datagrid de Silverlight dispone de un sistema de plantillas de datos a través de la etiqueta DataGridTemplateColumn. En el siguiente ejemplo, puedes ver cómo se crea una columna para un campo de tipo fecha, de forma que mediante la propiedad DataGridTemplateColumn.CellTemplate se establece el modo de presentación del campo en el datagrid, y mediante DataGridTemplateColumn.CellEditingTemplate se establece el modo de edición.














    Espero que te sirva de ayuda.

    Un saludo,
    Luismi

  5. anonymous

    Gracias por responder.

    Mi pregunta iba mas hacia el codigo del Bound, por ejemplo en asp.net se tiene el evento xxx_bound donde puedo por medio de codigo en C# asignar los valores a los controles dentro del template, sin embargo al manejar el evento mas parecido xxx_LoadingRow(object sender, DataGridRowEventArgs e) en silverlight lo que me dispone el objeto e.Row no me permite jugar con los valores de los controles del template, por ejemplo si en el template tengo una etiqueta que dice nombre completo y de la bse de datos o de la coleccion del objetos del source del datagrid vienen sus compmonentes no concatenados (primer nombre, segundo nombre, etc.) como hago para decirle a esa etiqueta que su propiedad .text debe ser igual a la concatenacion. en asp.net solo era e.Row.FindControl («lblNombreCompleto) as Label y a eso asignarle la concatenacion. Con el esquema actual en silverlight como puedo conseguir ese mismo resultado?

    Muchas Gracias.

  6. lmblanco

    Hola Edilberto

    Si necesitas concatenar más de un campo dentro de una columna del control Silverlight creo que puedes aplicar también la solución de las plantillas de datos, lo que ocurre es que no puedes añadir controles «sueltos», sino que deben estar agrupados dentro de un contenedor, por ejemplo un StackPanel, de forma parecida a como puedes ver a continuación:










    Espero que te sea de ayuda.

    Un saludo,
    Luismi

  7. anonymous

    Mi pregunta es:
    Porque me da un error en el lado de page.xaml.cs cuando intento añadir mi grid? ya no me aparece el nombre para escoger en el intellisense… así q qería saber xq no se carga el nombre si lo supierais…

  8. lmblanco

    Hola Rubén

    Prueba a añadir el DataGrid cuando estés en el diseñador xaml, el correspondiente al archivo page.xaml en vez de page.xaml.cs

    Un saludo,
    Luismi

  9. anonymous

    Amig… Excelente Post… pero tengo un problema y ya no se a quien recurrir… tengo instalado todas las herramientas y ya descargue el Toolkit correspondiente a Silverlight 3, pero no puedo agregarlos a mi VS2008 ya que me da un error de dependencia… si me pudiera ayudar estaria muy agradecido, el error que me da es el siguiente

    Could not locate file or Assembly ‘System, version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e

    Gracias

  10. lmblanco

    Hola Jesús

    Gracias por leer el post. Respecto al problema que comentas, prueba a revisar todos aquellos componentes que tengas instalados, que puedan estar relacionados con alguna versión anterior de Silverlight; intenta desinstalarlos y después prueba de nuevo a instalar el Toolkit a ver si de esa forma lo consiguieras.

    Un saludo.
    Luismi

  11. anonymous

    quisiera ver si me puedes sacar de dudas sobre unos movimientos que quiero hacer con mi datagrid, pero no me funcionan como quiero. pero esto seria de otro tema o como le podira hacer para explicar el punto completo, o lo puedo hacer por aqui??

  12. lmblanco

    Hola orozco

    Puedes exponer tu duda aquí, ya que cualquiera que visite el blog, y en concreto, este post, podría aportar ideas sobre el modo de abordar el problema.

    Un saludo,
    Luismi

  13. anonymous

    Hola quisiera saber como se accede al contenido de un control que tengo dentro de un datatemplate que se encuentra dentro de un datagrid.
    como por ejemplo

    acceder por el codigo de c# a los datos que se hayan cargado en el

    asi mismo para un treeview etc….

  14. lmblanco

    Hola Ninoska

    Te adjunto un par de enlaces en donde se plantea un problema parecido con algunas soluciones:

    http://forums.silverlight.net/forums/p/133885/298814.aspx

    http://www.eggheadcafe.com/community/aspnet/56/10103547/access-control-inside-row.aspx

    Espero que te sean de utilidad.

    Un saludo.
    Luismi

  15. anonymous

    te pasaste con este post, era lo que andaba buscando me va a ser util para un proyecto que tengo. Gracias

  16. lmblanco

    Hola Miguel Ángel

    Gracias por tu interés en el post, me alegra que te sirviera de ayuda.

    Un saludo,
    Luismi

Responder a Cancelar respuesta

Tema creado por Anders Norén