September 2008 - Artículos

Si trabajando con alguna de las betas de Silverlight hemos creado una página que contuviera un control TextBox, a buen seguro que nos hemos llevado un disgusto cuando al pulsar alguna de las teclas especiales (@ , | , # , \ , signos de acentuación, etc.), comprobábamos que este control hacía caso omiso de nuestras pulsaciones -por muy fuerte que aporreáramos el teclado- no mostrando el carácter correspondiente.

Este ha sido un tema ya comentado en los foros de Silverlight, donde se proponían algunas soluciones y trucos para salir del paso ante el problema.

Sin embargo, desde la reciente liberación de la Release Candidate de Silverlight 2, parece que este ha sido uno de los inconvenientes solucionados por el equipo de desarrollo del producto, ya que nada más hacerse pública la noticia, me descargué los bits de esta versión -próxima a la definitiva-, y en un pequeño proyecto de prueba observé que el TextBox ya posibilita teclear todos estos caracteres que anteriormente no permitía.

 

Lo cual quiere decir que ya no necesitaremos capturar los eventos de teclado de este control para añadir manualmente esta funcionalidad.

Un saludo.

Publicado por Luis Miguel Blanco | con no comments
Archivado en:

Nota. A efectos de poder seguir adecuadamente el contenido de este artículo, los ejemplos de código fuente del libro que se menciona se encuentran en este enlace para C# y en este enlace para VB.

 

Un interesante control de selección de fecha

Hace unos cuantos meses leí el estupendo libro "Applications = Code + Markup" de Charles Petzold, una obra totalmente recomendable para todos aquellos que quieran iniciarse y profundizar en el nuevo paradigma de aplicaciones de escritorio que representa Windows Presentation Foundation (WPF).

Al llegar al capítulo 25 encontré un ejemplo muy interesante, en el cual se desarrollaba un control de selección de fecha denominado DatePicker, que al ser ejecutado presenta el siguiente aspecto.

 

Este control cumple su cometido a la perfección, aunque debemos destacar un inconveniente que el propio Petzold menciona en su obra, más concretamente en la página 696, y que consiste en que el primer día de la semana que se muestra es el domingo, lo cual representa un problema si lo utilizamos en países donde el primer día de la semana es lunes, tal y como ocurre en España.

Continúa Petzold indicando en su libro, que una posible solución a este problema pasaría por acceder, desde el codebehind de la página XAML, a la propiedad FirstDayOfWeek de la clase DateTimeFormatInfo, para asignar manualmente el valor correcto. Sin embargo, si accedemos a dicha propiedad, veremos que ya tiene establecido el lunes como primer día de la semana -aunque lo muestre en inglés-, por lo que parece ser que la raíz del problema no se encuentra en esta propiedad.

 

Observando el código XAML del control DatePicker, vemos que para mostrar las columnas que contienen como título los nombres abreviados de los días de la semana utiliza un control StatusBar, cuyo contenido rellena asignando a su propiedad ItemsSource un enlace a datos que apunta a la propiedad AbbreviatedDayNames del objeto DateTimeFormatInfo, situado en DateTimeFormatInfo.CurrentInfo, como podemos apreciar en el siguiente bloque de código.

<!-- =============================================
      DatePicker.xaml (c) 2006 by Charles Petzold 
     ============================================= -->
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:g="clr-namespace:System.Globalization;assembly=mscorlib"
             x:Class="Petzold.CreateDatePicker.DatePicker">
<!--....-->
<!-- StatusBar with UniformGrid for days of the week. -->
<StatusBar Grid.Row="1"
           ItemsSource="{Binding Source={x:Static 
                        g:DateTimeFormatInfo.CurrentInfo}, 
                        Path=AbbreviatedDayNames}">
    <!--....-->

 

Si en tiempo de ejecución observamos los elementos de la colección contenida en la propiedad AbbreviatedDayNames, comprobaremos que el primer elemento es "dom", siendo esta la causa de que el control visualice los nombres de los días en un orden que no se ajusta al utilizado en nuestro calendario.

 

Retocando la colección AbbreviatedDayNames

Para solventar este inconveniente podemos recurrir al empleo de un pequeño truco dentro del constructor del control DatePicker, consistente en obtener el objeto CultureInfo de la hebra de ejecución en curso de la aplicación, y pasar a una variable el contenido de la propiedad AbbreviatedDayNames. A continuación añadimos un nuevo elemento al final de esta colección, en el que depositamos el mismo valor existente dentro del primer elemento, eliminando por último el primer elemento, con lo que ya tenemos la colección que utilizará el StatusBar rellena de los valores en el orden que necesitamos.

//-------------------------------------------
// DatePicker.cs (c) 2006 by Charles Petzold
//-------------------------------------------
//....
// código añadido ---------------
using System.Threading;
using System.Collections.Generic;
// ----------------------------------

namespace Petzold.CreateDatePicker
{
    public partial class DatePicker
    {
        //....
        // Constructor.
        public DatePicker()
        {
            //....

            // código añadido -------------------------------------
            CultureInfo oCultureInfoActual = (CultureInfo)Thread.CurrentThread.CurrentCulture;
            List<string> lstDiasAbreviados = new List<string>(oCultureInfoActual.DateTimeFormat.AbbreviatedDayNames);
            lstDiasAbreviados.Add(lstDiasAbreviados[0]);
            lstDiasAbreviados.RemoveAt(0);
            // ----------------------------------------------------

            //....

 

Empleando el depurador, podemos apreciar el resultado de las mencionadas operaciones, como muestra la siguiente imagen.

 

Para que estos cambios sobre la colección de nombres de días de la semana que acabamos de realizar sean efectivos, tenemos que asignar la variable que contiene la colección de nuevo a la propiedad AbbreviatedDayNames del objeto CultureInfo.DateTimeFormat, pero en este punto vamos a encontrarnos con una desagradable sorpresa ya que al realizar dicha operación mediante la siguiente línea de código.

oCultureInfoActual.DateTimeFormat.AbbreviatedDayNames = lstDiasAbreviados.ToArray();

 

Se producirá una excepción que nos indica que el objeto al que intentamos asignar el valor es de sólo lectura.

 

 

La forma de solucionar este problema pasa por crear una copia del objeto CultureInfo obtenido de la hebra de ejecución -utilizando su método Clone-, lo que nos permitirá realizar la modificación que precisamos y devolverlo de nuevo a la hebra actual de ejecución, como vemos en el siguiente bloque de código. Nótese igualmente, que para conseguir que este código funcione eficazmente, debe estar situado antes de la llamada al método InitializeComponent; dicho método es el encargado de hacer que el compilador de código de marcado procese el código XAML de la página, por lo que si en el momento de invocar a InitializeComponent la propiedad AbbreviatedDayNames no tiene establecida nuestra modificación, la página no se percatará de la misma.

CultureInfo oCultureInfoActual = (CultureInfo)Thread.CurrentThread.CurrentCulture.Clone();
List<string> lstDiasAbreviados = new List<string>(oCultureInfoActual.DateTimeFormat.AbbreviatedDayNames);
lstDiasAbreviados.Add(lstDiasAbreviados[0]);
lstDiasAbreviados.RemoveAt(0);
oCultureInfoActual.DateTimeFormat.AbbreviatedDayNames = lstDiasAbreviados.ToArray();
Thread.CurrentThread.CurrentCulture = oCultureInfoActual;

InitializeComponent();

 

Al volver a ejecutar nuestro proyecto, el control mostrará los nombres de los días de la semana en el orden deseado.

 

Reubicando los días del mes

Pero ahora nos encontraremos con un nuevo inconveniente: si bien hemos cambiado las posiciones de los nombres de los días, no hemos hecho lo propio con los números, los cuales ahora no se corresponden con las columnas del día de la semana, es decir, la primera columna de números pertenece todavía al domingo, la segunda al lunes y así sucesivamente.

El origen de este comportamiento se debe al control UniformGrid utilizado para visualizar los números de los días del mes. Dicho control se utiliza como plantilla de elementos dentro de otro control ListBox, como vemos en el siguiente código.

<!-- =============================================
      DatePicker.xaml (c) 2006 by Charles Petzold 
     ============================================= -->
<!--....-->
<ListBox Name="lstboxMonth" 
         SelectionChanged="ListBoxOnSelectionChanged">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <UniformGrid Name="unigridMonth" 
                         Columns="7" Rows="6"
                         IsItemsHost="True" 
                         Background="{DynamicResource {x:Static SystemColors.ControlLightBrushKey}}" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBoxItem>dummy item</ListBoxItem>
</ListBox>
<!--....-->

 

El control UniformGrid dispone de una propiedad llamada FirstColumn, que como su nombre indica, nos permite especificar la columna a partir de la cual se van a comenzar a rellenar los valores del control. Dicha propiedad es utilizada en este ejemplo del libro en dos lugares: durante la carga del control -evento Loaded- y cada vez que se produzca un cambio en la fecha seleccionada -propiedad de dependencia Date-, momento en el que se producirá una llamada al método DateChangedCallback y desde este a OnDateChanged, que es donde se establece la columna para el UniformGrid. En ambos casos se toma el número de día de la semana que corresponde al primer día del mes utilizado por el control, asignando este valor a la propiedad UniformGrid.FirstColumn.

Para sincronizar correctamente los números del día del mes con las columnas de los días de la semana, lo que debemos hacer es restar uno a la operación anteriormente mencionada, de modo que el código quedaría de la siguiente manera.

//-------------------------------------------
// DatePicker.cs (c) 2006 by Charles Petzold
//-------------------------------------------
//....
public partial class DatePicker
{
    //....
    // Handler for window Loaded event.
    void DatePickerOnLoaded(object sender, RoutedEventArgs args)
    {
            //....
            // código modificado ---------------------
            unigridMonth.FirstColumn =
                (int)(new DateTime(dt.Year, dt.Month, 1).DayOfWeek) - 1;
            // ----------------------------------------------
        }
    }


    // OnDateChanged changes the visuals to match the new value of Date.
    protected virtual void OnDateChanged(DateTime? dtOldValue,
                                         DateTime? dtNewValue)
    {
        //....
        // Set the first day of the month.
        if (unigridMonth != null)
            // código modificado ------------------
            unigridMonth.FirstColumn =
                (int)(new DateTime(dtNew.Year,
                                   dtNew.Month, 1).DayOfWeek) - 1;
        // ----------------------------------------------
        //....
    }
}

 

Al ejecutar de nuevo la aplicación comprobaremos cómo ahora los días del mes sí que coinciden adecuadamente con los de la semana.

 

Conclusión

El modelo de desarrollo de interfaces de usuario con WPF, basado en una clara separación entre la parte de presentación y la correspondiente a la lógica de funcionamiento, permite que podamos realizar modificaciones, como en este caso sobre la lógica, sin necesidad de alterar en absoluto el código XAML de presentación, lo que supone en muchos casos una ventaja en los procesos de mantenimiento de dichas interfaces.

Espero que os resulte interesante.

Un saludo.

Publicado por Luis Miguel Blanco | 7 comment(s)
Archivado en: ,

En la primera entrega de este artículo iniciamos el desarrollo de una página Silverlight que incluía un control DataGrid, cuya principal característica debía consistir en la posibilidad de descargar archivos asociados a las celdas de una columna.

En esa primera parte se trataron aspectos tales como la creación del contexto de datos y el servicio WCF que proveía al DataGrid de la información a mostrar. También vimos la creación manual de columnas junto a los convertidores de tipo, elementos estos fundamentales en el proceso de formatear el dato original antes de ser presentado por el control.

Esta segunda entrega hará hincapié una vez más en el tema de los convertidores y explicará, tal y como habíamos prometido, los detalles relacionados con la columna para la descarga de archivos.

 

Un convertidor para tipos numéricos

La siguiente columna sobre la que tenemos que aplicar un formato es la que muestra el campo Importe. Se trata de valores numéricos que necesitamos visualizar con los separadores decimales y de millar, por lo que la operativa a seguir es prácticamente igual que la descrita para la columna de fecha: crear un convertidor de tipo, declararlo como recurso dentro del código XAML, e incluirlo en la definición de la columna del DataGrid, como vemos en los siguientes fragmentos de código.

public class NumeroConvertidor : IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        decimal nNumero = (decimal)value;
        string sNumero = nNumero.ToString("#,#.00", Thread.CurrentThread.CurrentCulture);
        return sNumero;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return null;
    }

    #endregion
}

 

<UserControl.Resources>
    <!--....-->                    
    <propio:NumeroConvertidor x:Key="cnvNumero" />
</UserControl.Resources>

<!--....-->                    

<my:DataGrid.Columns>
    <!--....-->                   
    <my:DataGridTextColumn 
        Header="Tarifa"  
        DisplayMemberBinding="{Binding Importe, Converter={StaticResource cnvNumero}}" />
    <!--....-->                    
</my:DataGrid.Columns>

 

En la siguiente imagen vemos la nueva columna numérica, formateada según acabamos de indicar.

 

Al observar el resultado en ejecución, el único inconveniente que en principio podemos achacar a esta columna radica en que sus valores no se encuentran alineados a la derecha, como sería lo deseable al tratarse de una columna de contenido numérico. Dado que entre las propiedades de la clase DataGridTextColumn no encontramos ninguna que nos permita realizar la mencionada alineación, debemos buscar otro medio para resolver el problema.

La solución en este caso la encontramos recurriendo al uso de una columna de tipo plantilla -clase DataGridTemplateColumn-la cual nos ofrece un alto grado de personalización ante el dato a mostrar.

Una vez añadida al código XAML la etiqueta correspondiente a este tipo de columna, dentro de la misma situaremos la plantilla de celda -propiedad/etiqueta CellTemplate-en cuyo interior añadiremos una plantilla de datos -etiqueta DataTemplate-, que será la que contenga aquellos controles que nos servirán para ajustar con mayor precisión el valor a mostrar.

El control en cuestión que incluiremos dentro del DataTemplate será un TextBlock sobre el que estableceremos la adecuada alinación de contenido mediante sus propiedades Margin, HorizontalAligment y VerticalAlignment.

<my:DataGrid.Columns>
    <!--....-->
    <my:DataGridTemplateColumn Header="Tarifa">
        <my:DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Importe, Converter={StaticResource cnvNumero}}"
                    Margin="0,0,10,0"
                    HorizontalAlignment="Right" 
                    VerticalAlignment="Center" />
            </DataTemplate>
        </my:DataGridTemplateColumn.CellTemplate>
    </my:DataGridTemplateColumn>
    <!--....-->                    
</my:DataGrid.Columns>

 

A partir de este momento, la columna se mostrará con la alineación que queríamos.

 

Creando la columna para descargar archivos

Y llegamos a la parte del ejemplo en la que crearemos una columna que nos permitirá realizar la descarga de archivos. Como habíamos comentado al comienzo del artículo, esta columna debería mostrar una imagen, en la que al hacer clic, se realice la descarga del correspondiente archivo.

Vamos a asumir que el servidor Web de la agencia de viajes contiene los archivos a descargar en la siguiente ruta:

http://localhost/viajes/folletos/

Mientras que los archivos de imagen están en esta otra ruta:

http://localhost/viajes/paises/

Resulta obvio deducir, que dada la información y comportamiento que debe ofrecer esta columna, tendremos que implementarla utilizando un objeto DataGridTemplateColumn, debido a su mayor nivel de flexibilidad.

En lo que respecta a la funcionalidad de descarga de archivos, recurriremos al uso de un control HyperlinkButton, en cuya propiedad NavigateUri asignaremos la ruta del archivo a descargar, mientras que la propiedad Content nos permitirá mostrar el literal en forma de vínculo sobre el cual, cuando el usuario haga clic, provocará el comienzo de la descarga. Sin embargo, si enlazamos estas propiedades con la fuente de datos mediante un objeto Binding de la siguiente forma.

<my:DataGrid.Columns>
    <!--....-->
    <my:DataGridTemplateColumn Header="Folleto">
        <my:DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <HyperlinkButton NavigateUri="{Binding Folleto}" Content="{Binding Folleto}" />
            </DataTemplate>
        </my:DataGridTemplateColumn.CellTemplate>
    </my:DataGridTemplateColumn>
    <!--....-->                    
</my:DataGrid.Columns>

 

En la celda veremos el nombre del archivo, pero al hacer clic en él, no se producirá descarga alguna debido a que el vínculo no está correctamente construido; para colmo de males, el texto de dicho nombre de archivo tampoco se encontrará correctamente alineado dentro de la celda.

 

El motivo se debe a que la propiedad NavigateUri del control HyperlinkButton espera recibir un tipo Uri, y lo que nosotros le estamos pasando actualmente es una cadena simple, que ni tan siquiera contiene una ruta válida.

Puesto que conocemos la ruta en la que residen los archivos y el nombre del archivo seleccionado nos lo proporciona la fuente de datos a través de un objeto Binding, un modo de construir el vínculo pasaría, de nuevo, por recurrir a un convertidor de tipo que realice esta operación, lo cual vuelve a demostrarnos la potencia que este elemento tiene en la adaptación de los valores a visualizar dentro de la interfaz de usuario.

public class UriConvertidor : IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Uri oUri = new Uri("http://localhost/viajes/folletos/" + value.ToString());
        return oUri;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return null;
    }

    #endregion
}

 

<UserControl.Resources>
    <!--....-->                    
    <propio:UriConvertidor x:Key="cnvUri" />
</UserControl.Resources>

<my:DataGrid.Columns>
    <!--....-->
    <my:DataGridTemplateColumn Header="Folleto">
        <my:DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <HyperlinkButton NavigateUri="{Binding Folleto, Converter={StaticResource cnvUri}}" 
                                 Content="{Binding Folleto}" />
            </DataTemplate>
        </my:DataGridTemplateColumn.CellTemplate>
    </my:DataGridTemplateColumn>
    <!--....-->                    
</my:DataGrid.Columns>

 

Tras agregar el anterior código a la aplicación, cuando a partir de ahora volvamos a hacer clic en las celdas de esta columna, se abrirá el conocido cuadro de diálogo para la descarga del archivo.

 

Añadiendo las imágenes

Para dar el toque final a este ejemplo solamente nos queda un detalle: reemplazar por imágenes el texto que en esta columna aparece en forma de vínculos.

El control HyperlinkButton, como ya hemos visto, dispone de la propiedad Content para albergar el contenido a visualizar, lo cual quiere decir que no estamos obligados a utilizar valores textuales para que el usuario interactúe con el control, sino que podemos asignarle una imagen para que reaccione ante un clic del usuario, navegando al destino especificado en la propiedad NavigateUri. El modo de establecer dicha imagen desde el código XAML pasa por utilizar la propiedad Content como una etiqueta, dentro de la cual situaremos un control Image.

<HyperlinkButton NavigateUri="{Binding Folleto, Converter={StaticResource cnvUri}}">
    <HyperlinkButton.Content>
        <!--....-->
        <!--contenido-->
        <!--....-->
    </HyperlinkButton.Content>
</HyperlinkButton>

 

Centrando seguidamente nuestra atención sobre el control Image, su propiedad Source será la que usaremos para asignar la imagen a mostrar. El valor para dicha propiedad lo tomaremos de la fuente de datos mediante un objeto Binding como en las otras columnas, y como también ocurre con el control HyperlinkButton, debemos crear un convertidor que construya la ruta en la que se encuentra el archivo de imagen, a fin de que el control Image pueda acceder adecuadamente a ella para visualizarla.

public class RutaImagenConvertidor : IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        string sRuta = "http://localhost/viajes/paises/" + value.ToString();
        return sRuta;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion
}

 

<UserControl.Resources>
    <!--....-->                    
    <propio:UriConvertidor x:Key="cnvUri" />
    <propio:RutaImagenConvertidor x:Key="cnvRutaImagen" />
</UserControl.Resources>

<my:DataGrid.Columns>
    <!--....-->
    <my:DataGridTemplateColumn Header="Folleto">
        <my:DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <HyperlinkButton 
                    NavigateUri="{Binding Folleto, Converter={StaticResource cnvUri}}">
                    <HyperlinkButton.Content>                        
                        <Image 
                            Source="{Binding Bandera, Converter={StaticResource cnvRutaImagen}}" 
                            Width="30" Height="30" />
                    </HyperlinkButton.Content>
                </HyperlinkButton>
            </DataTemplate>
        </my:DataGridTemplateColumn.CellTemplate>
    </my:DataGridTemplateColumn>
    <!--....-->                    
</my:DataGrid.Columns>

 

La siguiente imagen muestra finalmente el DataGrid resultante con toda la funcionalidad requerida.

 

Y con esto concluimos el presente artículo, en el que hemos explicado cómo añadir al control DataGrid de Silverlight una funcionalidad que no viene incorporada "de fábrica", pero que posiblemente necesitaremos implementar en diversas ocasiones sobre este control de cuadrícula. El código fuente, al igual que en la primera parte, está disponible en los enlaces C# y VB.

Espero que os resulte de utilidad.

Un saludo.

Publicado por Luis Miguel Blanco | 7 comment(s)
Archivado en:

Durante el desarrollo de una aplicación para entorno Web, uno de los requerimientos con que nos podemos encontrar consiste en ofrecer al usuario la posibilidad de realizar la descarga de un archivo al seleccionar o hacer clic en uno de los controles que forman nuestra página, habitualmente un vínculo construido mediante la etiqueta <a> de HTML junto a su atributo href.

Sin embargo en esta ocasión vamos a ir un paso más allá, trasladando el escenario a una página Silverlight que contenga un control DataGrid; dicho control deberá ofrecer una columna cuyas celdas contengan imágenes en las que al hacer clic, darán al usuario la posibilidad de descargar un archivo a su máquina local.

 

El escenario a desarrollar

Supongamos que una agencia de viajes quiere ofrecer en su portal Web una página con su catálogo de destinos, de forma que los usuarios puedan visualizar la oferta disponible, seleccionar un destino, y descargar un archivo conteniendo el folleto en formato electrónico con la información sobre el destino elegido.

Para desarrollar esta característica podemos utilizar una página Web con contenido Silverlight, donde a través de un control DataGrid ofreceremos esta información que se encuentra contenida en la base de datos de la agencia, dentro de una tabla con la siguiente estructura.

CREATE TABLE [dbo].[Viajes](
    [IDViaje] [bigint] NULL,
    [Nombre] [varchar](50) NULL,
    [FechaAlta] [datetime] NULL,
    [Importe] [decimal](9, 4) NULL,
    [Folleto] [varchar](100) NULL,
    [Bandera] [varchar](100) NULL
) ON [PRIMARY]

 

En la siguiente imagen podemos ver una muestra de los registros contenidos en esta tabla.

 

Creación y configuración del entorno de datos

Comenzaremos por crear desde Visual Studio 2008 un nuevo proyecto de tipo Silverlight, donde estableceremos -desde el proyecto Web que compone la solución-en la ventana Server Explorer una conexión con la base de datos SQL Server AgenciaViajes, a fin de poder acceder a la tabla Viajes.

 

Los siguientes pasos para la creación de la fuente de datos utilizada por el DataGrid ya fueron explicados en este enlace, aunque a continuación los repasaremos brevemente.

Mediante la opción de menú de Visual Studio Project > Add New Item, añadiremos al proyecto Web de la solución un elemento LINQ to SQL Classes, con lo que obtendremos un contexto de datos al que llamaremos DCAgenciaViajes; la propiedad Serialization Mode del contexto de datos deberá tener el valor Unidirectional.

Arrastrando la tabla Viajes desde la ventana Server Explorer hasta el diseñador del contexto de datos, será creada una entidad de datos con el nombre Viaje, que representará los registros de la tabla Viajes, y nos servirá para poder transferir la información desde la fuente de datos hasta el control Silverlight como veremos próximamente.

También agregaremos al proyecto un servicio Web WCF con su correspondiente interfaz, donde escribiremos un método que devuelva, mediante LINQ, el contenido de la tabla Viajes a través de una colección genérica List<Viaje>.

[ServiceContract]
public interface IWSDatos
{
    [OperationContract]
    List<Viaje> ObtenerViajes();
}
//---------------------------------------
public class WSDatos : IWSDatos
{
    #region IWSDatos Members

    public List<Viaje> ObtenerViajes()
    {
        DCAgenciaViajesDataContext dcAgenciaViajes = new DCAgenciaViajesDataContext();

        var lstLista = from tblViajes
                       in dcAgenciaViajes.Viajes
                       select tblViajes;

        return lstLista.ToList();
    }

    #endregion
}

 

Para recuperar dicha colección estableceremos en el proyecto Silverlight una referencia hacia el servicio WCF, y desde el evento de carga de la página XAML crearemos una instancia del servicio, a través de la cual haremos una llamada a su método ObtenerViajes, el cual nos devolverá la colección en el evento de finalización de la llamada. Esta colección será asignada a la propiedad ItemsSource del control DataGrid, que la utilizará para visualizarla en formato tabular.

public partial class Page : UserControl
{
    public Page()
    {
        InitializeComponent();

        this.Loaded += new RoutedEventHandler(Page_Loaded);
    }

    void Page_Loaded(object sender, RoutedEventArgs e)
    {
        RefWSDatos.WSDatosClient wsDatos = new DescargaArchivosCeldasDataGridSL_CS.RefWSDatos.WSDatosClient();
        wsDatos.ObtenerViajesCompleted += new EventHandler<DescargaArchivosCeldasDataGridSL_CS.RefWSDatos.ObtenerViajesCompletedEventArgs>(wsDatos_ObtenerViajesCompleted);
        wsDatos.ObtenerViajesAsync();
    }

    void wsDatos_ObtenerViajesCompleted(object sender, DescargaArchivosCeldasDataGridSL_CS.RefWSDatos.ObtenerViajesCompletedEventArgs e)
    {
        this.grdDatos.ItemsSource = e.Result;
    }
}

 

Comenzando a desarrollar la interfaz de usuario. Presentación básica

Pasemos ahora a la fase de creación de la interfaz de usuario, que como ya hemos mencionado estará compuesta principalmente por un control DataGrid, cuyo proceso de creación y puesta a punto iremos refinando progresivamente, a fin de que el lector pueda apreciar con mejor detalle las diferentes fases de construcción llevadas a cabo.

El modo más simple de presentación de los datos asignados al control en la propiedad ItemsSource consiste en establecer el valor True en su propiedad AutoGenerateColumns.

<my:DataGrid x:Name="grdDatos" 
             Width="575" Height="225" 
             AutoGenerateColumns="True" 
             IsReadOnly="True">    
    <!--....-->
    <!--....-->
</my:DataGrid>

 

Esta  acción visualizará la información con sus valores originales.

 

Creación manual de columnas

Para comenzar con la personalización de las columnas asignaremos el valor False a la propiedad AutoGenerateColumns, esto supone que tendremos que crear manualmente cada columna que necesitemos visualizar, lo cual haremos dentro de la colección Columns del control, utilizando alguna de las clases derivadas de DataGridBoundColumn: DataGridTextColumn para mostrar valores textuales, y DataGridCheckBoxColumn para mostrar valores lógicos a través de una casilla de marcado.

Nota. Dentro del contexto del presente artículo, emplearemos el término "campo" para referirnos a las columnas o campos de la tabla de la base de datos que vamos a visualizar en el DataGrid.

La forma de indicar a un objeto columna el elemento - campo- de la fuente de datos que debe mostrar, pasa por asignar a la propiedad DisplayMemberBinding una expresión de marcado extendida con la información del enlace a establecer utilizando el formato "{Binding NombreCampo}". El siguiente código fuente define varias columnas del origen de datos mediante objetos DataGridTextColumn; como podemos observar, no es necesario crear todas las columnas disponibles en la fuente de datos, sino simplemente aquellas que necesitemos.

<my:DataGrid x:Name="grdDatos" 
             Width="575" Height="225" 
             AutoGenerateColumns="False" 
             IsReadOnly="True">
    
    <my:DataGrid.Columns>
        <my:DataGridTextColumn Header="Código" DisplayMemberBinding="{Binding IDViaje}" />
        <my:DataGridTextColumn Header="Descripción" DisplayMemberBinding="{Binding Nombre}" />
        <my:DataGridTextColumn Header="Creado" DisplayMemberBinding="{Binding FechaAlta}" />        
    </my:DataGrid.Columns>    

</my:DataGrid>

 

La siguiente imagen muestra el resultado obtenido.

 

Convertidores de tipo. Dando forma a los datos

Antes de entrar a resolver el objetivo principal de este artículo: la descarga de archivos desde el control de cuadrícula, observemos un notable inconveniente en la presentación de los datos de tipo fecha, consistente en que dicha información se muestra sin formato alguno, lo cual también es totalmente lógico, puesto que el control desconoce a priori el estilo de formato que queremos aplicar.

La técnica a seguir para establecer un formato personalizado en una columna del DataGrid consiste en la creación de un convertidor de tipo, el cual aplicaremos en aquel punto del código XAML donde se establezca el enlace a datos de la columna; a partir de ese momento, el dato enviado por el origen para ser mostrado en el grid, pasará primeramente por el convertidor, que lo "moldeará" según nuestras necesidades antes de mandarlo a la celda.

Para ello tenemos que añadir al proyecto Silverlight una clase que implemente la interfaz IValueConverter, que se compone de los métodos Convert y ConvertBack. En el primero escribiremos el código para formatear el valor que recibimos del origen de datos tal y como queremos visualizarlo en el control, mientras que en el segundo deberemos devolver el valor formateado al tipo original. Dado que en nuestro ejemplo no vamos a editar las celdas del DataGrid, en el método ConvertBack sencillamente retornaremos null.

using System;
using System.Windows.Data;
using System.Threading;
//....
public class FechaConvertidor:IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        DateTime dtFecha = (DateTime)value;
        return dtFecha.ToString("dd - MMMM - yyyy", culture);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return null;
    }

    #endregion
}

 

El modo de utilizar este convertidor en nuestro código XAML pasa por declarar un espacio de nombres que haga referencia al ensamblado en el que reside la clase del convertidor -en este caso, el mismo ensamblado del proyecto Silverlight. Como nombre para el espacio de nombres utilizaremos la palabra "propio", tal y como vemos en la siguiente figura.

 

A continuación, dentro del apartado de recursos de la página, declararemos un nuevo recurso, que partiendo del espacio de nombres que acabamos de declarar, accederá a la clase del convertidor. También le asignaremos mediante el atributo Key, una clave para su posterior utilización.

<UserControl.Resources>
    <propio:FechaConvertidor x:Key="cnvFecha" />
</UserControl.Resources>

 

Tanto en el momento de declarar el espacio de nombres como al crear el recurso contaremos con la asistencia de Intellisense, que nos evitará tener que escribir el valor, guiándonos además en su selección, como vemos en la siguiente figura.

En el caso de que Intellisense no ofrezca la lista de selección automáticamente, podemos invocarla pulsando la combinación Ctrl + J.

Finalmente, en la definición de la columna que muestra la fecha ampliaremos la expresión de marcado que habíamos asignado a la propiedad DisplayMemberBinding, incluyendo, mediante la propiedad Converter del enlace a datos -objeto Binding-una referencia hacia el convertidor de tipo que hemos declarado como recurso, utilizando la palabra clave StaticResource. La sintaxis empleada tendrá el siguiente formato: {Binding NombreCampo, Converter = {StaticResource ClaveRecurso}}.

<my:DataGrid.Columns>
    <!--....-->                    
    <my:DataGridTextColumn 
        Header="Creado" 
        DisplayMemberBinding="{Binding FechaAlta, Converter={StaticResource cnvFecha}}" />
    <!--....-->                    
</my:DataGrid.Columns>

 

La siguiente imagen muestra el formato resultante sobre la columna de fecha.

 

No obstante seguimos observando una pequeña pega: el nombre de los meses está en inglés, dado que en el método FechaConvertidor.Convert utilizamos el objeto CultureInfo que este manipulador de evento recibe como parámetro. Para evitar tal inconveniente, lo que vamos a hacer es utilizar la información cultural del sistema que obtenemos de la hebra de ejecución de nuestra aplicación -Thread.CurrentThread.CurrentCulture- en forma de objeto CultureInfo. Sustituyendo este objeto por el que estábamos utilizando como segundo parámetro del método DateTime.ToString habremos solucionado el problema.

public class FechaConvertidor : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        DateTime dtFecha = (DateTime)value;
        return dtFecha.ToString("dd - MMMM - yyyy", Thread.CurrentThread.CurrentCulture);
    }
    //....
}

 

Como vemos en la siguiente imagen, las fechas ahora sí que muestran el valor deseado.

 

Y llegados a este punto concluimos la primera parte de este artículo, en la siguiente entrega abordaremos algunos aspectos adicionales sobre los convertidores de tipo, así como la prometida funcionalidad de la columna para realizar la descarga de archivos a través de sus celdas. Para todos aquellos que quieran probar el ejemplo al completo, en los enlaces C# y VB se encuentran los proyectos para los respectivos lenguajes.

Un saludo.

Publicado por Luis Miguel Blanco | 5 comment(s)
Archivado en:

Cuando trabajamos con controles de tipo cuadrícula de datos, podemos encontrarnos ante la necesidad de incluir en una misma columna, varios de los campos obtenidos a partir de la fuente de datos con la que alimentamos el control, de forma que el usuario tenga la sensación de que se encuentra ante un único valor.

Para abordar el desarrollo de un requerimiento de estas características, el control DataGrid de Silverlight cuenta con un poderoso sistema de plantillas enlazadas a datos, que nos van a proporcionar una inusitada flexibilidad a la hora de definir las columnas de nuestro grid.

 

El sistema tradicional

Para conseguir un efecto como el que acabamos de mencionar, el truco al que habitualmente recurre el programador parte de la sentencia SQL utilizada para obtener los datos, y consiste en escribir dicha sentencia incluyendo un campo calculado, que engloba al conjunto de campos que se quieren mostrar en una sola columna.

Supongamos que la tabla con la que vamos a trabajar es Employees, de la base de datos Northwind. Aplicando el mencionado truco, la siguiente consulta nos devolvería los campos TitleOfCourtesy, FirstName y LastName como un único campo llamado NombreCompleto.

SELECT EmployeeID, Title, 
(TitleOfCourtesy + ' ' + FirstName + ' ' + LastName) AS NombreCompleto,
City, Country 
FROM Employees

Sin embargo, imaginemos que solamente necesitamos unir dichos campos en determinadas ocasiones, pero utilizando el mismo origen de datos. Para tales circunstancias podemos emplear la técnica que describiremos a lo largo del presente artículo.

 

Acceso directo a los datos desde ADO.NET

En un artículo publicado con anterioridad en este mismo blog, explicábamos los pasos a seguir para rellenar un control DataGrid de Silverlight utilizando LINQ to SQL, a través de un contexto de datos. Sin embargo, no es este el único medio de acceder al contenido de una fuente de datos, dado que también es posible utilizar para este propósito las clases tradicionales de ADO.NET (SqlConnection, SqlCommand, SqlDataAdapter, SqlDataReader, etc.), con un poco de trabajo adicional por parte del programador.

 

La clase para el mapeo de los registros de la base de datos

Tras crear un nuevo proyecto de tipo Silverlight, el primer paso que daremos será la creación de una clase con el nombre Employee, encargada de representar a los registros de la tabla Employees que posteriormente visualizaremos en el DataGrid. Debido a que la información de estos registros será volcada a una colección genérica de tipo List<Employee>, y enviada al control de cuadrícula a través de un servicio Web, es preciso que el tipo Employee pueda ser seriado, por lo que será necesario calificar la clase con el atributo DataContract, y cada propiedad con el atributo DataMember.

using System;
using System.Runtime.Serialization;
//....
 [DataContract]
public class Employee
{
    private int mnEmployeeID;
    private string msTitle;
    private string msTitleOfCourtesy;
    private string msFirstName;
    private string msLastName;
    private string msCity;
    private string msCountry;

    public Employee(int nEmployeeID, string sTitle, string sTitleOfCourtesy,
        string sFirstName, string sLastName, string sCity, string sCountry)
    {
        mnEmployeeID = nEmployeeID;
        msTitle = sTitle;
        msTitleOfCourtesy = sTitleOfCourtesy;
        msFirstName = sFirstName;
        msLastName = sLastName;
        msCity = sCity;
        msCountry = sCountry;
    }

    [DataMember]
    public int EmployeeID
    {
        get { return mnEmployeeID; }
        set { mnEmployeeID = value; }
    }

    [DataMember]
    public string Title
    {
        get { return msTitle; }
        set { msTitle = value; }
    }

    [DataMember]
    public string TitleOfCourtesy
    {
        get { return msTitleOfCourtesy; }
        set { msTitleOfCourtesy = value; }
    }

    [DataMember]
    public string FirstName
    {
        get { return msFirstName; }
        set { msFirstName = value; }
    }

    [DataMember]
    public string LastName
    {
        get { return msLastName; }
        set { msLastName = value; }
    }

    [DataMember]
    public string City
    {
        get { return msCity; }
        set { msCity = value; }
    }

    [DataMember]
    public string Country
    {
        get { return msCountry; }
        set { msCountry = value; }
    }
}

 

El servicio WCF. Canal de transporte para los datos

El siguiente paso consistirá en la creación del servicio WCF, al que llamaremos WSDatos, donde escribiremos un método encargado de conectar con la base de datos para obtener los registros de la tabla, los cuales pasaremos a una colección genérica de tipo List<Employee>, que devolveremos como resultado del método.

Recordemos que para que la comunicación entre el servicio y Silverlight funcione adecuadamente, en el archivo Web.config del proyecto Web tenemos que establecer el valor basicHttpBinding para el atributo binding de la etiqueta endpoint, dentro de la sección de configuración del servicio WCF.

<endpoint address="" binding="basicHttpBinding" contract="DataGridColumnaValores_CSWeb.IWSDatos">

A continuación se muestra el código fuente del servicio junto a su correspondiente interfaz.

using System.ServiceModel;
//....
[ServiceContract]
public interface IWSDatos
{
    [OperationContract]
    List<Employee> ObtenerEmployees();
}


using System.Collections.Generic;
using System.Data.SqlClient;
//....
public class WSDatos : IWSDatos
{
    public List<Employee> ObtenerEmployees()
    {
        SqlConnection cnConexion = new SqlConnection(ConfigurationManager.ConnectionStrings["CadConexion"].ConnectionString);

        string sSQL = "SELECT EmployeeID, Title, TitleOfCourtesy, " +
            "FirstName, LastName, City, Country " +
            "FROM Employees";

        SqlCommand cmdComando = new SqlCommand(sSQL, cnConexion);

        cnConexion.Open();

        SqlDataReader drReader = cmdComando.ExecuteReader();
        List<Employee> lstEmployees = new List<Employee>();

        while (drReader.Read())
        {
            lstEmployees.Add(new Employee((int)drReader.GetValue(drReader.GetOrdinal("EmployeeID")),
                (string)drReader.GetValue(drReader.GetOrdinal("Title")),
                (string)drReader.GetValue(drReader.GetOrdinal("TitleOfCourtesy")),
                (string)drReader.GetValue(drReader.GetOrdinal("FirstName")),
                (string)drReader.GetValue(drReader.GetOrdinal("LastName")),
                (string)drReader.GetValue(drReader.GetOrdinal("City")),
                (string)drReader.GetValue(drReader.GetOrdinal("Country"))));
        }

        cnConexion.Close();

        return lstEmployees;
    }
}

Esta colección es obtenida desde el evento de carga de la página Silverlight, para lo que previamente deberemos haber establecido en el proyecto Silverlight de la solución, una referencia hacia el servicio WCF.

 

public partial class Page : UserControl
{
    private SolidColorBrush oOriginalBrush;
    private double nTamFuente;
    private StackPanel oStackPanel;
    private TextBlock oTextBlock;

    public Page()
    {
        InitializeComponent();

        this.Loaded += new RoutedEventHandler(Page_Loaded);
    }

    void Page_Loaded(object sender, RoutedEventArgs e)
    {
        RefWSDatos.WSDatosClient wsDatos = new DataGridColumnaValores_CS.RefWSDatos.WSDatosClient();
        wsDatos.ObtenerEmployeesCompleted += new EventHandler<DataGridColumnaValores_CS.RefWSDatos.ObtenerEmployeesCompletedEventArgs>(wsDatos_ObtenerEmployeesCompleted);
        wsDatos.ObtenerEmployeesAsync();
    }
    //....

Completada la llamada al método del servicio, asignaremos el resultado de la misma a la propiedad ItemsSource de un control DataGrid que hemos incluido en el código XAML.

void wsDatos_ObtenerEmployeesCompleted(object sender, DataGridColumnaValores_CS.RefWSDatos.ObtenerEmployeesCompletedEventArgs e)
{
    this.grdDatos.ItemsSource = e.Result;
}

 

Construyendo la interfaz de usuario

La definición inicial de la interfaz de usuario en XAML podemos verla a continuación.

<UserControl 
    x:Class="DataGridColumnaValores_CS.Page"
    xmlns:my="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"  
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="550" Height="350">

    <Grid x:Name="LayoutRoot" Background="LightGreen">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
        </Grid.RowDefinitions>

        <TextBlock Grid.Row="0" HorizontalAlignment="Center" 
                   Margin="10" TextDecorations="Underline">
            Tabla Employees
        </TextBlock>

        <my:DataGrid x:Name="grdDatos" 
                     Grid.Row="1"
                     Width="500" Height="250" 
                     Margin="10"
                     AutoGenerateColumns="False">
        </my:DataGrid>
    </Grid>
</UserControl>

En el caso de que fuéramos a mostrar cada campo de la fuente de datos en columnas independientes, podríamos utilizar la característica de creación automática de columnas, mediante la propiedad AutoGenerateColumns a True, o bien crear nosotros manualmente las columnas mediante etiquetas DataGridTextColumn, como vemos en este fuente.

<my:DataGrid.Columns>
    <my:DataGridTextColumn Header="Código" DisplayMemberBinding="{Binding EmployeeID}" />
    <my:DataGridTextColumn Header="Cargo" DisplayMemberBinding="{Binding Title}" />
    <my:DataGridTextColumn Header="Trat." DisplayMemberBinding="{Binding TitleOfCourtesy}" />
    <my:DataGridTextColumn Header="Nombre" DisplayMemberBinding="{Binding FirstName}" />
    <my:DataGridTextColumn Header="Apellido" DisplayMemberBinding="{Binding LastName}" />
    <my:DataGridTextColumn Header="Ciudad" DisplayMemberBinding="{Binding City}" />
    <my:DataGridTextColumn Header="País" DisplayMemberBinding="{Binding Country}" />
</my:DataGrid.Columns>

Con el resultado mostrado a continuación.

Pero como ya hemos comentado anteriormente, queremos agrupar los campos TitleOfCourtesy, FirstName y LastName en uno, por lo que al tratarse de una visualización personalizada de columna debemos recurrir a la etiqueta DataGridTemplateColumn, que nos permite crear la columna con una mayor libertad de acción.

 

DataGridTemplateColumn. El poder de la personalización en nuestras manos

Mediante DataGridTemplateColumn podemos establecer el modo de visualización y de edición personalizada para los datos de una columna; en esta ocasión nos limitaremos al primero de estos dos modos, para el que debemos utilizar su propiedad CellTemplate; dentro de este último es necesario situar una etiqueta DataTemplate, en cuyo interior agregaremos aquellos elementos que necesitemos para visualizar el valor. En nuestro caso, ya que se trata de mostrar tres valores de texto que no vamos a editar, una buena opción consistiría en emplear un TextBlock para cada uno de ellos. No obstante, si intentamos añadir directamente tres controles TextBlock dentro del DataTemplate de esta manera.

<my:DataGridTemplateColumn Header="Nombre completo">
    <my:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBlock  Text="{Binding TitleOfCourtesy}" Margin="10,0,5,0" />
            <TextBlock  Text="{Binding FirstName}" Margin="0,0,5,0" />
            <TextBlock Text="{Binding LastName}" Margin="0,0,0,0" />
        </DataTemplate>
    </my:DataGridTemplateColumn.CellTemplate>
</my:DataGridTemplateColumn>

Al compilar la aplicación obtendremos un error que nos informa de que el árbol visual está intentando ser asignado más de una vez.

Lo que quiere decir este error es que dentro de la etiqueta DataTemplate solamente puede añadirse un control. En ese caso, ¿cómo nos apañamos para añadir los tres que necesitamos?, pues de una manera muy sencilla: ya que no podemos poner más de un control, utilizaremos sólo uno, pero que permita actuar como contenedor de otros, es decir, emplearemos un panel, el cual nos permitirá hacer lo que pretendemos sin encontrarnos con errores.

<my:DataGridTemplateColumn Header="Nombre completo">
    <my:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding TitleOfCourtesy}" Margin="10,0,5,0" />
                <TextBlock Text="{Binding FirstName}" Margin="0,0,5,0" />
                <TextBlock Text="{Binding LastName}" Margin="0,0,0,0" />
            </StackPanel>
        </DataTemplate>
    </my:DataGridTemplateColumn.CellTemplate>
</my:DataGridTemplateColumn>

Mediante este pequeño truco, conseguimos concatenar varios campos que provienen de la fuente de datos utilizada por el DataGrid en una simple columna. Observemos que para que los valores tengan un mínimo de separación unos de otros, dentro de la declaración de los controles TextBlock utilizamos su atributo Margin allá donde sea necesario.

 

Limando asperezas

Aunque ya hemos conseguido nuestro objetivo, aún podemos "pulir" un poco más el resultado, ya que si observamos detenidamente, el valor de nuestra columna personalizada no queda alineado verticalmente con el resto de columnas del control, sino que se encuentra alineado a la parte superior de la celda. La forma de solucionarlo consiste simplemente en recurrir al atributo VerticalAlignment del StackPanel, asignando el valor Center, para obtener un resultado mucho más adecuado.

<my:DataGridTemplateColumn Header="Nombre completo">
    <my:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                <!--....-->

 

Aplicando un toque personal

Y ya que estamos metidos en faena, aprovechemos para dar una vuelta de tuerca más a este ejemplo, que consistiría en lo siguiente: según el usuario vaya desplazando el cursor por cada uno de los elementos de nuestra columna, debemos resaltar cada una de las partes que la componen de forma independiente, volviendo a su estado original cuando el cursor salga del área que ocupa.

Si utilizamos los eventos MouseEnter y MouseLeave del StackPanel, el efecto se aplicará al unísono sobre todo lo que dicho panel contenga, lo cual no es el comportamiento del que estamos hablando.

Puesto que estamos trabajando con el concepto de elementos contenidos, vamos a dar un paso más allá añadiendo al StackPanel de la plantilla de datos otro panel del mismo tipo para cada uno de los TextBlock; en estos tres nuevos StackPanel codificaremos los antes mencionados eventos del ratón, para conseguir el efecto deseado. El código de marcado es el que vemos en el siguiente listado.

<my:DataGridTemplateColumn Header="Nombre completo">
    <my:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                <StackPanel VerticalAlignment="Center" 
                            MouseEnter="StackPanel_MouseEnter" MouseLeave="StackPanel_MouseLeave">
                    <TextBlock Text="{Binding TitleOfCourtesy}" Margin="10,0,5,0" />
                </StackPanel>

                <StackPanel VerticalAlignment="Center" 
                            MouseEnter="StackPanel_MouseEnter" MouseLeave="StackPanel_MouseLeave">
                    <TextBlock Text="{Binding FirstName}" Margin="0,0,5,0" />
                </StackPanel>

                <StackPanel VerticalAlignment="Center" 
                            MouseEnter="StackPanel_MouseEnter" MouseLeave="StackPanel_MouseLeave">
                    <TextBlock Text="{Binding LastName}" Margin="0,0,0,0" />
                </StackPanel>
            </StackPanel>
        </DataTemplate>
    </my:DataGridTemplateColumn.CellTemplate>
</my:DataGridTemplateColumn>

El code-behind que es necesario escribir para estos eventos es el siguiente.

private SolidColorBrush oOriginalBrush;
private double nTamFuente;
private StackPanel oStackPanel;
private TextBlock oTextBlock;

private void StackPanel_MouseEnter(object sender, MouseEventArgs e)
{
    oStackPanel = sender as StackPanel;
    oTextBlock = oStackPanel.Children[0] as TextBlock;

    oOriginalBrush = oStackPanel.Background as SolidColorBrush;
    nTamFuente = oTextBlock.FontSize;

    oStackPanel.Background = new SolidColorBrush(Colors.Orange);
    oTextBlock.FontSize = 20;
}

private void StackPanel_MouseLeave(object sender, MouseEventArgs e)
{
    oStackPanel = sender as StackPanel;
    oTextBlock = oStackPanel.Children[0] as TextBlock;

    oStackPanel.Background = oOriginalBrush;
    oTextBlock.FontSize = nTamFuente;
}

Para poder retornar al estado original del contenido visualizado, observemos que se declaran con ámbito a nivel de la clase un conjunto de variables que nos permitirán almacenar el objeto Brush, tamaño de fuente, etc. originales.

En la siguiente imagen podemos apreciar el resultado.

Llegados a este punto podemos dar por concluido el desarrollo de este ejemplo, en el que hemos abordado el modo en cómo Silverlight nos permite manipular la presentación de los datos en el control DataGrid a través de su mecanismo de plantillas. El código fuente del proyecto está disponible en los siguientes enlaces: C# y VB.

Esperando que os resulte de utilidad, un saludo para todos.

Publicado por Luis Miguel Blanco | 8 comment(s)
Archivado en: