C# 星级评分控件






4.32/5 (37投票s)
2004 年 12 月 21 日
5分钟阅读

140976

6968
一个 C# 星级评分控件。
引言
这是一个类似于 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.UserPaint
和 ControlStyle.AllPaintInWmPaint
应与此位结合使用。
>>> SetStyle(ControlStyles.ResizeRedraw, true);
这表示控件在调整大小时会重绘自身。
LeftMargin
、RightMargin
、TopMargin
和 BottomMargin
是公开以下私有数据成员的属性
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 杂志文章的例子,实现功能以渲染用户提供的图像而不是星形。当然,这需要更改名称,因为它在技术上不再是 *星形* 评分控件。
请发邮件给我任何评论/问题/建议。我将努力实施任何有效的改进建议,并回答您的所有问题。