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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (43投票s)

2009年11月27日

CPOL

4分钟阅读

viewsIcon

109292

downloadIcon

3281

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.MinimumRatingsControl.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.ValueRatingsControl.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 个 StarControlValue 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.MinimumStarControl.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日:初次发布
© . All rights reserved.