Mejorando la experiencia de usuario en el control DataForm de Silverlight (2)

En la primera parte de este artículo, sentamos las bases para empezar a trabajar en la optimización de la interfaz de usuario del DataForm construyendo el proyecto en Visual Studio 2010, la fuente de datos, y el formulario con una funcionalidad básica para las plantillas de lectura y edición. En esta segunda entrega será cuando realmente comencemos con el proceso de mejora sobre los controles de edición.

 

NumericUpDown para valores numéricos

Iniciamos nuestro periplo de optimizaciones por el campo Total del formulario. Se trata de un campo de tipo numérico que admite decimales, por lo que revisando la Barra de herramientas de Visual Studio 2010, en busca de un control más adecuado para tratar estos valores, encontramos que NumericUpDown se adapta como un guante a este propósito.

Todo lo que tenemos que hacer es añadir a la plantilla de edición del DataForm un nuevo DataField que contenga un NumericUpDown. A la propiedad Value de este control le asignaremos la expresión de enlace a datos que muestra el valor del campo. Adicionalmente, configuraremos el control para que admita decimales, el valor a incrementar cada vez que hagamos clic en los botones de aumentar/disminuir, y la alineación horizontal.

<toolkit:DataField Label="Importe:">
    <toolkit:NumericUpDown Value="{Binding Path=Total, Mode=TwoWay}" 
                           DecimalPlaces="2" Width="50" Increment="0.5" 
                           HorizontalAlignment="Left" />
</toolkit:DataField>

 

 

DatePicket y TimePicker. Combinando controles para editar campos de tipo datetime

Cuando el control DataForm construye su interfaz de usuario predeterminada, genera controles DatePicker para editar los campos de tipo datetime. Esto generalmente funciona bien para la mayoría de las situaciones, pero ¿qué ocurre si necesitamos editar la parte horaria correspondiente a este tipo de campo?

Una posible solución consiste en utilizar un control TimePicker, el cual nos permitirá editar esta información. Por lo tanto, añadiremos una copia del mismo a la plantilla EditTemplate del formulario de datos, situándolo al lado del control DatePicker ya existente, y encerrando ambos en un StackPanel.

La propiedad utilizada por TimePicker para visualizar la hora es Value, a la que asignaremos la expresión de enlace a datos que la unirá con el campo InvoiceDate. En ambos controles, DatePicker y TimePicker, dicha expresión de enlace es igual, siendo la maquinaría interna de cada control la encargada de editar la parte (fecha u hora) que le corresponda.

<toolkit:DataField Label="Fecha:">
    <StackPanel Orientation="Horizontal">
        <sdk:DatePicker SelectedDate="{Binding Path=InvoiceDate, Mode=TwoWay}" 
                        Width="110" />

        <toolkit:TimePicker Value="{Binding Path=InvoiceDate, Mode=TwoWay}" 
                            Width="110" />
    </StackPanel>
</toolkit:DataField>

Para modificar la parte de fecha del campo InvoiceDate con DatePicker, podemos editar directamente la caja de texto que contiene el valor de fecha, o hacer clic en el icono de este control que despliega el calendario.

Respecto a la edición de la parte horaria del campo con TimePicker, podemos igualmente, editar la caja de texto de este control escribiendo directamente el número, mediante las teclas de flecha arriba/abajo, o bien podemos hacer clic en el icono con forma de reloj, para desplegar una lista de horas.

 

AutoCompleteBox. Editar y seleccionar valores de una lista dinámica

En ciertas tablas de una base de datos pueden existir columnas cuyos valores se repiten a lo largo de los registros que componen la tabla, existiendo, además, la certeza de que en los nuevos registros a incorporar, tales valores se volverán a repetir, como es el caso del campo BillingCity de nuestra tabla de ejemplo Invoice.

Para ayudar al usuario en la introducción del contenido para este campo vamos a recurrir al control AutoCompleteBox, el cual, aparte de proporcionar la funcionalidad de una caja de texto, muestra de forma dinámica una lista desplegable de sugerencias con los valores más parecidos al contenido que la caja tenga en cada momento.

 

El punto principal en la configuración de este control radica en la confección y asignación a su propiedad ItemsSource de la mencionada lista de valores con la que lo alimentamos. Existen diversas técnicas para llevar a cabo esta tarea, algunas de las cuales explicaremos en los próximos apartados.

En primer lugar añadiremos a la plantilla EditTemplate del DataForm una copia de este control. La propiedad Text, al igual que en el TextBox, es la encargada de contener el valor de la caja de texto, por lo que le asignaremos la expresión de enlace a datos que obtenga el valor de la propiedad correspondiente a la entidad de la colección.

<toolkit:DataField Label="Ciudad:">
    <sdk:AutoCompleteBox x:Name="acbBillingCity" 
                         Text="{Binding Path=BillingCity, Mode=TwoWay}" 
                         Width="120" 
                         HorizontalAlignment="Left" />
</toolkit:DataField>

 

AutoCompleteBox. Creación de la lista utilizando la colección de entidades

Cada vez que editemos un elemento de la colección de entidades asignada al DataForm, se activará la plantilla EditTemplate, cargando la entidad actual en los controles del formulario de datos.

Tal acción desencadenará el evento DataForm.ContentLoaded, en cuyo código comprobaremos el modo de edición actualmente establecido en el formulario interrogando a la propiedad Mode (tipo enumerado DataFormMode) del parámetro DataFormContentLoadEventArgs que recibe el evento. En el caso de que su valor sea Edit, obtendremos del DomainDataSource el objeto que representa al contexto de dominio (MusicaGestDomainContext), y mediante su colección de entidades Invoices, empleando una expresión de LINQ, obtendremos todos los valores distintos correspondientes a la propiedad BillingCity de las entidades. A continuación recuperaremos el control AutoCompleteBox añadido al formulario usando el método DataForm.FindNameInContent, asignando a la propiedad ItemsSource la lista de valores obtenida.

<toolkit:DataForm x:Name="frmInvoices" . . . .
                  ContentLoaded="frmInvoices_ContentLoaded">

 

using ControlesDataForm.Web;
//....
private void frmInvoices_ContentLoaded(object sender, DataFormContentLoadEventArgs e)
{
    if (e.Mode == DataFormMode.Edit)
    {
        MusicaGestDomainContext oDomainContext = (MusicaGestDomainContext)this.ddsInvoices.DomainContext;
        var oConsulta = (from oInvoice in oDomainContext.Invoices
                         select oInvoice.BillingCity).Distinct();
        AutoCompleteBox acbBillingCity = (AutoCompleteBox)this.frmInvoices.FindNameInContent("acbBillingCity");
        acbBillingCity.ItemsSource = oConsulta;
    }
}

En tiempo de ejecución, al comenzar a teclear un valor dentro de este control, se abrirá debajo del mismo una lista desplegable compuesta por los valores que acabamos de cargar, pero de los que sólo se mostrarán los que comiencen por el mismo valor que hay contenido en la caja de texto del AutoCompleteBox.

 

 

AutoCompleteBox. Creación de la lista mediante un recurso

Utilizando código XAML es posible crear declarativamente la lista de valores empleando un tipo ObjectCollection, dentro del cual incluiremos los elementos que formarán parte de la colección.

Para declarar tipos de datos de .NET Framework tales como int, double, string, etc., debemos añadir un atributo xmlns a la etiqueta UserControl de la página, que apunte al espacio de nombres System del ensamblado mscorlib. Una vez creada la colección (en la zona de recursos de la página XAML) asignaremos ésta como un recurso estático a la propiedad ItemsSource del AutoCompleteBox. El campo del formulario al que aplicaremos esta técnica será BillingState.

<UserControl x:Class="ControlesDataForm.MainPage"
....    
    xmlns:System="clr-namespace:System;assembly=mscorlib"
....    
>
    <!--....-->
    <UserControl.Resources>
        <!--....-->
        <toolkit:ObjectCollection x:Key="colEstados">
            <System:String>AB</System:String>
            <System:String>AZ</System:String>
            <System:String>BC</System:String>
            <System:String>CA</System:String>
            <System:String>DF</System:String>
            <System:String>Dublin</System:String>
            <System:String>FL</System:String>
             <!--....-->
        </toolkit:ObjectCollection>
    </UserControl.Resources>

    <toolkit:DataForm x:Name="frmInvoices"
        &lt;!--....-->
        <toolkit:DataForm.EditTemplate>
            <!--....-->
            <toolkit:DataField Label="Estado o Provincia:">
                <sdk:AutoCompleteBox Text="{Binding Path=BillingState, Mode=TwoWay}" 
                                     Width="100" HorizontalAlignment="Left"
                                     ItemsSource="{StaticResource colEstados}" />
            </toolkit:DataField>
            <!--....-->

 

 

DomainUpDown. Edición y navegación entre un conjunto de valores

El objetivo de DomainUpDown, al igual que AutoCompleteBox, consiste en seleccionar un valor de una lista (dominio de valores), aunque la diferencia en este caso reside en que dicha lista no se despliega, sino que nos movemos por ella mediante sus controles de desplazamiento.

 

El modo de creación y configuración de este control también es muy similar al de AutoCompleteBox, siendo ItemsSource la propiedad a la que tendremos que asignar la lista de valores. En nuestro ejemplo emplearemos este control para editar el campo BillingCountry, creando la lista de elementos mediante LINQ, como ya vimos anteriormente. En este caso aplicaremos también la partícula orderby a la expresión LINQ para obtener los valores ordenados.

<toolkit:DataField Label="País:">
    <toolkit:DomainUpDown x:Name="dudBillingCountry" 
                          Value="{Binding Path=BillingCountry, Mode=TwoWay}" 
                          Width="120" HorizontalAlignment="Left" />
</toolkit:DataField>

 

private void frmInvoices_ContentLoaded(object sender, DataFormContentLoadEventArgs e)
{
    if (e.Mode == DataFormMode.Edit)
    {
        //....
        var qryConsulta = (from oInvoice in oDomainContext.Invoices
                           orderby oInvoice.BillingCountry
                           select oInvoice.BillingCountry).Distinct();

        DomainUpDown dudBillingCountry = (DomainUpDown)this.frmInvoices.FindNameInContent("dudBillingCountry");
        dudBillingCountry.ItemsSource = qryConsulta;
    }
}

 

El conjunto de valores asignado a un control DomainUpDown inicialmente es cerrado. Esto quiere decir que si escribimos un valor en la caja de texto que no esté en la lista asignada al control, y pulsamos la tecla Intro o cambiamos el foco a otro control del formulario, dicho valor será rechazado, volviendo la zona de edición a recuperar su valor original; por lo que a priori, los únicos valores admisibles son los existentes en la propiedad ItemsSource.

Una forma de alterar este comportamiento del control pasa por asignar a su propiedad InvalidInputAction el valor UseFallbackItem, y en la propiedad FallbackItem un valor igual a uno de los elementos de la lista de valores asignada al control. De esta forma, si el usuario introduce un valor incorrecto, se asignará como valor por defecto el existente en FallbackItem.

<toolkit:DomainUpDown x:Name="dudBillingCountry" 
                      Value="{Binding Path=BillingCountry, Mode=TwoWay}" 
                      Width="120" HorizontalAlignment="Left" 
                      InvalidInputAction="UseFallbackItem" 
                      FallbackItem="Germany" />

Otra técnica consiste en asignar a la propiedad InvalidInputAction el valor TextBoxCannotLoseFocus, lo que mantiene el foco en el control mientras el usuario no introduzca un valor que concuerde con alguno de los existentes en la colección asignada a ItemsSource.

Pero supongamos que el usuario necesita que el valor tecleado en el control sea añadido a su lista de valores en el caso de que no exista. En este tipo de situación, el control DomainUpDown desencadena el evento ParseError, de manera que si escribimos un manipulador para dicho evento podemos adaptar el comportamiento del control a nuestras necesidades.

En el código de este evento obtendremos la instancia del control DomainUpDown a través del parámetro sender que el manipulador recibe, y acto seguido recuperaremos la lista de valores mediante su propiedad Items, volcándola a un tipo List<object>.

Añadiremos al objeto List el valor que el usuario ha escrito en la caja de texto, que se encuentra disponible en la propiedad Text del parámetro UpDownParseErrorEventArgs que recibe el manipulador del evento. Finalmente ordenaremos la lista llamando a su método OrderBy, asignándola de nuevo a la propiedad ItemsSource del control.

Al producirse este evento se vacía la caja de texto del DomainUpDown, por lo que volveremos a asignarle a su propiedad Value el valor que el usuario había tecleado, que como ya hemos indicado, se encuentra en la propiedad UpDownParseErrorEventArgs.Text.

<toolkit:DataField Label="País:">
    <toolkit:DomainUpDown x:Name="dudBillingCountry" 
                          Value="{Binding Path=BillingCountry, Mode=TwoWay}" 
                          Width="120" HorizontalAlignment="Left" 
                          ParseError="dudBillingCountry_ParseError" />
</toolkit:DataField>

 

private void dudBillingCountry_ParseError(object sender, UpDownParseErrorEventArgs e)
{
    DomainUpDown dudBillingCountry = (DomainUpDown)sender;
    List<object> lstPaises = dudBillingCountry.Items.ToList();
    lstPaises.Add(e.Text);
    dudBillingCountry.ItemsSource = lstPaises.OrderBy(sPais => sPais);
    dudBillingCountry.Value = e.Text;
    e.Handled = true;
}

Con este control llegamos al final de la segunda parte del artículo, en el que hemos abordado varias maneras de mejorar la forma en que podemos editar los valores de los campos de un DataForm. En la tercera parte, que concluye la serie, continuaremos mostrando controles adicionales, que consigan hacer que la edición de los campos por parte de nuestros usuarios, sea una labor un poco más fácil y grata. En el siguiente enlace tenemos disponible el proyecto de ejemplo

8 Comentarios

  1. anonymous

    Bravo, esperamos más partes de este tema 🙂 Habrá algo en el futuro de tus maravillosos cubos ?? jeje salu2

  2. lmblanco

    Hola Enrique

    Muchas gracias por tu interés en el tema, y a ver si hago un poco de tiempo y preparo otra entrega sobre alguna cosilla relacionada con los cubos 😉

    Saludotes,
    Luismi

  3. anonymous

    Excelente estos temas, me estan ayudando para comenzar a programar en silverlight. Muchas gracias

  4. lmblanco

    Hola Cristian

    Gracias por tu interés en el artículo y celebro que te sea de ayuda.

    Un saludo,
    Luismi

  5. anonymous

    Existe una solucion mas simple para cargar los paises

    agregas un nuevo domaindatasource y asignas un queryname que devuelva solo la entidad que deseas, y lo enlazas con el combobox.


  6. lmblanco

    Hola Gabriel

    Estupendo!!!. Muchas gracias por tu aportación 🙂

    Un saludo,
    Luismi

  7. luna17sp

    Hola gracias nueamente por estos temas ya pude descargar el ejemplo en el otro enlace pero me parece un error mo me reconoce

  8. lmblanco

    Hola luna17sp

    Pues por el error que me comentas, parece ser que te falta instalar el «Silverlight Toolkit», que es el que incluye ciertos controles como DataForm. Puedes descargarlo del siguiente enlace:

    http://silverlight.codeplex.com/

    Un saludo,
    Luismi

Responder a Cancelar respuesta

Tema creado por Anders Norén