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

En esta tercera entrega del artículo seguiremos con nuestra tarea de editar los campos del DataForm usando dos controles sobradamente conocidos por la gran mayoría de desarrolladores: ComboBox y RadioButton. El código fuente del proyecto está disponible aquí.

 

ComboBox. Seleccionando el valor del campo en una lista desplegable

Continuamos con las operaciones de selección en listas de valores de la mano de uno de los grandes clásicos entre los controles de usuario: ComboBox.

Del mismo modo que en los anteriores controles, el control ComboBox también necesita una colección de elementos para mostrar en su lista desplegable; pero en este caso, en lugar de tratarse de una colección simple de valores, emplearemos la colección de entidades de tipo Customer obtenidas a partir del control DomainDataSource ddsCustomers, que anteriormente añadimos a la página MainPage.xaml.

En primer lugar trasladaremos el control ddsCustomers al bloque de recursos de la página XAML, operación necesaria para que el ComboBox tenga acceso a los datos que ddsCustomers proporciona. Adicionalmente, ordenaremos por la propiedad CustomerName de los objetos Customer el resultado devuelto por este DomainDataSource, utilizando para ello una etiqueta SortDescriptors.

A continuación añadiremos un ComboBox a la plantilla EditTemplate del DataForm, dentro del DataField reservado a la información del cliente de la factura. El código XAML que emplearemos será el siguiente.

<UserControl.Resources>
    <!--....-->
    <riaControls:DomainDataSource x:Name="ddsCustomers" QueryName="GetCustomers">
        <riaControls:DomainDataSource.DomainContext>
            <domainctx:MusicaGestDomainContext />
        </riaControls:DomainDataSource.DomainContext>

        <riaControls:DomainDataSource.SortDescriptors>
            <riaControls:SortDescriptor PropertyPath="CustomerName" />
        </riaControls:DomainDataSource.SortDescriptors>
    </riaControls:DomainDataSource>
</UserControl.Resources>
<!--....-->
<toolkit:DataForm.EditTemplate>
<!--....-->
    <toolkit:DataField Label="Cliente:">
        <StackPanel Orientation="Horizontal">
            <TextBox Text="{Binding Path=CustomerId, Mode=TwoWay}" 
                     Width="40" 
                     IsEnabled="False" />

            <ComboBox x:Name="cboCustomers" 
                      Width="200" 
                      Margin="5,0,0,0"
                      ItemsSource="{Binding Source={StaticResource ddsCustomers}, Path=Data}"
                      DisplayMemberPath="CustomerName"
                      SelectedValuePath="CustomerId" />
        </StackPanel>
    </toolkit:DataField>

Respecto a la configuración de las propiedades del ComboBox, a la propiedad ItemsSource le asignaremos una expresión de enlace a datos cuya fuente sea el DomainDataSource que hemos situado como recurso; mientras que las propiedades DisplayMemberPath y SelectedValuePath contendrán, respectivamente, los valores CustomerName y CustomerId, que corresponden a los nombres de las propiedades de los objetos Customer contenidos en la colección de entidades asignada al ComboBox. Con DisplayMemberPath le indicamos al ComboBox la propiedad a utilizar para los valores a mostrar en la lista, y SelectedValuePath es la propiedad que el control utilizará internamente para informar al DataForm del identificador de cliente seleccionado para la factura.

En el estado actual de la aplicación, cada vez que hagamos clic en el botón de edición del DataForm, el ComboBox siempre mostrará, para CustomerName, el primer valor de la colección, sin mantener la adecuada correspondencia con el valor de CustomerId.

Para corregir este comportamiento erróneo, en primer lugar, a través de la propiedad x:Name, asignaremos un nombre al control TextBox que contiene el valor del campo CustomerId.

<TextBox x:Name="txtCustomerId"
         Text="{Binding Path=CustomerId, Mode=TwoWay}"
         Width="40"
         IsEnabled="False" />

Seguidamente escribiremos en el manipulador del evento ContentLoaded del DataForm un bloque de código en el que obtendremos la instancia del mencionado TextBox y el contexto de dominio del control ddsCustomers. Ambos objetos nos permitirán construir una expresión LINQ, que tendrá como resultado el objeto Customer cuya propiedad CustomerId corresponde  al cliente actual de la factura. Como último paso de este proceso recuperaremos la instancia del ComboBox y asignaremos a su propiedad SelectedItem el objeto Customer obtenido. 

private void frmInvoices_ContentLoaded(object sender, DataFormContentLoadEventArgs e)
{
    if (e.Mode == DataFormMode.Edit)
    {
        //....
        TextBox txtCustomerId = (TextBox)this.frmInvoices.FindNameInContent("txtCustomerId");

        MusicaGestDomainContext oDomCtxCustomers = (MusicaGestDomainContext)this.ddsCustomers.DomainContext;

        Customer oCustomerActual = (from oCustomer in oDomCtxCustomers.Customers
                         where oCustomer.CustomerId == int.Parse(txtCustomerId.Text)
                         select oCustomer).Single();

        ComboBox cboCustomers = (ComboBox)this.frmInvoices.FindNameInContent("cboCustomers");

        cboCustomers.SelectedItem = oCustomerActual;
    }
}

A partir de ahora, el elemento visualizado por el ComboBox sí corresponderá con el valor adecuado cada vez que entremos en el modo de edición del formulario de datos.

 

No obstante, el comportamiento del ComboBox dentro del formulario de datos sigue sin ser adecuado. Expliquemos esto con más detalle: cuando situados en modo de edición, el usuario modifica un campo del DataForm, éste detecta el cambio habilitando el botón OK para poder hacer clic en él y confirmar las modificaciones. Esta situación no se está produciendo actualmente para el ComboBox, ya que la selección de un nuevo valor en dicho control no hace que se active el botón OK.

Para conseguir esta funcionalidad vamos a escribir un bloque de código en el manipulador del evento SelectionChanged del ComboBox. Dentro de dicho evento recuperaremos las instancias de los controles ComboBox y TextBox, asignando a este último el valor seleccionado en la lista desplegable, lo que producirá la activación del botón OK del DataForm. 

<ComboBox x:Name="cboCustomers" 
....
          SelectionChanged="cboCustomers_SelectionChanged" />

 

private void cboCustomers_SelectionChanged(object sender, SelectionChangedEventArgs e) { ComboBox cboCustomers = (ComboBox)this.frmInvoices.FindNameInContent("cboCustomers"); if (cboCustomers != null) { string sCustomerIdComboBox = cboCustomers.SelectedValue.ToString(); TextBox txtCustomerId = (TextBox)this.frmInvoices.FindNameInContent("txtCustomerId"); txtCustomerId.Text = sCustomerIdComboBox; } }

 

Empleando esta técnica ya hemos conseguido que el control ComboBox trabaje de manera coordinada con la maquinaria del DataForm. Sin embargo, demos otra vuelta de tuerca a esta situación: supongamos que en la plantilla EditTemplate del formulario prescindimos del TextBox txtCustomerId, ¿cómo conseguimos entonces que el DataForm se percate de los cambios de selección que hagamos en el ComboBox?

La solución pasa por manipular la propiedad DataForm.CurrentItem, la cual contiene el objeto que representa a la entidad actualmente en edición en el formulario de datos; en nuestro caso un objeto Invoice.

Primeramente escribiremos el siguiente bloque de código en el evento DataForm.ContentLoaded, que nos permitirá establecer el valor correcto en el ComboBox al entrar en el modo de edición del formulario.

private void frmInvoices_ContentLoaded(object sender, DataFormContentLoadEventArgs e)
{
    if (e.Mode == DataFormMode.Edit)
    {
        //....
        Invoice oInvoiceActual = (Invoice)this.frmInvoices.CurrentItem;

        MusicaGestDomainContext oDomCtxCustomers = (MusicaGestDomainContext)this.ddsCustomers.DomainContext;

        Customer oCustomerActual = (from oCustomer in oDomCtxCustomers.Customers
                                    where oCustomer.CustomerId == oInvoiceActual.CustomerId
                                    select oCustomer).Single();

        ComboBox cboCustomers = (ComboBox)this.frmInvoices.FindNameInContent("cboCustomers");

        cboCustomers.SelectedItem = oCustomerActual;
    }
}

A continuación procederemos de forma similar en el evento ComboBox.SelectionChanged; esta vez  para asignar el valor seleccionado en el ComboBox a la propiedad Invoice.CustomerId de la entidad actualmente en edición. 

private void cboCustomers_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    ComboBox cboCustomers = (ComboBox)this.frmInvoices.FindNameInContent("cboCustomers");

    if (cboCustomers != null)
    {
        int nCustomerIdComboBox = (int)cboCustomers.SelectedValue;
        Invoice oInvoiceActual = (Invoice)this.frmInvoices.CurrentItem;
        oInvoiceActual.CustomerId = nCustomerIdComboBox;
    }
}

Como resultado, al editar ahora la entidad en el DataForm, en el campo del cliente sólo aparecerá el ComboBox.

 

 

RadioButton. Selección de opciones autoexcluyentes

RadioButton es un control que representa una alternativa más en la manera en que podemos editar/seleccionar los valores para un campo en el DataForm, ya que mediante un conjunto de controles de este tipo, podemos ofrecer al usuario varias opciones entre las cuales elegir una para asignar como valor al campo del formulario.

Vamos a emplear este control para editar el campo Region de la tabla Invoice. Dicho campo tiene cinco valores posibles en esta tabla: Asia, Europe, North America, South America y Oceania, por lo que añadiremos el mismo número de controles RadioButton a nuestra página, dentro de la plantilla EditTemplate del DataForm, usando el siguiente bloque de código XAML.

<toolkit:DataField Label="Región:">
    <StackPanel>
        <RadioButton x:Name="rbtAsia" Content="Asia" GroupName="Region" Checked="rbtRegion_Checked" />
        <RadioButton x:Name="rbtEurope" Content="Europe" GroupName="Region" Checked="rbtRegion_Checked" />
        <RadioButton x:Name="rbtNorthAmerica" Content="North America" GroupName="Region" Checked="rbtRegion_Checked" />
        <RadioButton x:Name="rbtSouthAmerica" Content="South America" GroupName="Region" Checked="rbtRegion_Checked" />
        <RadioButton x:Name="rbtOceania" Content="Oceania" GroupName="Region" Checked="rbtRegion_Checked" />
    </StackPanel>
</toolkit:DataField>

Para que el formulario considere a todos estos controles como pertenecientes a un mismo grupo, de forma que solamente uno de ellos pueda estar seleccionado a la vez, hemos asignado el mismo valor a su propiedad GroupName.

A continuación necesitamos codificar la lógica para que el RadioButton adecuado quede marcado cuando entramos en modo de edición de una entidad, para lo cual añadiremos el siguiente código al evento ContentLoaded del formulario de datos, en el que una vez obtenida la instancia de la entidad a editar, y basándonos en el valor de su propiedad Region, obtendremos del formulario el RadioButton correspondiente, para marcarlo mediante su propiedad IsChecked. Nótese que puesto que algunos nombres de región están formados por dos palabras, para componer el nombre del RadioButton, eliminamos los espacios en blanco mediante el método string.Replace.

private void frmInvoices_ContentLoaded(object sender, DataFormContentLoadEventArgs e)
{
    if (e.Mode == DataFormMode.Edit)
    {
        //....
        Invoice oInvoiceActual = (Invoice)this.frmInvoices.CurrentItem;
        //....
        string sRegion = oInvoiceActual.Region;
        RadioButton rbtRegion = (RadioButton)this.frmInvoices.FindNameInContent("rbt" + sRegion.Replace(" ", string.Empty));
        rbtRegion.IsChecked = true;
    }
}

 

La otra parte de la funcionalidad que debemos implementar para estos controles corresponde al cambio en la selección del RadioButton mientras estamos en modo de edición, ya que debemos actualizar la propiedad Region de la entidad Invoice que estamos editando con el valor del RadioButton seleccionado. Con tal finalidad, en la declaración de los controles en el código XAML hemos incluido la llamada al método rbtRegion_Checked, que actuará como manejador del evento Checked. La labor de dicho método consiste en comprobar si el RadioButton pulsado es distinto del valor de la propiedad Region de la entidad en edición; en caso afirmativo actualizamos el valor de la propiedad.

private void rbtRegion_Checked(object sender, RoutedEventArgs e)
{
    Invoice oInvoiceActual = (Invoice)this.frmInvoices.CurrentItem;
    RadioButton rbtRegion = (RadioButton)sender;

    if (oInvoiceActual.Region != rbtRegion.Content.ToString())
    {
        oInvoiceActual.Region = rbtRegion.Content.ToString();
    }
}

Y después de esta demostración de las capacidades de edición del control RadioButton en el formulario de datos concluimos este artículo, en el cual hemos abordado una manera de potenciar las características de edición en el control DataForm, a través del uso de controles alternativos para los campos, en reemplazo del habitual TextBox, utilizado usualmente como control de edición por defecto. Espero que os resulte de interés.

2 Comentarios

  1. luna17sp

    Hola estoy por aqui leyendo tu articulos y me sirven de mucho pero este me sale algo mal porfavor podrias bajar de nuevo el codigo de ejemplo ya que el enlace parace roto te lo agradeceria mucho estoy al pendiente de tu respuesta.

    gracias

  2. lmblanco

    Hola luna17sp

    Muchas gracias por tu interés en el blog, he revisado el enlace para descargar el ejemplo y en efecto, no sé que ha podido pasar 8-(, acabo de arreglarlo y parece que ya funciona. Intenta descargarlo otra vez a ver si ya puedes.

    Un saludo,
    Luismi

Leave a Reply

Tema creado por Anders Norén