65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 星级评分控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.32/5 (37投票s)

2004 年 12 月 21 日

5分钟阅读

viewsIcon

140976

downloadIcon

6968

一个 C# 星级评分控件。

C# Star Rating Control Screenshot

引言

这是一个类似于 Netflix 电影租赁网站上使用的控件,以及 iTunes 和 Microsoft Media Player 用来评价歌曲库中歌曲的控件。

背景

这里有一个 C++ 版本的实现,可以在这里找到:StarControl

本文和代码受到了 Duncan Mackenzie 最近发表在 MSDN 杂志上的一篇文章的启发,该文章可在此处找到:创建一个五星级评分控件。他的实现是用 Visual Basic .NET 编写的。

此控件是用 C# 实现的。

代码

  • 创建一个类库项目。
  • 继承自 System.Windows.Forms.Control
  • 添加对 System.Windows.Forms.dll 的项目引用。

这不是自动完成的,可以通过右键单击“解决方案资源管理器”中的“引用”并选择所需的 DLL 来完成。

构造函数

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];
}
 >>> SetStyle(ControlStyles.AllPaintingInWmPaint, true);

这可以通过告诉控件忽略 WM_ERASEBKGND 消息来减少背景绘制时的闪烁。但这仅在设置了 ControlStyles.UserPaint 控件样式时才有效。

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

这表示控件将自行绘制。

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

这表示控件将使用双缓冲来绘制自身。这也会减少闪烁。ControlStyle.UserPaintControlStyle.AllPaintInWmPaint 应与此位结合使用。

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

这表示控件在调整大小时会重绘自身。

LeftMarginRightMarginTopMarginBottomMargin 是公开以下私有数据成员的属性

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

公共属性大多采用以下形式

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

请注意,设置属性会强制重绘控件,如果边距的值发生更改。此逻辑也适用于其他处理控件可修改视觉属性的属性,例如公开星数以供绘制的属性。

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 );
}

所有绘图都在此方法中完成。要实现此方法,我们需要添加对 System.Drawing.dll 的引用。

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

这会清除背景。

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

这会根据控件的宽度和高度以及内部边距计算星形的宽度和高度。

然后我们绘制每个星形。

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 );
}

用于绘制星形的区域包含在 System::Drawing::Rectangle 对象的数组中。此数组的大小与星形数量相同,并且在通过 StarCount 属性更改星形数量时会被调整大小。每个星形都填充有渐变画笔。这是一个任意决定,但我希望它能产生比纯色更令人愉悦的效果。然后计算每个星形的 10 个顶点。我发现实现星形的最简单方法是将绘图区域划分为 64 个逻辑分区,并将水平和垂直点放置在 64 个分段空间的偏移量处。最后,我们用前斜线渐变画笔填充由这十个点指定的 polygon,并用轮廓笔勾勒出 polygon。最后,我们实现处理各种鼠标功能的事件处理程序。当鼠标进入控件时,我们将悬停标志设置为 true 并强制重绘控件。

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

当鼠标离开控件时,我们清除该标志,并再次强制重绘控件。

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

每当鼠标移动时,我们都会依次检查每个星形,以查看鼠标悬停在哪个星形上(如果有)。我们记录此信息并强制重绘控件。

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 );
}

当用户单击控件时,逻辑也是相似的。我们遍历每个星形,以查看单击了哪个星形(如果有),并记录此信息。然后我们强制重绘控件。

Invalidate() 最终会调用 OnPaint,而 OnPaint 又会调用 DrawStar()DrawStar() 反过来会检查我们记录的悬停和单击信息,以确定如何渲染控件。

测试和使用控件

任何使用该控件的项目都必须先添加引用,就像我们添加对 System.Drawing.dll 的引用一样。在 using 声明中添加 "usingRatingControls"。

示例应用程序除了依赖控件提供的默认属性外,几乎没有做什么,这些属性包括 5 颗星,带有深灰色轮廓,黄色悬停颜色和皇家蓝色选中颜色。控件的默认宽度和高度分别为 120 和 18 像素。我发现这个比例产生了最令人愉悦的图像。当然,高度和宽度是可修改的,因此您的星形可以像您喜欢的那样高或宽。

实际上,示例应用程序中唯一稍微有趣的代码位于构造函数中。以下是该代码段的全部内容

public MainForm()
{
    InitializeComponent();

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

    Controls.Add ( m_starRatingControl );
}

可能的改进

控件可以从更多公开的属性中受益,例如填充画笔样式,以便前斜线渐变不是唯一的实现。

如果速度优化比大小更重要,可以重写 OnPaint() 方法,使其使用缓存的预计算点来绘制每个星形,而不是即时计算点。这将需要一些额外的数据成员来存储缓存数据,但将大大提高控件的渲染速度。这可能会是我自己对该控件的改进之一。

如果您非常有野心,可以效仿 MSDN 杂志文章的例子,实现功能以渲染用户提供的图像而不是星形。当然,这需要更改名称,因为它在技术上不再是 *星形* 评分控件。

发邮件给我任何评论/问题/建议。我将努力实施任何有效的改进建议,并回答您的所有问题。

© . All rights reserved.