April 2008 - Artículos

Preparando unos ejemplos en Silverlight 2.0 para un reciente artículo que publiqué en este blog, me encontré con una desagradable sorpresa: de repente, la depuración no funcionaba, mostrándose en el margen del editor de código un icono distinto al habitual círculo rojo correspondiente al punto de interrupción.

Estaba completamente convencido (o casi) de que en dicho proyecto había funcionado el depurador el día anterior, por lo que no me explicaba qué había podido ocurrir; así que empecé a rastrear un poco a ver si encontraba algún caso parecido.

En el blog de Karsten Januszewski encontré un post con el que pensé que había solucionado el problema, ya que se mencionaba que, efectivamente, de forma aleatoria, la depuración en Silverlight se desactivaba. La manera de arreglarlo consiste en situarnos en el Explorador de soluciones y hacer clic derecho en el sitio Web perteneciente a la solución, seleccionando la opción "Opciones de inicio".

A continuación se abre un cuadro de diálogo en el que aparecen los depuradores disponibles, y aquí debemos marcar la casilla correspondiente al depurador de Silverlight, como vemos en la siguiente imagen.

Abriendo inmediatamente este cuadro de diálogo en mi Visual Studio 2008, lamentablemente comprobé que entre los depuradores no aparecía Silverlight por ningún lado, como puede verse en la siguiente imagen.

Puede que el motivo de que no aparezca la posibilidad de activar el depurador de Silverlight se deba a un problema con la instalación del SDK, pero dado que la solución que había encontrado no parecía muy viable de aplicar, opté por otro modo de abordar el problema, el cual consiste en restablecer el enlace que el sitio Web mantiene con el proyecto Silverlight, ya que probablemente el enlace actual esté dañado.

En primer lugar debemos eliminar el enlace actual, que se encuentra en el mismo cuadro de diálogo de propiedades que acabamos de utilizar. Para acceder a este enlace seleccionaremos el elemento "Silverlight Links", situado en el panel izquierdo. Una vez posicionados seleccionaremos el nombre del proyecto, y haremos clic en el botón Remove para eliminarlo.

El siguiente paso consistirá en crear un nuevo enlace con el proyecto Silverlight, por lo que haremos clic en el botón Add, apareciendo el cuadro de diálogo que vemos en la siguiente imagen, en el que mantendremos todos los valores que aparecen por defecto;  solamente debemos desmarcar la casilla que solicita la creación de la página de prueba aspx para alojar el control Silverlight -obsérvese también la casilla en la que se indica que se habilitará la depuración para Silverlight.

Aceptando este diálogo, el nuevo enlace será creado, y a partir de ese momento, la posibilidad de depuración estará nuevamente habilitada, con lo que ya deberían volver a funcionar adecuadamente los puntos de interrupción que hayamos establecido.

Espero que os resulte de ayuda.

Un saludo.

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

Haciendo algunas pruebas con el control DataGrid de Silverlight 2.0, he observado que si en el código XAML asigno valores a las propiedades HorizontalGridLinesBrush y VerticalGridLinesBrush de la siguiente forma.

<my:DataGrid x:Name="grdDatos" 
    HorizontalGridlinesBrush="Magenta"
    VerticalGridlinesBrush="Aquamarine"
    .... />                     

Al ejecutar la aplicación, las líneas no toman los colores que previamente establecí 8-¿?.

Supongo que será cuestión de la fase beta en que se encuentra Silverlight, por lo que una solución a este curioso comportamiento del control consiste en aplicar valores a estas propiedades en el code-behind.

No obstante, a través del evento Loaded, que a priori podría parecer más adecuado para esta labor, tampoco conseguimos que las líneas de separación de las celdas muestren el color deseado.

Buscando un poco más entre los eventos de la clase DataGrid encontramos LayoutUpdated, con el cual parece que hemos tenido más éxito. Este evento se produce cuando la disposición de elementos que componen la interfaz de usuario ha sido actualizada; uno de los momentos en que ocurre es al comienzo de la ejecución del programa, por lo que parece adecuado su uso. Para utilizarlo en nuestra aplicación primeramente lo declaramos en XAML, de forma que el editor genere el método vacío en el code-behind.

<my:DataGrid x:Name="grdDatos" 
    ....                     
    LayoutUpdated="grdDatos_LayoutUpdated" />

A continuación escribimos la lógica de funcionamiento, donde asignamos el valor a cada una de las anteriormente mencionadas propiedades del control. Para evitar la repetición innecesaria de asignaciones de color a estas propiedades, previamente comprobamos si su contenido ya corresponde al color que necesitamos.

private void grdDatos_LayoutUpdated(object sender, EventArgs e)
{
    if (((SolidColorBrush)this.grdDatos.HorizontalGridlinesBrush).Color != Colors.Magenta)
    {
        this.grdDatos.HorizontalGridlinesBrush = new SolidColorBrush(Colors.Magenta);
    }

    if (((SolidColorBrush)this.grdDatos.VerticalGridlinesBrush).Color != Colors.Cyan)
    {
        this.grdDatos.VerticalGridlinesBrush = new SolidColorBrush(Colors.Cyan);
    }
}

El control resultante en tiempo de ejecución ahora sí que visualizará los colores de las líneas de separación correctamente, como vemos en la siguiente imagen.

Espero que os sirva de ayuda.

Un saludo.

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

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.

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

En una serie de artículos publicados anteriormente sobre el control DataGridView, mostrábamos diversas técnicas para aplicar formato a las celdas de la cuadrícula de datos, empleando el evento CellFormatting para realizar tal operación.

El modo utilizado en esos ejemplos para saber si la columna a formatear era la requerida, pasaba por tomar el parámetro DataGridViewCellFormattingEventArgs que recibe el evento, y usando el valor de su propiedad ColumnIndex, aplicarlo al índice de la colección Columns del DataGridView, para obtener la columna en curso; finalmente, la propiedad Name del objeto columna obtenido nos proporciona su nombre, como vemos a continuación.

private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
    if (this.dataGridView1.Columns[e.ColumnIndex].Name == "FirstName")
    {
        //…. 

En escenarios donde creamos dinámicamente las columnas del DataGridView basándonos en la simple asignación del origen de datos a su propiedad DataSource, esta técnica resuelve correctamente la detección de la columna a formatear, pero si hemos creado previamente las columnas del control desde el diseñador de formularios, existe una manera que resulta más directa a la hora de comprobar la columna de formato.

Vamos a suponer que en nuestro proyecto hemos creado un origen de datos que devuelve las filas de ciertas columnas pertenecientes a la tabla Orders, de la base de datos Northwind.

Dicho origen de datos lo asociamos desde el diseñador del formulario a un DataGridView, acción esta que automáticamente nos construye las columnas de este control de cuadrícula.

Las columnas recién creadas para el DataGridView son objetos derivados de la clase DataGridViewTextBoxColumn; esto lo podemos comprobar si editamos la colección Columns del control y observamos su propiedad ColumnType.

Pero el detalle más importante reside en que estos objetos columna, aunque pertenecientes a la colección DataGridView.Columns, también se encuentran accesibles de forma independiente, de igual manera que el resto de controles que podamos haber añadido a nuestro formulario.

Como consecuencia de esta situación, al escribir el código del manipulador del evento CellFormatting, podemos comparar la propiedad DataGridViewCellFormattingEventArgs.ColumnIndex contra la propiedad Index de la columna sobre la que necesitemos aplicar el formato, del modo que vemos en el siguiente bloque de código.

private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
    if (e.ColumnIndex == this.orderDateDataGridViewTextBoxColumn.Index)
    {
        e.CellStyle.Format = "dddd, dd-MMM-yyyy";
    }
}

Lo que puede aportarnos una cierta mejora en el rendimiento de este evento, ya que al trabajar contra orígenes de datos que contengan gran número de filas, deberá ejecutarse en un elevado número de ocasiones. La siguiente imagen muestra el resultado de la operación de formato del anterior código fuente.

Antes de terminar, quería agradecer a Javier, lector de uno de los anteriormente mencionados artículos sobre CellFormatting, sus sugerencias sobre la técnica de formato comentada en el presente post.

Un saludo.

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

En primer lugar quería comentaros que acabo de actualizar el post que contiene la relación de enlaces concernientes a las herramientas y documentación recomendadas para montar nuestro entorno de desarrollo con Visual Studio 2008, Silverlight.

Y ahora ya sí que pasamos al motivo del presente post, que no es otro que ofrecer una propuesta de orden de instalación de aquellas herramientas necesarias para crear nuestro entorno de desarrollo de aplicaciones para Silverlight 2.0.

Dado que recientemente tuve que formatear el disco duro y hacer una reinstalación completa de todas las aplicaciones, pensé que podría resultar de utilidad indicar el orden que seguí en dicha instalación, de las herramientas a utilizar para el desarrollo con ASP.NET y Silverlight.

Por supuesto que este orden no tiene que ser necesariamente el mejor, pero espero que pueda resultar de orientación a todos aquellos que puedan encontrarse en la misma situación.

No obstante, tampoco es necesario esperar a tener que hacer una reinstalación completa de nuestra máquina, ya que una práctica recomendable -sobre todo cuando vamos a trabajar con versiones beta de nuestras herramientas de desarrollo, o necesitamos disponer de configuraciones basadas en distintas versiones de dichos productos o sistema operativo-consiste en emplear un software de máquinas virtuales como VirtualPC, el cual nos permite crear todas las máquinas virtuales que necesitemos, cada una con su propio sistema operativo y conjunto de aplicaciones necesarias para un entorno de trabajo concreto.

Así que tal y como indicaba al principio, aquí estaría el orden que he seguido, en una máquina con Windows Vista, para la instalación de las diferentes herramientas que componen nuestro entorno de desarrollo para ASP.NET y Silverlight.

 

-Visual Studio 2008                     

-Ajax Control Toolkit  

-Silverlight 2.0 runtime

-ASP.NET Futures

-Visual Studio 2008 hotfix para desarrollo web

-Silverlight 2.0 Beta 1 SDK

-Silverlight 2.0 Tools Beta 1 for Visual Studio 2008

-Expression Blend Preview 2.5 (200803) (con soporte para Silverlight 2.0)

 

La información adicional sobre los enlaces necesarios para descargar estos productos se encuentra en este post.

Espero que os resulte de utilidad.

Un saludo.

 

Hace unos días me dejaron un vetusto portátil equipado con un no menos vetusto procesador AMD Athlon 4 y 256 MB de RAM. Dado que su dueño me otorgó plena libertad para hacerle cuantas maldades a nivel de software se me antojaran, inmediatamente me pregunté si aquel "aparatillo" aguantaría la instalación de Visual Studio 2008; así que dicho y hecho, nos pusimos manos a la obra, y ante nuestra sorpresa sí que lo hizo, permitiéndonos también instalar Ajax Control Toolkit, ASP.NET Futures y las extensiones de Silverlight 2 para Visual Studio 2008; otra cuestión, por supuesto, es la velocidad a la que se ejecuta, muy despaciiiiito ;-).

Sin embargo, al crear un proyecto ASP.NET conteniendo diversos componentes de AJAX, nos encontramos con el siguiente error de ejecución.

Uno de los posibles motivos que provocan este error se debe a que la fecha del sistema no se encuentre debidamente actualizada. En nuestro caso recordé que al arrancar la máquina, en algunas ocasiones se mostraba un error de inicio, el cual nos permitía continuar con la carga del sistema operativo, o bien introducirnos en la BIOS del equipo para restaurar determinados valores que había perdido, tales como la fecha y hora, que había quedado establecida a 01/01/1999.

Comprobando la fecha del sistema en el área de notificación de la barra de tareas de Windows, observamos que, en efecto, esta se encontraba desactualizada, por lo que una vez que procedimos a establecer la fecha correcta, el mencionado error desapareció.

Esperamos que si alguien se encuentra con este mismo problema, este post le ayude a solventarlo.

Un saludo.

Una vez descritas en la primera parte de este artículo las características de la función FlashWindowEx, en esta segunda entrega pasaremos a la parte práctica, con varios ejemplos de uso.

Comencemos a parpadear

El primer ejemplo consistirá en realizar el efecto de parpadeo únicamente sobre el título y borde de la ventana un número determinado de veces, con un cierto intervalo de tiempo entre cada una; para ello utilizaremos el botón btnBasico de la ventana, en cuyo evento Click crearemos una instancia de la estructura FLASHWINFO, a la que pasaremos los datos necesarios para que la función FlashWindowEx se ejecute correctamente, como vemos en el siguiente bloque de código.

using System.Windows.Interop;
//....
private void btnBasico_Click(object sender, RoutedEventArgs e)
{
    // crear la estructura con los datos para pasar a la función
    FLASHWINFO oFlashwInfo = new FLASHWINFO();
    oFlashwInfo.cbSize = Marshal.SizeOf(oFlashwInfo);
    oFlashwInfo.hwnd = new WindowInteropHelper(Application.Current.MainWindow).Handle;
    oFlashwInfo.dwFlags = (int)FlashStatus.FLASHW_CAPTION;
    oFlashwInfo.uCount = 7;
    oFlashwInfo.dwTimeout = 500;

    // ejecutar la función
    FlashWindowEx(ref oFlashwInfo);
}

Lo que acabamos de hacer es obtener el tamaño de la estructura utilizando la clase Marshal, cuya misión consiste en permitirnos interaccionar con código no administrado; en concreto, el método que usaremos será SizeOf, pasándole como parámetro la propia estructura de datos.

El manipulador de la ventana lo obtendremos a través de la propiedad Handle, perteneciente a la clase WindowInteropHelper, contenida en el espacio de nombres System.Windows.Interop -que habremos declarado al comienzo de nuestro código-, pasando al constructor de esta clase una referencia a la ventana actual. Si estamos desarrollando una aplicación Windows Forms, podemos conseguir este manipulador a partir de la clase base del formulario, de la siguiente forma.

oFlashwInfo.hwnd = base.Handle;

El tipo de parpadeo que vamos a usar será sobre el título y borde de la ventana, y dado que hemos creado la enumeración FlashStatus para contener todas estas constantes, haremos uso de la misma, pero teniendo en cuenta que deberemos aplicar una operación de conversión de tipos sobre el valor devuelto por FlashStatus.FLASHW_CAPTION, para asignarlo adecuadamente al miembro dwFlags de la estructura FLASHWINFO.

Después de asignar el número de parpadeos y su intervalo, finalizaremos la operación llamando a la función FlashWindowEx, pasando como parámetro por referencia la estructura FLASHWINFO.

 

Ejecutando el efecto un número indeterminado de veces

En el ejemplo anterior realizamos el parpadeo un número concreto de veces, pero supongamos que queremos iniciar el efecto sin especificar su cantidad, para que sea el usuario quien lo detenga, y adicionalmente, que se visualice tanto en la ventana normal como en su icono de la barra de tareas.

En esta ocasión nos ayudaremos de los botones btnComenzar y btnTerminar de la ventana, pero antes de pasar al código de los mismos, explicaremos un detalle que puede resultar interesante si vamos a llamar a FlashWindowEx desde múltiples puntos del código de la ventana.

Si revisamos el ejemplo anterior, observaremos que la estructura FLASHWINFO la creamos a nivel local del método que hace la llamada a la función de la API de Windows. Sin embargo, podemos optimizar este aspecto de nuestro código utilizando -siempre que sea posible-una única instancia de dicha estructura.

Volviendo al presente ejemplo, lo que haremos será declarar, a nivel de la clase de la ventana, una variable para la estructura, inicializándola con los datos básicos cuando la ventana se haya cargado.

FLASHWINFO moFlashwInfo;
//....
private void Window_Loaded(object sender, RoutedEventArgs e)
{
    moFlashwInfo = new FLASHWINFO();
    moFlashwInfo.cbSize = Marshal.SizeOf(moFlashwInfo);
    moFlashwInfo.hwnd = new WindowInteropHelper(Application.Current.MainWindow).Handle;
}

Si estuviéramos escribiendo la aplicación para Windows Forms, este código podríamos situarlo en el constructor del formulario, pero bajo WPF no podemos obtener el manipulador de ventana en el constructor, ya que todavía no lo tiene asignado, y por ese motivo empleamos el evento Loaded.

A continuación pasaremos al código de los botones que se van a encargar de poner en marcha y parar el parpadeo. Como podemos observar, utilizamos la misma estructura para establecer el efecto necesario en cada ocasión. En lo que respecta al inicio del efecto, combinamos dos valores de la estructura FlashStatus para indicar que el parpadeo se producirá en la ventana y barra de tareas de forma indefinida; para detenerlo, volvemos a asignar el valor adecuado a la misma estructura. En ambos casos, siempre terminamos con la llamada a FlashWindowEx.

private void btnComenzar_Click(object sender, RoutedEventArgs e)
{
    moFlashwInfo.dwFlags = (int)FlashStatus.FLASHW_ALL | (int)FlashStatus.FLASHW_TIMER;
    FlashWindowEx(ref moFlashwInfo);
}

private void btnTerminar_Click(object sender, RoutedEventArgs e)
{
    // establecer parada del parpadeo en la estructura de datos
    moFlashwInfo.dwFlags = (int)FlashStatus.FLASHW_STOP;

    // detener el parpadeo
    FlashWindowEx(ref moFlashwInfo);
}

En la siguiente imagen vemos este ejemplo en ejecución.

 

Parpadear en estado minimizado

Un escenario que se produce con frecuencia consiste en poner en ejecución un proceso con una duración prolongada, lo cual motiva que con toda probabilidad, el usuario minimice la ventana de la aplicación; en esta situación es muy recomendable avisar al usuario cuando el proceso termina, y una de las formas de hacerlo es mediante nuestro querido parpadeo de ventana.

Para intentar emular este contexto de ejecución, lo que haremos será detectar el momento en que el usuario minimiza la ventana, deteniendo la hebra de ejecución de la aplicación durante unos momentos mediante el método Thread.Sleep, para seguidamente, comenzar el efecto de parpadeo con el que avisaremos al usuario de que el proceso ha terminado. Como comportamiento adicional, cuando el usuario restaure la ventana, el parpadeo se detendrá.

Todo lo que necesitaremos en lo que respecta al código a escribir será el evento StateChanged, donde implementaremos la lógica que detectará cuándo la ventana se minimiza, asignando la información a la estructura FLASHWINFO -que reutilizamos del ejemplo anterior al ser visible a nivel de clase-y llamando a la función de la API.

private void Window_StateChanged(object sender, EventArgs e)
{
    // iniciar el parpadeo al pasar dos segundos después de minimizar la ventana
    if (this.WindowState == WindowState.Minimized)
    {
        System.Threading.Thread.Sleep(2000);
        moFlashwInfo.dwFlags = (int)FlashStatus.FLASHW_TRAY | (int)FlashStatus.FLASHW_TIMERNOFG;
        FlashWindowEx(ref moFlashwInfo);
    }
}

 

FlashWindow. Los orígenes

Para conseguir el efecto de parpadeo en las ventanas, originalmente se desarrolló la función FlashWindow, la cual solamente realiza un parpadeo cada vez que es llamada. La firma de esta función en la documentación de Windows es la siguiente.

BOOL WINAPI FlashWindow(__in HWND hWnd, __in BOOL bInvert);

El primer parámetro contiene el manipulador de la ventana sobre la que aplicamos el efecto; el segundo un valor lógico que nos permite realizar un parpadeo, o bien cambiar el estado de la ventana a activa o inactiva; por último, el valor de retorno indica el estado de la ventana previo a la llamada de la función.

Para superar esta limitación, posteriormente se creó la función FlashWindowEx -de ahí el sufijo Ex-, que como hemos visto en los pasados ejemplos, aporta funcionalidad y características adicionales, simplificando nuestra labor de programación.

No obstante, dado que FlashWindow sigue estando disponible, vamos a crear un sencillo ejemplo ilustrativo de su uso.

En primer lugar añadiremos un nuevo botón a la ventana empleada como base para las pruebas.

<Window ….>
        <my1:Button Name="btnFlashWindow" Margin="10" Width="110" Click="btnFlashWindow_Click">Usar FlashWindow</my1:Button>
</Window>

El objetivo de este ejemplo consistirá en instanciar y activar un temporizador cuando pulsemos el anterior botón, de forma que en cada evento Elapsed del temporizador hagamos un parpadeo de ventana. Al mismo tiempo, empleando objetos TimeSpan, controlaremos el tiempo transcurrido, de forma que pasados varios segundos detendremos el temporizador, y por consiguiente el parpadeo.

En primer lugar declararemos en el código de la ventana la función FlashWindow y los elementos para este proceso que necesitan tener ámbito a nivel de clase.

using System.Timers;
//....
public partial class Window1 : Window
{
    //....
    [DllImport("user32.dll")]
    public static extern bool FlashWindow(IntPtr hWnd, bool bInvert);

    IntPtr hwndManipVentana;
    Timer tmrTemporizador;
    TimeSpan tsComienzo;
    //....

Seguidamente, en el evento Loaded de la ventana obtendremos su manipulador -podríamos haber utilizado el miembro hwnd de la estructura FLASHWINFO, pero por simplificar, hemos preferido emplear una variable aparte-, mientras que al pulsar el botón instanciaremos y activaremos el temporizador, obteniendo también la hora de comienzo del proceso. Por último, en el evento Elapsed del temporizador iremos aplicando el parpadeo sobre la ventana, hasta que comprobemos mediante objetos TimeSpan que han transcurrido varios segundos, momento en el que pararemos el temporizador y terminará el efecto de parpadeo.

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    //....
    hwndManipVentana = new WindowInteropHelper(Application.Current.MainWindow).Handle;
}

private void btnFlashWindow_Click(object sender, RoutedEventArgs e)
{
    // instanciar y configurar el temporizador
    tmrTemporizador = new Timer();
    tmrTemporizador.Interval = 500;
    tmrTemporizador.Elapsed += new ElapsedEventHandler(tmrTemporizador_Elapsed);

    // obtener la hora de comienzo del proceso
    DateTime dtComienzo = DateTime.Now;
    tsComienzo = new TimeSpan(dtComienzo.Hour, dtComienzo.Minute, dtComienzo.Second);

    // arrancar el temporizador
    tmrTemporizador.Start();
}

void tmrTemporizador_Elapsed(object sender, ElapsedEventArgs e)
{
    // hacer parpadear la ventana en cada intervalo del temporizador
    FlashWindow(hwndManipVentana, true);

    // obtener la hora actual y si ha transcurrido
    // un determinado tiempo, detener el temporizador
    // y con ello el parpadeo
    DateTime dtFinal = DateTime.Now;
    TimeSpan tsFinal = new TimeSpan(dtFinal.Hour, dtFinal.Minute, dtFinal.Second);
    TimeSpan tsDiferencia = tsFinal - tsComienzo;

    if (tsDiferencia.Seconds == 10)
    {
        tmrTemporizador.Stop();
    }
}

Y con este ejemplo damos por concluido el artículo, en el que a través del uso de estas funciones de la API de Windows, hemos visto un medio de captar la atención de nuestros usuarios sobre el programa que tenemos en ejecución.

Al igual que hicimos en la primera parte, en los enlaces para C# y VB tenemos disponibles los proyectos con los ejemplos para cada lenguaje.

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

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