Control de valoración

Esta semana debía añadir un control de valoración en una de nuestras aplicaciones, dicho control debía permitir la visualización, pero también la modificación del valor, permitiendo tanto estrellas enteras como medias estrellas.

Exáctamente esto:

image

Tras un rato buscando, lo más cercano que encontré fue un control realizado por Shai Raiten. Sin embargo, su control era de visualización y no de entrada de datos, así que he hecho unos pequeños cambios.

Necesitaremos seguir tres pasos:

  1. Crear el control de valoración
  2. Crear el convertidor que dado un valor muestre las estrellas
  3. Añadir el control de valoración a la vista.

Paso 1 - Creación del UserControl

En primer lugar deberemos crear el UserControl y asignar los recursos que necesitamos. Uno será el Converter, que crearemos más tarde, y el otro la string que usaremos para crear las estrellas.

<UserControl.Resources>
    <converter:RatingValueToWidthConverter x:Key="ratingValueToWidthConverter" />
    <x:String x:Key="StarPath">F1 M 0,217.042L 227.5,217.042L 297.875,
        0L 367.542,217L 595.542,217L 410.208,353.667L 480.708,
        569.667L 297.208,436.667L 116.208,568.167L 185.708,352.667L 0,217.042 Z</x:String>
</UserControl.Resources>

Evidentemente deberemos añadir el xmlns que del namespace donde crearemos el Converter, que en mi caso es:

xmlns:converter="using:AppRating.Converter"

Hecho esto deberemos crear el XAML del UserControl.

<Grid x:Name="MainGrid">
	<Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <Path Name="star" Stretch="Uniform" Data="{StaticResource StarPath}" Grid.Column="0" Grid.Row="1"
        Fill="Gold" Margin="4"
        HorizontalAlignment="Left" VerticalAlignment="Top" d:LayoutOverrides="Width, Height" />
    <Path x:Name="star2" Stretch="Uniform" Data="{StaticResource StarPath}" Grid.Column="1" Grid.Row="1"
        Fill="Gold" Margin="4" 
        HorizontalAlignment="Left" VerticalAlignment="Top" />
    <Path x:Name="star3" Stretch="Uniform" Data="{StaticResource StarPath}" Grid.Column="2" Grid.Row="1"
        Fill="Gold" Margin="4"
        HorizontalAlignment="Left" VerticalAlignment="Top" />
    <Path x:Name="star4" Stretch="Uniform" Data="{StaticResource StarPath}" Grid.Column="3" Grid.Row="1"
        Fill="Gold" Margin="4"
        HorizontalAlignment="Left" VerticalAlignment="Top" />
    <Path x:Name="star5" Stretch="Uniform" Data="{StaticResource StarPath}" Grid.Column="4" Grid.Row="1"
        Fill="Gold" Margin="4" 
        HorizontalAlignment="Left" VerticalAlignment="Top" />

    <TextBlock Text="{Binding Header, Mode=OneWay}" Grid.ColumnSpan="5"
        Style="{StaticResource BaseTextBlockStyle}" Margin="0,12"/>

    <Rectangle x:Name="Mask" Fill="{ThemeResource ApplicationPageBackgroundThemeBrush}" 
        VerticalAlignment="Top" Height="{Binding ElementName=star, Path=ActualHeight}" HorizontalAlignment="Right" 
        Width="{Binding Rating, 
            Converter={StaticResource ratingValueToWidthConverter}}" 
        RenderTransformOrigin="0.5,0.5" Grid.ColumnSpan="5"  Grid.Row="1" Margin="0,4"/>

    <Path Name="border" Stretch="Uniform" Data="{StaticResource StarPath}" Grid.Column="0" Grid.Row="1"
        StrokeThickness="1" Stroke="Goldenrod" Margin="3"
        HorizontalAlignment="Left"
        VerticalAlignment="Top" d:LayoutOverrides="Width, Height"/>
    <Path x:Name="border2" Stretch="Uniform" Data="{StaticResource StarPath}" Grid.Column="1" Grid.Row="1"
        StrokeThickness="1" Stroke="Goldenrod" Margin="3"
        HorizontalAlignment="Left" VerticalAlignment="Top"/>
    <Path x:Name="border3" Stretch="Uniform" Data="{StaticResource StarPath}" Grid.Column="2" Grid.Row="1"
        StrokeThickness="1" Stroke="Goldenrod" Margin="3"
        HorizontalAlignment="Left" VerticalAlignment="Top" />
    <Path x:Name="border4" Stretch="Uniform" Data="{StaticResource StarPath}" Grid.Column="3" Grid.Row="1"
        StrokeThickness="1" Stroke="Goldenrod" Margin="3"
        HorizontalAlignment="Left" VerticalAlignment="Top" />
    <Path x:Name="border5" Stretch="Uniform" Data="{StaticResource StarPath}" Grid.Column="4" Grid.Row="1"
        StrokeThickness="1" Stroke="Goldenrod"  Margin="3"
        HorizontalAlignment="Left" VerticalAlignment="Top" />

    <Canvas Grid.ColumnSpan="5" Grid.Row="1" Tapped="Canvas_Tapped" Background="Transparent"/>
</Grid>

Como veis, he hecho alguna modificación respecto al código de Shai Raiten, he creado una Grid con columnas para albergar las estrellas en lugar de usar margins (de esta forma nos será más fácil controlar el tamaño del control) y he añadido un Canvas al final de todo que mediante un evento Tapped nos permitirá modificar el Rating pinchando en las estrellas.

Finalmente iremos al CS del usercontrol para añadir las propiedades Header y Rating al UserControl, para que puedan ser modificadas desde el XAML de la vista.

public static readonly DependencyProperty RatingProperty =
    DependencyProperty.Register("Rating", typeof(double), typeof(UCRating), null);

public static readonly DependencyProperty HeaderProperty =
    DependencyProperty.Register("Header", typeof(string), typeof(UCRating), null);

public event PropertyChangedEventHandler PropertyChanged;

public UCRating()
{
    this.InitializeComponent();
    (this.Content as FrameworkElement).DataContext = this;
}

void SetValueDP(DependencyProperty property, object value,
    [System.Runtime.CompilerServices.CallerMemberName]
    String p = null)
{
    SetValue(property, value);
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(p));
    }
}

/// Texto del TextBlock
public string Header
{
    get { return (double)GetValue(HeaderProperty); }
    set { SetValue(HeaderProperty, value); }
}

/// Valoración de las estrellas
public double Rating
{
    get { return (double)GetValue(RatingProperty); }
    set { SetValue(RatingProperty, value); }
}

Además del evento del Canvas que nos servirá para actualizar el control.

/// Evento que se dispara al hacer Tap en el canvas
private void Canvas_Tapped(object sender, TappedRoutedEventArgs e)
{
    var position = e.GetPosition((Canvas)sender).X;
    double max = ((Canvas)sender).ActualWidth;
    Rating = 5 - Math.Round(((-position + max) * 5 / max) * 2, MidpointRounding.AwayFromZero) / 2;
}

Le he añadido un Match Round para que permita la entrada de valor enteros y medios.

Paso 2 - Creación del Converter

Aquí no hay mucho secreto, creamos un convertidor que dado un valor de tipo double nos devuelva un ancho, que hará que la máscara nos muestre pintadas las estrellas que tocan. 

/// Convertidor que devuelve el ancho dependiendo de un double
public class RatingValueToWidthConverter : IValueConverter
{
    //ancho del control
    private const double Max = 150;

    public object Convert(object value, Type targetType, object parameter, string lang)
    {
        var rate = (double)value;
        return ((((rate) * Max) / 5.0) - Max) * -1.0;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string lang)
    {
        throw new NotImplementedException();
    }
}

Hay que tener en cuenta que si queremos modificar el ancho del control lo deberemos modificar también aquí en el valor Max.

Paso 3 - Añadir el UserControl al XAML de la vista

Finalmente añadimos el “xmlns” y el control a la vista.

xmlns:uc="using:AppRating.UserControls"
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">	<uc:UCRating x:Uid="UCRating"  Header="Valoración" Rating="3.5" Width="150" VerticalAlignment="Center"/>
</Grid>

Obviamente, si usamos MVVM podemos bindear en Rating la propiedad que queramos y se actualizará cuando pulsemos en el control para modificar la cantidad de estrellas. Lo mismo con Header, si usamos resources podremos cargar desde el fichero de recursos el texto que queramos usando la key UCRating.Header.

blog comments powered by Disqus