WPF:一个简单而灵活的评分控件






4.96/5 (43投票s)
WPF 的不错的小评分控件
引言
前几天我在工作中,我的一位同事问我如何创建一个评分控件(就是那种带星星的)。我给他讲了如何做,但在讲的过程中,我想到如果我有一个或两个小时的空闲时间,我可以自己尝试一下。我找到了一些时间去尝试,并创建了一个我认为非常灵活的 WPF RatingControl
。
您可以修改以下属性
- 整体背景颜色
- 星星前景色
- 星星轮廓颜色
- 星星数量
- 当前值
所有这些属性都是 DependencyProperty
值,因此它们完全支持绑定。所有这些都封装在一个非常简单易用的 UserControl
中,名为 RatingsControl
。下面是最终的 RatingsControl
在演示窗口中的样子。

我喜欢我的实现方式,与你通常看到的实现方式相比,我的实现方式能够处理星星的碎屑。我的意思是,你可以提供一个像 7.5 这样的值,其中 0.5 会真正只填充半颗星。
您只需要在 XAML 中这样使用它
<local:RatingsControl x:Name="ratings0"
Value="2.6"
NumberOfStars="4"
BackgroundColor="White"
StarForegroundColor="Blue"
StarOutlineColor="Black"
Margin="5"
HorizontalAlignment="Left"/>
看,这很简单,不是吗。
工作原理
那么,我将向您解释它是如何工作的。
实际上,有两个控件可以实现这一点。
RatingsControl
有一个顶级的 RatingsControl
,您可以在其中设置值。基于这些值,RatingsControl
会计算出请求了多少颗星(这由 NumberOfStars
DP 决定)。还有 DP 值协调,以确保 NumberOfStars
DP 值不会超过 RatingsControl.Minimum
和 RatingsControl.Maximum
DP 属性值,我已将它们分别设置为 0 和 10。
代码如下
/// <summary>
/// NumberOfStars Dependency Property
/// </summary>
public static readonly DependencyProperty NumberOfStarsProperty =
DependencyProperty.Register("NumberOfStars", typeof(Int32), typeof(RatingsControl),
new FrameworkPropertyMetadata((Int32)5,
new PropertyChangedCallback(OnNumberOfStarsChanged),
new CoerceValueCallback(CoerceNumberOfStarsValue)));
/// <summary>
/// Gets or sets the NumberOfStars property.
/// </summary>
public Int32 NumberOfStars
{
get { return (Int32)GetValue(NumberOfStarsProperty); }
set { SetValue(NumberOfStarsProperty, value); }
}
/// <summary>
/// Handles changes to the NumberOfStars property.
/// </summary>
private static void OnNumberOfStarsChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(MinimumProperty);
d.CoerceValue(MaximumProperty);
RatingsControl ratingsControl = (RatingsControl)d;
SetupStars(ratingsControl);
}
/// <summary>
/// Coerces the NumberOfStars value.
/// </summary>
private static object CoerceNumberOfStarsValue(DependencyObject d, object value)
{
RatingsControl ratingsControl = (RatingsControl)d;
Int32 current = (Int32)value;
if (current < ratingsControl.Minimum) current = ratingsControl.Minimum;
if (current > ratingsControl.Maximum) current = ratingsControl.Maximum;
return current;
}
当 RatingsControl.Value
或 RatingsControl.NumberOfStars
DP 值发生变化时,将运行以下逻辑,它会创建正确数量的 StarControl
,并根据 RatingsControl.Value
的总份额来设置它们的实际值。
/// <summary>
/// Sets up stars when Value or NumberOfStars properties change
/// Will only show up to the number of stars requested (up to Maximum)
/// so if Value > NumberOfStars * 1, then Value is clipped to maximum
/// number of full stars
/// </summary>
/// <param name="ratingsControl"></param>
private static void SetupStars(RatingsControl ratingsControl)
{
Decimal localValue = ratingsControl.Value;
ratingsControl.spStars.Children.Clear();
for (int i = 0; i < ratingsControl.NumberOfStars; i++)
{
StarControl star = new StarControl();
star.BackgroundColor = ratingsControl.BackgroundColor;
star.StarForegroundColor = ratingsControl.StarForegroundColor;
star.StarOutlineColor = ratingsControl.StarOutlineColor;
if (localValue > 1)
star.Value = 1.0m;
else if (localValue > 0)
{
star.Value = localValue;
}
else
{
star.Value = 0.0m;
}
localValue -= 1.0m;
ratingsControl.spStars.Children.Insert(i,star);
}
}
从这段代码可以看出,这是创建每个 StarControl
并为其分配值的地方。例如,如果总的 RatingsControl.Value
是 7.5,而 RatingsControl.NumberOfStars
是 8,我们将循环创建 8 个 StarControl
,其中前 7 个 StarControl
的 Value
DP 将设置为 1.0,除了最后一个,它的 Value
DP 将设置为 0.5。
如上所述,先前提到的大部分其他 DP,例如 BackgroundColor
/StarForegroundColor
/StarOutlineColor
DP,都用于设置创建的任何 StarControl
的相应 DP 值。
那么 StarControl
是如何工作的,它们又是如何渲染部分星星的呢?
StarControl
StarControl
代表 RatingsControl
中的单个星星,每个 StarControl
都有以下 DP
BackgroundColor
(通过RatingsControl
设置)StarForegroundColor
(通过RatingsControl
设置)StarOutlineColor
(通过RatingsControl
设置)Value
(通过RatingsControl
设置,但会在StarControl.Minimum
和StarControl.Maximum
DP 值之间进行协调,这两个值分别设置为 0.0 和 1.0)Minimum
用于协调StarControl.Value
,如果它超出可接受范围Maximum
用于协调StarControl.Value
,如果它超出可接受范围
描述 StarControl
外观的 XAML 如下
<UserControl x:Class="StarRatingsControl.StarControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="Auto" Width="Auto">
<Grid x:Name="gdStar">
<Path Name="starForeground" Fill="Gray" Stroke="Transparent" StrokeThickness="1"
Data="M 5,0 L 4,4 L 0,4 L 3,7 L 2,11 L 5,9 L 6,
9 L 9,11 L 8,7 L 11,4 L 7,4 L 6,0"/>
<Rectangle x:Name="mask" Margin="0"/>
<Path Name="starOutline" Fill="Transparent"
Stroke="Transparent" StrokeThickness="1"
Data="M 5,0 L 4,4 L 0,4 L 3,7 L 2,11 L 5,9 L 6,
9 L 9,11 L 8,7 L 11,4 L 7,4 L 6,0"/>
</Grid>
</UserControl>
再次回到 StarControl
如何渲染部分星星的问题,这里使用了两个技巧
技巧 1:裁剪
在 StarControl
的构造函数中,设置了一个 Clip 几何图形,它阻止 StarControl
渲染任何出现在此裁剪几何图形外部的内容。
public StarControl()
{
this.DataContext = this;
InitializeComponent();
gdStar.Width = STAR_SIZE;
gdStar.Height = STAR_SIZE;
gdStar.Clip = new RectangleGeometry
{
Rect = new Rect(0, 0, STAR_SIZE, STAR_SIZE)
};
mask.Width = STAR_SIZE;
mask.Height = STAR_SIZE;
}
技巧 2:移动遮罩
实际上有一个矩形(我称之为 Mask),它位于星星背景路径和星星轮廓路径之间。这个矩形(Mask)的颜色与背景色相同,并且它的 Margin 被调整到正确的位置,以产生部分填充的星星的错觉。然后,轮廓绘制在这个移动的矩形之上。
这张图更好地解释了这一点

这是 StarControl
中处理此问题的代码:
/// <summary>
/// Handles changes to the Value property.
/// </summary>
private static void OnValueChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(MinimumProperty);
d.CoerceValue(MaximumProperty);
StarControl starControl = (StarControl)d;
if (starControl.Value == 0.0m)
{
starControl.starForeground.Fill = Brushes.Gray;
}
else
{
starControl.starForeground.Fill = starControl.StarForegroundColor;
}
Int32 marginLeftOffset = (Int32)(starControl.Value * (Decimal)STAR_SIZE);
starControl.mask.Margin = new Thickness(marginLeftOffset, 0, 0, 0);
starControl.InvalidateArrange();
starControl.InvalidateMeasure();
starControl.InvalidateVisual();
}
就是这样……希望您喜欢
总之,希望您喜欢。我知道这是一篇非常小的文章,但我希望它能对某人有所帮助。
谢谢
一如既往,欢迎投票/评论。
历史
- 2009年11月27日:初次发布