Saturday 17 May 2014

Star rating control with dynamically adding star with rating in decimal points

In this article we are going to see how to create a star contol in which we can add dynamically the no of stars need in the control and as well as we can give the ratings in decimal also. Additionally we can add few properties which can used to change the color of star and the outline color and as well as background of the whole control.

 while start to write this control the code is not in a regular standard, i only  targets the write the code for the control instead of the standard, so at the start there are some junky code in the control, the funny thing at one point the Control is ready it started to show the design in the Designer, but when i try to run the code it results in wrong render, i am wired why the code works in design not works correctly in run-time, The issue is when i am giving the rating value as 2.3 in design time it works well and render the star up to 2.3 , but in  run-time it renders full three star, I thought i need to knew the answer for this why this kind of behavior, even that time the code is not in standard format, so i raised the issue to the Microsoft about this as issue, to find the cause Why it works in design and not in Runtime. The answer i get from the Microsoft is that the order of the Execution of  dependency property is different for run-time and design time, that's y it is working for design time and Runtime, so i tweeked some code and make it work for both environment.

So now let we start the code and program to code what we need is mention in the below diagram.

                                   


First we have to get a clarify. what and all we need to understand the requirement to do we need the Six property

1. Rating                            : For giving the Rating 
2. NoOfStars                     : dynamic adding the stars additonally 
3. ContentColor                 : Color of the Star
4. OutlineColor                  : Color of the Outline of the Star
5. BackgroundColor          : Background color of the control
6. Orientation                    : Orientation of the control

So building a star control is easy by just have the path of the star and fill the color , then how we can give the decimal  like 4.3 rating  for that i have some logic below is the star path code.


<Path Name="BackstarPath" Fill="OrangeRed" StrokeThickness="5" Data="F1 M 145.637,174.227L 127.619,110.39L 180.809,70.7577L 114.528,68.1664L 93.2725,5.33333L 70.3262,67.569L 4,68.3681L 56.0988,109.423L 36.3629,172.75L 91.508,135.888L 145.637,174.227 Z" />

Above code to draw the star , the plan i have to set the three layers where first layer is full star ,second layer is masking rectangle which is set to the background color, third layer is the star with only outline. so when 
user set the value is 2.4 , i will move the margin of rectangle to the left side so whole star will be visible, for 0.3 i will move 3 part of 10 from the total length of star.


Star Control
***********


<UserControl x:Class="RatingControl.Star"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             x:Name="cntrl"
             d:DesignHeight="400" d:DesignWidth="400" Height="50" Width="50">
    <Viewbox>
        <Grid x:Name="strgrid">       
            <Path Name="BackstarPath" Fill="OrangeRed" StrokeThickness="5" Data="F1 M 145.637,174.227L 127.619,110.39L 180.809,70.7577L 114.528,68.1664L 93.2725,5.33333L 70.3262,67.569L 4,68.3681L 56.0988,109.423L 36.3629,172.75L 91.508,135.888L 145.637,174.227 Z" />
            <Rectangle x:Name="Ratingdisc" Fill="White" Margin="0,0,0,0"></Rectangle>
            <Path Name="OutlinestarPath" Fill="Transparent" StrokeThickness="5" Stroke="Orange"  Data="F1 M 145.637,174.227L 127.619,110.39L 180.809,70.7577L 114.528,68.1664L 93.2725,5.33333L 70.3262,67.569L 4,68.3681L 56.0988,109.423L 36.3629,172.75L 91.508,135.888L 145.637,174.227 Z" />
        </Grid>
    </Viewbox>
</UserControl>




using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace RatingControl
{
    /// <summary>
    /// Interaction logic for Star.xaml
    /// </summary>
    public partial class Star : UserControl
    {
        public Star()
        {           
            InitializeComponent();
            this.DataContext = this;          
        }
      

        public static readonly DependencyProperty StarValueProperty =
            DependencyProperty.Register("StarValue", typeof(decimal), typeof(Star), new FrameworkPropertyMetadata(0.0m,FrameworkPropertyMetadataOptions.AffectsMeasure|
                               FrameworkPropertyMetadataOptions.AffectsArrange|
                               FrameworkPropertyMetadataOptions.AffectsParentArrange|
                               FrameworkPropertyMetadataOptions.AffectsParentMeasure|
                               FrameworkPropertyMetadataOptions.AffectsRender,
                               OnStarValueChanged,OnStarValueCoerce));
    


        public static readonly DependencyProperty BackgroundColorProperty = DependencyProperty.Register("BackgroundColor", typeof (SolidColorBrush), typeof (Star), 
                     new PropertyMetadata(Brushes.Transparent,OnBackgroundColorChanged));

   

        public static readonly DependencyProperty ContentColorProperty = DependencyProperty.Register("ContentColor",typeof (SolidColorBrush), typeof (Star),
                  new FrameworkPropertyMetadata(Brushes.Transparent, OnContentColorChanged));


        public static readonly DependencyProperty OutlineColorProperty =
           DependencyProperty.Register("OutlineColor", typeof(SolidColorBrush), typeof(Star),                   new PropertyMetadata(Brushes.Transparent, OnOutlineColorChanged));


        public decimal StarValue
        {
            get { return (decimal)GetValue(StarValueProperty); }
            set { SetValue(StarValueProperty, value); }
        }
     

        public SolidColorBrush BackgroundColor
        {
            get { return (SolidColorBrush)GetValue(BackgroundColorProperty); }
            set { SetValue(BackgroundColorProperty, value); }
        }


        public SolidColorBrush ContentColor
        {
            get { return (SolidColorBrush) GetValue(ContentColorProperty); }
            set{SetValue(ContentColorProperty,value);}
        }


        public SolidColorBrush OutlineColor
        {
            get { return (SolidColorBrush)GetValue(OutlineColorProperty); }
            set { SetValue(OutlineColorProperty, value); }
        }

              
        private static void OnOutlineColorChanged(DependencyObject d,                                                                            DependencyPropertyChangedEventArgs e)
        {
            var starcontrol = d as Star;
            if (starcontrol != null)
                starcontrol.OutlinestarPath.Stroke = e.NewValue as SolidColorBrush;
        }


        private static void OnContentColorChanged(DependencyObject d,                                                                            DependencyPropertyChangedEventArgs e)
        {
            var starcontrol = d as Star;
            if (starcontrol != null)
                starcontrol.BackstarPath.Fill = e.NewValue as SolidColorBrush;
        }
       
        private static void OnBackgroundColorChanged(DependencyObject d,                                                                           DependencyPropertyChangedEventArgs e)
        {
            var starcontrol = d as Star;
            if (starcontrol != null)
            {
                starcontrol.strgrid.Background = e.NewValue as SolidColorBrush;
                starcontrol.Ratingdisc.Fill = e.NewValue as SolidColorBrush;
            }

        }

        private static object OnStarValueCoerce(DependencyObject d, object baseValue)
        {
            var current = (decimal)baseValue;
            if (current < 0)
                current = 0;
            if (current > 1)
                current = 1;
            return current;
        }


        private static void OnStarValueChanged(DependencyObject d,                                                                           DependencyPropertyChangedEventArgs e)
        {
            var starcontrol = d as Star;
            if (starcontrol != null)
            {
                var val = (decimal)e.NewValue;               
                double leftmove = (184 * (double)(val * 10)) / 10;
                starcontrol.Ratingdisc.Margin = new Thickness(leftmove, 0, 0,0);             
                starcontrol.InvalidateArrange();
                starcontrol.InvalidateMeasure();
                starcontrol.InvalidateVisual();
            }
        }

    }
}



Rating Control
*************


<UserControl x:Class="RatingControl.StarRatingControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <Viewbox>
    <StackPanel x:Name="StarratingPanel" Orientation="Horizontal">
       
    </StackPanel>
    </Viewbox>
</UserControl>




using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace RatingControl
{
    /// <summary>
    /// Interaction logic for StarRatingControl.xaml
    /// </summary>
    public partial class StarRatingControl : UserControl
    {
        public StarRatingControl()
        {
            InitializeComponent();
           
        }
              
        public int NoOfStar
        {
            get { return (int)GetValue(NoOfStarProperty); }
            set { SetValue(NoOfStarProperty, value); }
        }

        public decimal Rating
        {
            get { return (decimal)GetValue(RatingProperty); }
            set { SetValue(RatingProperty, value); }
        }

        public SolidColorBrush BackgroundColor
        {
            get { return (SolidColorBrush)GetValue(BackgroundColorProperty); }
            set { SetValue(BackgroundColorProperty, value); }
        }

        public SolidColorBrush ContentColor
        {
            get { return (SolidColorBrush)GetValue(ContentColorProperty); }
            set { SetValue(ContentColorProperty, value); }
        }

        public SolidColorBrush OutlineColor
        {
            get { return (SolidColorBrush)GetValue(OutlineColorProperty); }
            set { SetValue(OutlineColorProperty, value); }
        }

        public  Orientation StarOrdering
        {
            set
            {
                SetValue(StarOrderingProperty, value);
            }
            get
            {
                return (Orientation)GetValue(StarOrderingProperty);
            }
        }


        public static readonly DependencyProperty StarOrderingProperty =   DependencyProperty.Register("StarOrdering",typeof(Orientation),typeof(StarRatingControl),
            new FrameworkPropertyMetadata(Orientation.Horizontal,OnStarOrderChanged));


        public static readonly DependencyProperty NoOfStarProperty =
            DependencyProperty.Register("NoOfStar", typeof(int), typeof(StarRatingControl),               new FrameworkPropertyMetadata(0,FrameworkPropertyMetadataOptions.AffectsMeasure |                 FrameworkPropertyMetadataOptions.AffectsArrange|                                             FrameworkPropertyMetadataOptions.AffectsParentArrange |                                       FrameworkPropertyMetadataOptions.AffectsParentMeasure|                                       FrameworkPropertyMetadataOptions.AffectsRender, 
                OnNoofStarChanged, OnNoOfStarCoerce));



        public static readonly DependencyProperty RatingProperty =
          DependencyProperty.Register("Rating", typeof(decimal), typeof(StarRatingControl),             new FrameworkPropertyMetadata(0.0m,FrameworkPropertyMetadataOptions.AffectsMeasure|
              FrameworkPropertyMetadataOptions.AffectsArrange
              FrameworkPropertyMetadataOptions.AffectsParentArrange |                                       FrameworkPropertyMetadataOptions.AffectsParentMeasure
              FrameworkPropertyMetadataOptions.AffectsRender, 
              OnRatingChanged, OnRatingCoerce));


        public static readonly DependencyProperty BackgroundColorProperty =                                         DependencyProperty.Register("BackgroundColor", typeof(SolidColorBrush),                       typeof(StarRatingControl),
                      new FrameworkPropertyMetadata((SolidColorBrush)Brushes.Transparent,
                      new PropertyChangedCallback( OnBackgroundColorChanged)));


        public static readonly DependencyProperty ContentColorProperty =                                 DependencyProperty.Register("ContentColor",typeof(SolidColorBrush), 
            typeof(StarRatingControl), 
            new FrameworkPropertyMetadata(Brushes.Transparent, OnContentColorChanged));


        public static readonly DependencyProperty OutlineColorProperty =
           DependencyProperty.Register("OutlineColor", typeof(SolidColorBrush),                          typeof(StarRatingControl), 
           new PropertyMetadata(Brushes.Transparent, OnOutlineColorChanged));


        private static void OnStarOrderChanged(DependencyObject d,                                                                           DependencyPropertyChangedEventArgs e)
        {
            var starcontrol = d as StarRatingControl;
            if(starcontrol!=null)
            {
                starcontrol.StarratingPanel.Orientation = (Orientation)e.NewValue;
            }
        }

        private static void OnOutlineColorChanged(DependencyObject d,                                                                            DependencyPropertyChangedEventArgs e)
        {
            var starcontrol = d as StarRatingControl;
            if (starcontrol != null)
            {
                foreach (Star ctrl in starcontrol.StarratingPanel.Children)
                {
                    ctrl.OutlineColor = (SolidColorBrush)e.NewValue;
                }
            }   
        }


        private static void OnContentColorChanged(DependencyObject d,                                                                            DependencyPropertyChangedEventArgs e)
        {
            var starcontrol = d as StarRatingControl;
            if (starcontrol != null)
            {
                foreach (Star ctrl in starcontrol.StarratingPanel.Children)
                {
                    ctrl.ContentColor = (SolidColorBrush)e.NewValue;
                }
            }

            decimal rateval =(decimal)starcontrol.GetValue(RatingProperty);
            AddStarsValue(starcontrol,rateval);
        }


        private static void OnBackgroundColorChanged(DependencyObject d,                                                                           DependencyPropertyChangedEventArgs e)
        {
            var starcontrol = d as StarRatingControl;
            if (starcontrol != null)
            {
                foreach (Star ctrl in starcontrol.StarratingPanel.Children)
                {
                    ctrl.BackgroundColor = (SolidColorBrush)e.NewValue;
                }
            }               
        }


        private static object OnNoOfStarCoerce(DependencyObject d, object baseValue)
        {
            int curvalue = (int)baseValue;

            if (curvalue < 0)
            {
                curvalue = 0;
            }
            if (curvalue > 20)
            {
                curvalue = 20;
            }
            return curvalue;
        }


        private static void OnNoofStarChanged(DependencyObject d,                                                                            DependencyPropertyChangedEventArgs e)
        {
            StarRatingControl cntrl = d as StarRatingControl;
            decimal ratevalue = (decimal)cntrl.GetValue(RatingProperty);

            var stars = (int)e.NewValue;
            if (cntrl != null)
            {
                int existchild = cntrl.StarratingPanel.Children.Count;
                if(existchild<stars)
                {
                    int reminstar = stars - existchild;

                    for (var i = 0; i < reminstar; i++)
                    {
                        var str = new Star();                     
                        cntrl.StarratingPanel.Children.Add(str);

                    }
                }
                else
                {
                    int removestar = existchild - stars;                                                          cntrl.StarratingPanel.Children.RemoveRange(
                     cntrl.StarratingPanel.Children.Count - removestar - 1, removestar);
                }
               
                if (ratevalue > 0)
                {
                   AddStarsValue(cntrl, ratevalue);
                }

            }

        }


        private static object OnRatingCoerce(DependencyObject d, object baseValue)
        {
            var current = (decimal)baseValue;

            if (current < 0)
                current = 0;
         
            return current;
        }


        private static void OnRatingChanged(DependencyObject d,                                                                            DependencyPropertyChangedEventArgs e)
        {
            var strcntrl = d as StarRatingControl;
            var val = (decimal) e.NewValue;
            var oldvalue = (decimal)e.OldValue;

            if (strcntrl != null)
            {
                AddStarsValue(strcntrl, val);               
            }          
        }

        private static void AddStarsValue(StarRatingControl strcntrl, decimal val)
        {
           
            foreach (Star ctrl in strcntrl.StarratingPanel.Children)
            {               
                    if (val > 1)
                    {
                        DrawRating(strcntrl, ctrl, 1);
                        val -= 1;
                    }
                    else
                    {
                        DrawRating(strcntrl, ctrl, val);
                        val -= val;
                    }

      SetColors(ctrl, strcntrl.BackgroundColor, strcntrl.OutlineColor,strcntrl.ContentColor);
            }
        }


        private static void SetColors(Star str,SolidColorBrush background,
                      SolidColorBrush outline,SolidColorBrush content)
        {
            str.BackgroundColor = background;
            str.ContentColor = content;
            str.OutlineColor = outline;
        }


        private static void DrawRating(StarRatingControl strcntrl, Star str, 
              decimal starvalue)
        {                     
            str.StarValue = starvalue;           
        }

    }
}


Main xaml
**********


<Window x:Class="RatingControl.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:RatingControl"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="20"/>
            <ColumnDefinition />
            <ColumnDefinition Width="20"/>
        </Grid.ColumnDefinitions>
        <local:StarRatingControl x:Name="strcn" Grid.Row="0" Grid.Column="1" NoOfStar="5" Rating="2.4"
                                 BackgroundColor="White"
                                 StarOrdering="Horizontal"
                                 OutlineColor="OrangeRed" ContentColor="Red">

        </local:StarRatingControl>

        <Button Grid.Row="2" Grid.Column="1" Margin="183,0,233,52" Click="Button_Click">Change</Button>
    </Grid>
</Window>




using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace RatingControl
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            strcn.NoOfStar = 10;           
            strcn.Rating = 4.35m;
            strcn.BackgroundColor = Brushes.Gray;
            strcn.OutlineColor = Brushes.White;
            strcn.ContentColor = Brushes.White;
           
        }
    }
}



Output:

Now the code in main xaml have the ability to change the no of stars and color and ratings.

Design 





Runtime






From this article you can learn how to build  a star control with dynamic adding noofstars and rating , background color with orientation.





1 comment: