Click here to Skip to main content
15,884,099 members
Articles / Programming Languages / C#
Article

C# Star Rating Control

Rate me:
Please Sign up or sign in to vote.
4.32/5 (39 votes)
20 Dec 20045 min read 136.8K   6.9K   55   24
A C# star rating control.

C# Star Rating Control Screenshot

Introduction

This is a control similar to one used on the Netflix movie rental website, and one used by the iTunes and Microsoft Media Player to rate the songs in the song library.

Background

There is an implementation of such a control in C++, which can be found here: StarControl.

This article and code were influenced by a recently published MSDN magazine article by Duncan Mackenzie, which is available here: Creating a Five-Star Rating Control. His implementation is in Visual Basic .NET.

This control is implemented in C#.

The Code

  • Create a Class Library project.
  • Inherit from System.Windows.Forms.Control.
  • Add a project Reference to the System.Windows.Forms.dll.

This is not done automatically, and can be done by right clicking on "References" in the Solution Explorer and choosing the wanted DLL.

Constructor

C#
public StarRatingControl()
{
    SetStyle(ControlStyles.AllPaintingInWmPaint, true);
    SetStyle(ControlStyles.UserPaint, true);
    SetStyle(ControlStyles.DoubleBuffer, true);
    SetStyle(ControlStyles.ResizeRedraw, true);

    Width = 120;
    Height = 18;

    m_starAreas = new Rectangle[StarCount];
}
C#
>>> SetStyle(ControlStyles.AllPaintingInWmPaint, true);

This lowers the flickering during background painting by telling the control to ignore the WM_ERASEBKGND message. This only works however when the ControlStyles.UserPaint control style is set.

C#
>>> SetStyle(ControlStyle.UserPaint, true);

This indicates that the control is going to paint itself.

C#
>>> SetStyle(ControlStyles.DoubleBuffer, true);

This indicates that the control will use double buffering to draw itself. This will also reduce flicker. ControlStyle.UserPaint and ControlStyle.AllPaintInWmPaint should be set in conjunction with this bit.

C#
>>> SetStyle(ControlStyles.ResizeRedraw, true);

This indicates that the control is to redraw itself upon being resized.

LeftMargin, RightMargin, TopMargin, and BottomMargin are properties exposing the following private data members:

C#
private int m_leftMargin = 2;
private int m_rightMargin = 2;
private int m_topMargin = 2;
private int m_bottomMargin = 2;

The public properties are mostly of the form:

C#
public int LeftMargin
{
    get
    {
        return m_leftMargin;
    }
    set
    {
        if ( m_leftMargin != value )
        {
            m_leftMargin = value;
            Invalidate();
        }
    }
} 

Please notice that setting the property forces a redraw of the control if the value of the margin changes. This logic also holds for some of the other properties dealing with modifiable visual properties of the control, such as the property exposing the number of stars to draw.

C#
protected override void OnPaint(PaintEventArgs pe)
{    
    pe.Graphics.Clear(BackColor);

    int starWidth =(Width -(LeftMargin + RightMargin + 
                            (StarSpacing *(StarCount - 1))))/StarCount;
    int starHeight = (Height - (TopMargin + BottomMargin));

    Rectangle drawArea = 
            new Rectangle(LeftMargin, TopMargin, starWidth, starHeight);

    for ( int i = 0 ; i < StarCount; ++i )
    {
        m_starAreas[i].X = drawArea.X - StarSpacing / 2;
        m_starAreas[i].Y = drawArea.Y;
        m_starAreas[i].Width = drawArea.Width + StarSpacing / 2;
        m_starAreas[i].Height = drawArea.Height;

        DrawStar ( pe.Graphics, drawArea, i );
        
        drawArea.X += drawArea.Width + StarSpacing;
    }

    base.OnPaint ( pe );
}

All of the drawing is done in this method. For the implementation of this method, we will need to add a reference to System.Drawing.dll.

C#
>>> pe.Graphics.Clear(this.BackColor)

This erases the background.

C#
int starWidth =(Width -(LeftMargin + RightMargin + 
                      (m_starSpacing *(m_starCount -1))))/ m_starCount;
int starHeight = (Height - (TopMargin + BottomMargin));

This calculates the width and height of the stars given the control width and height and the inner margins.

Then we draw each of the stars.

C#
protected void DrawStar ( Graphics g, Rectangle rect, int starAreaIndex )
{    
    Brush fillBrush;
    Pen outlinePen = new Pen ( OutlineColor, OutlineThickness );
    
    if ( m_hovering  &&   m_hoverStar > starAreaIndex )
    {
        fillBrush = new LinearGradientBrush(rect,
               HoverColor, BackColor, LinearGradientMode.ForwardDiagonal); 
    }
    else if ( (!m_hovering) &&     m_selectedStar > starAreaIndex )
    {
        fillBrush = new LinearGradientBrush(rect,
               SelectedColor, BackColor, LinearGradientMode.ForwardDiagonal);
    }
    else
    {
        fillBrush = new SolidBrush ( BackColor );
    }
    
    PointF[] p = new PointF[10];
    p[0].X = rect.X + (rect.Width / 2);
    p[0].Y = rect.Y;
    p[1].X = rect.X + (42 * rect.Width / 64);
    p[1].Y = rect.Y + (19 * rect.Height / 64);
    p[2].X = rect.X + rect.Width;
    p[2].Y = rect.Y + (22 * rect.Height / 64);
    p[3].X = rect.X + (48 * rect.Width / 64);
    p[3].Y = rect.Y + (38 * rect.Height / 64);
    p[4].X = rect.X + (52 * rect.Width / 64);
    p[4].Y = rect.Y + rect.Height;
    p[5].X = rect.X + (rect.Width / 2);
    p[5].Y = rect.Y + (52 * rect.Height / 64);
    p[6].X = rect.X + (12 * rect.Width / 64);
    p[6].Y = rect.Y + rect.Height;
    p[7].X = rect.X + rect.Width / 4;
    p[7].Y = rect.Y + (38 * rect.Height / 64);
    p[8].X = rect.X;
    p[8].Y = rect.Y + (22 * rect.Height / 64);
    p[9].X = rect.X + (22 * rect.Width / 64);
    p[9].Y = rect.Y + (19 * rect.Height / 64);

    g.FillPolygon ( fillBrush, p );
    g.DrawPolygon ( outlinePen, p );
}

The areas in which the stars are to be drawn are contained in an array of System::Drawing::Rectangle objects. This array is of the same size as the number of stars and is resized whenever the count of stars changes through the StarCount property. Each of the stars is filled with a gradient brush. This is an arbitrary decision, but one which I hope would yield more pleasing results than a solid color would. The 10 points of each star are then calculated. I found it easiest to implement the star by dividing the drawing area into 64 logical partitions and placing the horizontal and vertical points as the offset into the 64 segment space. Finally, we fill the polygon specified by these ten points with the forward-diagonal, gradient brush, and outline the polygon with the outline pen. Lastly, we implement the event handlers dealing with various mouse functions. When the mouse enters the control, we set the hovering flag to true and force a repaint of the control:

C#
protected override void OnMouseEnter ( System.EventArgs ea )
{
    m_hovering = true;
    Invalidate();
    base.OnMouseEnter ( ea );
}

When the mouse leaves the control, we clear that flag and once again force a repaint of the control:

C#
protected override void OnMouseLeave ( System.EventArgs ea )
{
    m_hovering = false;
    Invalidate();
    base.OnMouseLeave ( ea );
}

Whenever the mouse moves, we check each of the stars in order to see over which, if any, the mouse is hovering. We record this information and force a repaint of the control.

C#
protected override void OnMouseMove ( MouseEventArgs args )
{
    for ( int i = 0 ; i < StarCount ; ++i )
    {
        if ( m_starAreas[i].Contains(args.X, args.Y) )
        {
            m_hoverStar = i + 1;
            Invalidate();
            break;
        }
    }

    base.OnMouseMove ( args );
}

The logic is similar whenever a user clicks on the control. We iterate over each of the stars to see which, if any, was clicked on, and record this information. We then force a repaint of the control.

Invalidate() eventually turns into a call to OnPaint which calls DrawStar(). DrawStar(), in turn, will check our recorded hover and click information to figure out how to render the control.

Testing and using the control

Any project using the control must first add a reference just like we added a reference to the System.Drawing.dll. Add "usingRatingControls" to the using declarations.

The sample application does little else than depend on the default properties provided by the control, which include 5 stars with a dark gray outline, a yellow hover color, and a royal blue selected color. The control also has a default width and height of 120 and 18 pixels respectively. I found that this ratio produced the most pleasing looking image. The height and width are, of course, modifiable, and your stars can thus look as tall or as wide as you please.

Really, the only mildly-interesting piece of code in the sample application is in the constructor. The following is the piece of code, in its entirety:

C#
public MainForm()
{
    InitializeComponent();

    m_starRatingControl.Top = 45;
    m_starRatingControl.Left = 85;

    Controls.Add ( m_starRatingControl );
}

Possible Improvements

The control could benefit from more publicly exposed properties such as fill brush style, so that forward-diagonal gradient is not the only implementation.

If speed optimization is a priority over size, one can rewrite the OnPaint() method so that it uses cached pre-calculated points to draw each of the stars instead of calculating the points on the fly. This would necessitate a few more data members to store the cached data, but would greatly improve the rendering speed of the control. This will probably be one of my own improvements of this control.

If you are really ambitious, you can follow the example of the MSDN magazine article and implement functionality to render a user-supplied image rather than a star. This would, of course, necessitate a name change, as it would no longer technically be a *star* rating control.

Please send me any comments/questions/suggestions. I will strive to implement any valid suggestions for improvements and answer all of your questions.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionWhat are those number ? Pin
Syl Njco23-Jun-19 19:54
Syl Njco23-Jun-19 19:54 
QuestionI Using This Method Pin
Memba Co8-Oct-18 4:20
Memba Co8-Oct-18 4:20 
QuestionHow to get the selected stars? Pin
Member 1025167827-Nov-17 3:47
Member 1025167827-Nov-17 3:47 
AnswerRe: How to get the selected stars? Pin
MightyMart30-Jan-18 9:20
MightyMart30-Jan-18 9:20 
QuestionStar Rating Control Pin
Member 1221828411-Jun-17 20:47
Member 1221828411-Jun-17 20:47 
Questionnumber of selected stars Pin
sally nabil badrah26-Oct-14 2:17
sally nabil badrah26-Oct-14 2:17 
Questionhow to set the selected star value Pin
hemayouness20-Nov-12 0:54
hemayouness20-Nov-12 0:54 
GeneralMy vote of 4 Pin
kiransolkar1-Nov-12 23:43
kiransolkar1-Nov-12 23:43 
QuestionUnable to Activate star from runtime - urgent Pin
srinivasvutukuru20-Oct-12 6:31
srinivasvutukuru20-Oct-12 6:31 
GeneralMy vote of 5 Pin
Rupesh Baikar28-Mar-12 4:59
Rupesh Baikar28-Mar-12 4:59 
GeneralSharePoint WebPart Pin
dubbele onzin2-Nov-07 11:02
dubbele onzin2-Nov-07 11:02 
GeneralHi , I am unable to download this demo Pin
Rashmi_developer27-Aug-07 23:39
Rashmi_developer27-Aug-07 23:39 
GeneralRe: Hi , I am unable to download this demo Pin
Andrey Butov28-Aug-07 5:20
Andrey Butov28-Aug-07 5:20 
QuestionHow to add rating on c# window application gridview Pin
kumar2729-May-07 20:24
kumar2729-May-07 20:24 
GeneralAllow the state of zero stars after selection Pin
philippe dykmans18-Jan-07 4:43
philippe dykmans18-Jan-07 4:43 
Hello,

I noticed that once you have a selection of stars, it is not possible to go back to the state of 0 stars. But that's easy to code:

Just replace this line in the OnClick method:

m_selectedStar = i + 1;

By this line:

m_selectedStar = (i == 0 && m_selectedStar == 1) ? 0 : i + 1;

Now, every time you click on the first star and leave the control, it will toggle between 1 and 0 star.

Regards!
Philippe

Philippe Dykmans
Software developpement
University of Antwerp

GeneralRe: Allow the state of zero stars after selection Pin
Pedro Mota24-Jul-13 1:07
Pedro Mota24-Jul-13 1:07 
GeneralSupport for transparency Pin
philippe dykmans18-Jan-07 4:26
philippe dykmans18-Jan-07 4:26 
Questionhelp me ? Pin
mehmoodmi5-Sep-06 20:53
mehmoodmi5-Sep-06 20:53 
GeneralDrawStar procedure Pin
bchojnowski29-Dec-04 2:12
bchojnowski29-Dec-04 2:12 
GeneralRe: DrawStar procedure Pin
Andrey Butov29-Dec-04 6:59
Andrey Butov29-Dec-04 6:59 
GeneralRe: DrawStar procedure Pin
Axel Rietschin6-Aug-05 16:50
professionalAxel Rietschin6-Aug-05 16:50 
GeneralTip Pin
leppie20-Dec-04 21:33
leppie20-Dec-04 21:33 
GeneralRe: Tip Pin
Andrey Butov21-Dec-04 3:40
Andrey Butov21-Dec-04 3:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.