Plantillas de presentación de datos en el control DataForm de Silverlight (1)

El control DataForm, perteneciente al paquete de controles y componentes Silverlight Toolkit, permite al programador crear formularios de consulta y edición de datos de un modo muy sencillo y rápido; cualidad muy de agradecer cuando desarrollamos la interfaz de usuario de nuestra aplicación.

Si durante el diseño de una página XAML, definimos una fuente de datos mediante un control DomainDataSource y asignamos éste a la propiedad ItemsSource de un DataForm, tendremos listo un formulario con toda la lógica necesaria para realizar la navegación y edición sobre la colección de entidades obtenidas a partir de un origen de datos.

Puede suceder, no obstante, que a pesar de ser un buen candidato para implementar nuestros procesos de manipulación de datos, el modo de presentación o edición predeterminado empleado por el DataForm no se ajuste totalmente a nuestras necesidades, ya sea por los controles utilizados para las operaciones de manipulación con los datos; por la disposición de dichos controles en la interfaz del formulario; o por cualquier otro motivo que dificulte su integración en nuestra interfaz de usuario.

 

Plantillas en el control DataForm

Para resolver esta situación, el control DataForm ofrece un sistema de plantillas, mediante las cuales obtendremos un alto grado de flexibilidad a la hora de establecer la disposición (layout) de los elementos integrantes del formulario, tanto en lo referente a las tareas de visualización como a la edición de datos.

En función del tipo de operación que vayamos a realizar utilizaremos una de las siguientes plantillas, bien de forma independiente o combinadas entre sí, dependiendo en este último caso de si necesitamos que el usuario pueda consultar y editar los datos simultáneamente. Todas ellas heredan de la clase DataTemplate, la cual se utiliza para describir la estructura visual de un objeto de datos.

–EditTemplate. Plantilla para realizar la edición de un elemento existente.

–HeaderTemplate. Plantilla para definir la cabecera del formulario de datos.

–NewItemTemplate. Plantilla para realizar la edición de un nuevo elemento.

–ReadOnlyTemplate. Plantilla para consultar/visualizar el elemento actual.

El objetivo del presente artículo consistirá en explicar el proceso de creación de un DataForm de sólo lectura, utilizando la ayuda, como es lógico, de la plantilla ReadOnlyTemplate. El código fuente del proyecto y el script de la base de datos se encuentran disponibles aquí.

 

Preparación de la fuente de datos

Como datos de ejemplo a mostrar desde el DataForm, crearemos una base de datos con el nombre MusicaGest, que contendrá dos tablas: Invoice y Customer. En el siguiente bloque de código tenemos el script de creación de la base de datos y tablas, así como la inserción de los registros. 

CREATE DATABASE MusicaGest
GO

USE MusicaGest
GO

CREATE TABLE Invoice
(
    InvoiceId int IDENTITY(1,1) NOT NULL,
    CustomerId int NOT NULL,
    InvoiceDate datetime NULL,
    BillingAddress varchar(70) NULL,
    BillingCity varchar(50) NULL,
    BillingCountry varchar(50) NULL,
    Total numeric(10, 2) NULL,
    FirstInvoice bit NULL,
    CONSTRAINT PK_Invoice PRIMARY KEY CLUSTERED (InvoiceId ASC)
)
GO

CREATE TABLE Customer
(
    CustomerId int NOT NULL,
    FirstName nvarchar(40) NOT NULL,
    LastName nvarchar(20) NOT NULL,
    Country nvarchar(40) NULL,
 CONSTRAINT PK_Customer PRIMARY KEY CLUSTERED (CustomerId ASC)
)
GO

INSERT INTO Invoice VALUES (42,'03/27/2007','9, Place Louis Barthou','Bordeaux','France',7.92,1)
INSERT INTO Invoice VALUES (39,'05/09/2007','4, Rue Milton','Paris','France',6.93,0)
INSERT INTO Invoice VALUES (41,'08/23/2007','11, Place Bellecour','Lyon','France',2.97,1)
INSERT INTO Invoice VALUES (13,'10/04/2007','Qe 7 Bloco G','Brasília','Brazil',5.95,0)
INSERT INTO Invoice VALUES (3,'10/12/2007','1498 rue Bélanger','Montréal','Canada',10.93,1)
INSERT INTO Invoice VALUES (14,'07/07/2008','8210 111 ST NW','Edmonton','Canada',6.93,0)
INSERT INTO Invoice VALUES (31,'08/24/2008','194A Chain Lake Drive','Halifax','Canada',3.96,1)
INSERT INTO Invoice VALUES (22,'08/27/2008','120 S Orange Ave','Orlando','USA',2.97,0)
INSERT INTO Invoice VALUES (24,'11/13/2009','162 E Superior Street','Chicago','USA',5.95,1)
INSERT INTO Invoice VALUES (10,'12/01/2009','Rua Dr. Falcão Filho, 155','São Paulo','Brazil',9.90,0)
INSERT INTO Invoice VALUES (54,'01/09/2010','110 Raeburn Pl','Edinburgh ','United Kingdom',2.97,1)
INSERT INTO Invoice VALUES (52,'01/11/2010','202 Hoxton Street','London','United Kingdom',1.98,0)
INSERT INTO Invoice VALUES (37,'03/22/2010','Berger Straße 10','Frankfurt','Germany',2.97,1)
INSERT INTO Invoice VALUES (50,'03/27/2010','C/ San Bernardo 85','Madrid','Spain',4.95,0)
INSERT INTO Invoice VALUES (18,'05/15/2010','627 Broadway','New York','USA',1.98,1)
INSERT INTO Invoice VALUES (23,'09/18/2010','69 Salem Street','Boston','USA',2.97,0)
INSERT INTO Invoice VALUES (50,'11/03/2010','C/ San Bernardo 85','Madrid','Spain',2.97,1)
INSERT INTO Invoice VALUES (36,'11/09/2010','Tauentzienstraße 8','Berlin','Germany',2.97,0)

INSERT INTO Customer VALUES (3,'François','Tremblay','Canada')
INSERT INTO Customer VALUES (10,'Eduardo','Martins','Brazil')
INSERT INTO Customer VALUES (13,'Fernanda','Ramos','Brazil')
INSERT INTO Customer VALUES (14,'Mark','Philips','Canada')
INSERT INTO Customer VALUES (18,'Michelle','Brooks','USA')
INSERT INTO Customer VALUES (22,'Heather','Leacock','USA')
INSERT INTO Customer VALUES (23,'John','Gordon','USA')
INSERT INTO Customer VALUES (24,'Frank','Ralston','USA')
INSERT INTO Customer VALUES (31,'Martha','Silk','Canada')
INSERT INTO Customer VALUES (36,'Hannah','Schneider','Germany')
INSERT INTO Customer VALUES (37,'Fynn','Zimmermann','Germany')
INSERT INTO Customer VALUES (39,'Camille','Bernard','France')
INSERT INTO Customer VALUES (41,'Marc','Dubois','France')
INSERT INTO Customer VALUES (42,'Wyatt','Girard','France')
INSERT INTO Customer VALUES (50,'Enrique','Muñoz','Spain')
INSERT INTO Customer VALUES (52,'Emma','Jones','United Kingdom')
INSERT INTO Customer VALUES (54,'Steve','Murray','United Kingdom')

Estas tablas están basadas en la base de datos Chinook, disponible en CodePlex, pero en nuestro caso el número de registros es notablemente inferior, existiendo, por otra parte, algunas diferencias entre sus estructuras, debido a la necesidad de adaptarlas al proyecto que vamos a desarrollar.

 

Creación del proyecto

A continuación abriremos Visual Studio 2010 y crearemos un nuevo proyecto de tipo Silverlight llamado PlantillaReadOnly, con la opción de WCF RIA Services habilitada. Agregaremos un modelo de datos a partir de nuestra base de datos MusicaGest, que genere sendas entidades en base a las tablas que contiene. También crearemos un servicio de dominio, que proporcione la lógica para la manipulación de las mencionadas entidades.

 

Presentando datos con el control DataForm. La vía fácil y rápida

Como siguiente paso, añadiremos al código de la página XAML un control DomainDataSource, que obtenga la colección de entidades Invoice llamando al método GetInvoices del servicio de dominio.

<UserControl x:Class="PlantillaReadOnly.MainPage"
    xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.DomainServices" 
    xmlns:domainctx="clr-namespace:PlantillaReadOnly.Web"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    
    <Grid x:Name="LayoutRoot" Background="White">
        <riaControls:DomainDataSource x:Name="ddsInvoices" QueryName="GetInvoices">
            <riaControls:DomainDataSource.DomainContext>
                <domainctx:MusicaGestDomainContext />
            </riaControls:DomainDataSource.DomainContext>
        </riaControls:DomainDataSource>

        <StackPanel Background="Turquoise">
        </StackPanel>
    </Grid>
</UserControl>

Finalmente agregaremos un DataForm, que enlazaremos con el DomainDataSource.

<UserControl x:Class="PlantillaReadOnly.MainPage"
    xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.DomainServices" 
    xmlns:domainctx="clr-namespace:PlantillaReadOnly.Web"
    xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <riaControls:DomainDataSource x:Name="ddsInvoices" QueryName="GetInvoices">
            <riaControls:DomainDataSource.DomainContext>
                <domainctx:MusicaGestDomainContext />
            </riaControls:DomainDataSource.DomainContext>
        </riaControls:DomainDataSource>

        <StackPanel Background="Turquoise">
            <toolkit:DataForm x:Name="frmInvoices" 
                      ItemsSource="{Binding ElementName=ddsInvoices, Path=Data}" 
                      Margin="10" />
        </StackPanel>
    </Grid>
</UserControl>

Al ejecutar la aplicación obtendremos un formulario de datos que utiliza el modo de manipulación predeterminado. Esta interfaz de usuario, generada automáticamente por el DataForm, cumple el doble objetivo de servir como medio de visualización y edición de la colección de entidades.

 

Empleando la plantilla ReadOnlyTemplate

Pero supongamos que el formulario tuviera que limitarse a visualizar los datos, sin que existiera la posibilidad de editarlos. Éste sería un aspecto de la aplicación que también podríamos resolver de forma rápida asignando el valor True a la propiedad IsReadOnly del DataForm. 

<toolkit:DataForm x:Name="frmInvoices" 
    ItemsSource="{Binding ElementName=ddsInvoices, Path=Data}"
    Margin="10" 
    IsReadOnly="True">
    <!--....-->

No obstante, nuestro objetivo es lograr un mayor control sobre la interfaz de usuario haciendo uso de la plantilla ReadOnlyTemplate del DataForm; tarea que comenzaremos a desarrollar a partir de ahora.

Para aplicar esta plantilla debemos utilizar las etiquetas ReadOnlyTemplate y DataTemplate en el código XAML de la siguiente manera:

<toolkit:DataForm x:Name="frmInvoices" 
                    ItemsSource="{Binding ElementName=ddsInvoices, Path=Data}" 
                    Margin="10">
    <toolkit:DataForm.ReadOnlyTemplate>
        <DataTemplate>
            <!--....-->
        </DataTemplate>
    </toolkit:DataForm.ReadOnlyTemplate>
</toolkit:DataForm>

Dentro de la etiqueta DataTemplate, la organización de los campos la realizaremos combinando uno o varios paneles, en función de la complejidad con que vayamos a diseñar nuestra interfaz de usuario.

Por cada campo que queramos agregar al formulario incluiremos un control DataField, que servirá para definir una etiqueta con el título del campo. En el interior del DataField insertaremos el control que se ocupará de visualizar, mediante una expresión de enlace a datos (data binding), el valor de uno de los campos de la fuente de datos. Dado que la finalidad de nuestro ejemplo consiste en desarrollar un formulario de sólo lectura, para mostrar el valor de los campos emplearemos controles TextBlock.

<toolkit:DataForm x:Name="frmInvoices" 
        ItemsSource="{Binding ElementName=ddsInvoices, Path=Data}" 
        Margin="10">
    <toolkit:DataForm.ReadOnlyTemplate>
        <DataTemplate>
            <StackPanel>
                <toolkit:DataField Label="Código factura:">
                    <TextBlock Text="{Binding Path=InvoiceId, Mode=OneTime}" />
                </toolkit:DataField>

                <toolkit:DataField Label="Código cliente:">
                    <TextBlock Text="{Binding Path=CustomerId, Mode=OneTime}" />
                </toolkit:DataField>

                <toolkit:DataField Label="Fecha de factura:">
                    <TextBlock Text="{Binding Path=InvoiceDate, Mode=OneTime}" />
                </toolkit:DataField>

                <toolkit:DataField Label="Dirección de entrega:">
                    <TextBlock Text="{Binding Path=BillingAddress, Mode=OneTime}" />
                </toolkit:DataField>

                <toolkit:DataField Label="Ciudad de entrega:">
                    <TextBlock Text="{Binding Path=BillingCity, Mode=OneTime}" />
                </toolkit:DataField>

                <toolkit:DataField Label="País de entrega:">
                    <TextBlock Text="{Binding Path=BillingCountry, Mode=OneTime}" />
                </toolkit:DataField>

                <toolkit:DataField Label="Importe factura:">
                    <TextBlock Text="{Binding Path=Total, Mode=OneTime}" />
                </toolkit:DataField>

                <toolkit:DataField Label="¿Es primer envío?:">
                    <TextBlock Text="{Binding Path=FirstInvoice, Mode=OneTime}" />
                </toolkit:DataField>
            </StackPanel>
        </DataTemplate>
    </toolkit:DataForm.ReadOnlyTemplate>
</toolkit:DataForm>

Al ejecutar nuevamente la solución, el DataForm aparecerá con la configuración de controles que acabamos de definir en su plantilla ReadOnlyTemplate. Un detalle a destacar, relacionado con esta técnica de trabajo, consiste en la plena libertad de que disponemos para situar los campos en el orden y/o ubicación que mejor se adapte a los requisitos de la aplicación. Si bien es cierto que este es un aspecto que también podemos tratar desde la clase de metadatos de la entidad, mediante el atributo Display, la libertad y flexibilidad que nos aporta el uso de las plantillas es muy superior.

Un pequeño inconveniente del que podemos habernos percatado tras aplicar la plantilla, reside en que a pesar de no poder editar los campos del formulario, en la barra de herramientas, junto a los botones de navegación, siguen apareciendo los botones añadir y eliminar. El primero no reviste problema, ya que permanece deshabilitado, pero el segundo sí se encuentra activo, permitiendo eliminar elementos de la colección.

Puesto que la única operación que queremos ofrecer al usuario dentro del formulario es la navegación por la colección de entidades, en el código XAML que usamos para declarar el DataForm, asignaremos a la propiedad CommandButtonsVisibility el valor Navigation, indicando así que solamente los botones de navegación estarán visibles.

<toolkit:DataForm x:Name="frmInvoices" 
    ItemsSource="{Binding ElementName=ddsInvoices, Path=Data}" 
    Margin="10" 
    CommandButtonsVisibility="Navigation">

La propiedad DataForm.CommandButtonsVisibility pertenece al tipo enumerado DataFormCommandButtonsVisibility. Podemos combinar los miembros de esta enumeración para especificar qué botones de operación deseamos ofrecer al usuario en el DataForm.

Y llegados a este punto finalizamos la primera parte del artículo, en el cual hemos realizado una introducción a las plantillas de datos del control DataForm, ilustrando las diferencias entre la creación de un formulario de datos mediante el modo predeterminado y aplicando la plantilla ReadOnlyTemplate. En la segunda entrega abordaremos otros interesantes aspectos relacionados con la presentación de datos mediante este control, tales como el formateo de los valores de los campos y el acceso a una tabla catálogo auxiliar.

 

4 Comentarios

  1. anonymous

    Qué grande !!!

  2. lmblanco

    Hola Enrique y muchas gracias. Me alegra que te guste 😎

    Un saludo.
    Luismi

  3. anonymous

    Después de la introducción inicial a la plantilla ReadOnlyTemplate del control DataForm

  4. anonymous

    Continuando con la tónica iniciada en el artículo sobre la plantilla ReadOnlyTemplate del

Deja un comentario

Tema creado por Anders Norén