优化更新代码,添加界面功能并整合
This commit is contained in:
BIN
WPFluent/Controls/RatingControl/RatingControl.bmp
Normal file
BIN
WPFluent/Controls/RatingControl/RatingControl.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 824 B |
102
WPFluent/Controls/RatingControl/RatingControl.xaml
Normal file
102
WPFluent/Controls/RatingControl/RatingControl.xaml
Normal file
@@ -0,0 +1,102 @@
|
||||
<!--
|
||||
This Source Code Form is subject to the terms of the MIT License.
|
||||
If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
|
||||
Copyright (C) Leszek Pomianowski and WPF UI Contributors.
|
||||
All Rights Reserved.
|
||||
-->
|
||||
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:WPFluent.Controls">
|
||||
|
||||
<Style TargetType="{x:Type controls:RatingControl}">
|
||||
<!-- Universal WPF UI focus -->
|
||||
<Setter Property="FocusVisualStyle" Value="{DynamicResource DefaultControlFocusVisualStyle}" />
|
||||
<!-- Universal WPF UI focus -->
|
||||
<Setter Property="Foreground" Value="{DynamicResource RatingControlSelectedForeground}" />
|
||||
|
||||
<Setter Property="FontSize" Value="20" />
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type controls:RatingControl}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid
|
||||
x:Name="StarsContainer"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<controls:SymbolIcon
|
||||
x:Name="PART_Star1"
|
||||
Grid.Column="0"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
Symbol="Star28" />
|
||||
<controls:SymbolIcon
|
||||
x:Name="PART_Star2"
|
||||
Grid.Column="1"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
Symbol="Star28" />
|
||||
<controls:SymbolIcon
|
||||
x:Name="PART_Star3"
|
||||
Grid.Column="2"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
Symbol="Star28" />
|
||||
<controls:SymbolIcon
|
||||
x:Name="PART_Star4"
|
||||
Grid.Column="3"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
Symbol="Star28" />
|
||||
<controls:SymbolIcon
|
||||
x:Name="PART_Star5"
|
||||
Grid.Column="4"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
Symbol="Star28" />
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
x:Name="ContentGrid"
|
||||
Grid.Column="1"
|
||||
Margin="8,0,0,0"
|
||||
VerticalAlignment="Center">
|
||||
<ContentPresenter TextElement.FontSize="13" TextElement.Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="Content" Value="{x:Null}">
|
||||
<Setter TargetName="ContentGrid" Property="Margin" Value="0" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="StarsContainer" Property="Opacity" Value="0.5" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
396
WPFluent/Controls/RatingControl/RatingControl.xaml.cs
Normal file
396
WPFluent/Controls/RatingControl/RatingControl.xaml.cs
Normal file
@@ -0,0 +1,396 @@
|
||||
|
||||
|
||||
|
||||
using System.Windows.Input;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace WPFluent.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Displays the rating scale with interactions.
|
||||
/// </summary>
|
||||
[TemplatePart(Name = "PART_Star1", Type = typeof(SymbolIcon))]
|
||||
[TemplatePart(Name = "PART_Star2", Type = typeof(SymbolIcon))]
|
||||
[TemplatePart(Name = "PART_Star3", Type = typeof(SymbolIcon))]
|
||||
[TemplatePart(Name = "PART_Star4", Type = typeof(SymbolIcon))]
|
||||
[TemplatePart(Name = "PART_Star5", Type = typeof(SymbolIcon))]
|
||||
public class RatingControl : System.Windows.Controls.ContentControl
|
||||
{
|
||||
private const double MaxValue = 5.0D;
|
||||
private const double MinValue = 0.0D;
|
||||
private const int OffsetTolerance = 8;
|
||||
|
||||
private static readonly SymbolRegular StarHalfSymbol = SymbolRegular.StarHalf28;
|
||||
private static readonly SymbolRegular StarSymbol = SymbolRegular.Star28;
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="HalfStarEnabled"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty HalfStarEnabledProperty = DependencyProperty.Register(
|
||||
nameof(HalfStarEnabled),
|
||||
typeof(bool),
|
||||
typeof(RatingControl),
|
||||
new PropertyMetadata(true));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="MaxRating"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty MaxRatingProperty = DependencyProperty.Register(
|
||||
nameof(MaxRating),
|
||||
typeof(int),
|
||||
typeof(RatingControl),
|
||||
new PropertyMetadata(5));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="ValueChanged"/> routed event.
|
||||
/// </summary>
|
||||
public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
|
||||
nameof(ValueChanged),
|
||||
RoutingStrategy.Bubble,
|
||||
typeof(RoutedEventHandler),
|
||||
typeof(RatingControl));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="Value"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
|
||||
nameof(Value),
|
||||
typeof(double),
|
||||
typeof(RatingControl),
|
||||
new PropertyMetadata(0.0D, OnValueChanged));
|
||||
private SymbolIcon? _symbolIconStarFive;
|
||||
private SymbolIcon? _symbolIconStarFour;
|
||||
private SymbolIcon? _symbolIconStarOne;
|
||||
private SymbolIcon? _symbolIconStarThree;
|
||||
private SymbolIcon? _symbolIconStarTwo;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after the user selects the rating.
|
||||
/// </summary>
|
||||
public event RoutedEventHandler ValueChanged
|
||||
{
|
||||
add => AddHandler(ValueChangedEvent, value);
|
||||
remove => RemoveHandler(ValueChangedEvent, value);
|
||||
}
|
||||
|
||||
private int ExtractValueFromOffset(double offset)
|
||||
{
|
||||
var starValue = (int)(offset + OffsetTolerance) / 10;
|
||||
|
||||
if(!HalfStarEnabled)
|
||||
{
|
||||
if(starValue < 2)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(starValue % 2 != 0)
|
||||
{
|
||||
starValue += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return starValue;
|
||||
}
|
||||
|
||||
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if(d is not RatingControl ratingControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ratingControl.OnValueChanged((double)e.OldValue);
|
||||
}
|
||||
|
||||
private void SetStarsPresence(int index)
|
||||
{
|
||||
switch(index)
|
||||
{
|
||||
case 10:
|
||||
UpdateStar(4, StarValue.Filled);
|
||||
UpdateStar(3, StarValue.Filled);
|
||||
UpdateStar(2, StarValue.Filled);
|
||||
UpdateStar(1, StarValue.Filled);
|
||||
UpdateStar(0, StarValue.Filled);
|
||||
break;
|
||||
|
||||
case 9:
|
||||
UpdateStar(4, StarValue.HalfFilled);
|
||||
UpdateStar(3, StarValue.Filled);
|
||||
UpdateStar(2, StarValue.Filled);
|
||||
UpdateStar(1, StarValue.Filled);
|
||||
UpdateStar(0, StarValue.Filled);
|
||||
break;
|
||||
|
||||
case 8:
|
||||
UpdateStar(4, StarValue.Empty);
|
||||
UpdateStar(3, StarValue.Filled);
|
||||
UpdateStar(2, StarValue.Filled);
|
||||
UpdateStar(1, StarValue.Filled);
|
||||
UpdateStar(0, StarValue.Filled);
|
||||
break;
|
||||
|
||||
case 7:
|
||||
UpdateStar(4, StarValue.Empty);
|
||||
UpdateStar(3, StarValue.HalfFilled);
|
||||
UpdateStar(2, StarValue.Filled);
|
||||
UpdateStar(1, StarValue.Filled);
|
||||
UpdateStar(0, StarValue.Filled);
|
||||
break;
|
||||
|
||||
case 6:
|
||||
UpdateStar(4, StarValue.Empty);
|
||||
UpdateStar(3, StarValue.Empty);
|
||||
UpdateStar(2, StarValue.Filled);
|
||||
UpdateStar(1, StarValue.Filled);
|
||||
UpdateStar(0, StarValue.Filled);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
UpdateStar(4, StarValue.Empty);
|
||||
UpdateStar(3, StarValue.Empty);
|
||||
UpdateStar(2, StarValue.HalfFilled);
|
||||
UpdateStar(1, StarValue.Filled);
|
||||
UpdateStar(0, StarValue.Filled);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
UpdateStar(4, StarValue.Empty);
|
||||
UpdateStar(3, StarValue.Empty);
|
||||
UpdateStar(2, StarValue.Empty);
|
||||
UpdateStar(1, StarValue.Filled);
|
||||
UpdateStar(0, StarValue.Filled);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
UpdateStar(4, StarValue.Empty);
|
||||
UpdateStar(3, StarValue.Empty);
|
||||
UpdateStar(2, StarValue.Empty);
|
||||
UpdateStar(1, StarValue.HalfFilled);
|
||||
UpdateStar(0, StarValue.Filled);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
UpdateStar(4, StarValue.Empty);
|
||||
UpdateStar(3, StarValue.Empty);
|
||||
UpdateStar(2, StarValue.Empty);
|
||||
UpdateStar(1, StarValue.Empty);
|
||||
UpdateStar(0, StarValue.Filled);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
UpdateStar(4, StarValue.Empty);
|
||||
UpdateStar(3, StarValue.Empty);
|
||||
UpdateStar(2, StarValue.Empty);
|
||||
UpdateStar(1, StarValue.Empty);
|
||||
UpdateStar(0, StarValue.HalfFilled);
|
||||
break;
|
||||
|
||||
default:
|
||||
UpdateStar(4, StarValue.Empty);
|
||||
UpdateStar(3, StarValue.Empty);
|
||||
UpdateStar(2, StarValue.Empty);
|
||||
UpdateStar(1, StarValue.Empty);
|
||||
UpdateStar(0, StarValue.Empty);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStar(int starIndex, StarValue starValue)
|
||||
{
|
||||
var selectedIcon = starIndex switch
|
||||
{
|
||||
1 => _symbolIconStarTwo,
|
||||
2 => _symbolIconStarThree,
|
||||
3 => _symbolIconStarFour,
|
||||
4 => _symbolIconStarFive,
|
||||
_ => _symbolIconStarOne,
|
||||
};
|
||||
|
||||
if(selectedIcon is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch(starValue)
|
||||
{
|
||||
case StarValue.HalfFilled:
|
||||
selectedIcon.Filled = false;
|
||||
selectedIcon.Symbol = StarHalfSymbol;
|
||||
break;
|
||||
|
||||
case StarValue.Filled:
|
||||
selectedIcon.Filled = true;
|
||||
selectedIcon.Symbol = StarSymbol;
|
||||
break;
|
||||
|
||||
default:
|
||||
selectedIcon.Filled = false;
|
||||
selectedIcon.Symbol = StarSymbol;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStarsFromValue() { SetStarsPresence(ExtractValueFromOffset(Value * 100 / 5)); }
|
||||
|
||||
private void UpdateStarsOnMouseClick(double offsetPercentage)
|
||||
{
|
||||
var currentValue = ExtractValueFromOffset(offsetPercentage);
|
||||
|
||||
SetCurrentValue(ValueProperty, currentValue / 2.0);
|
||||
}
|
||||
|
||||
private void UpdateStarsOnMousePreview(double offsetPercentage)
|
||||
{ SetStarsPresence(ExtractValueFromOffset(offsetPercentage)); }
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts the control's <see cref="Value"/> in response to keyboard input, incrementing or decrementing based on
|
||||
/// the key pressed.
|
||||
/// </summary>
|
||||
/// <param name="e">Key event arguments containing details about the key press.</param>
|
||||
protected override void OnKeyUp(KeyEventArgs e)
|
||||
{
|
||||
base.OnKeyUp(e);
|
||||
|
||||
if((e.Key == Key.Right || e.Key == Key.Up) && Value < MaxValue)
|
||||
{
|
||||
Value += HalfStarEnabled ? 0.5D : 1;
|
||||
}
|
||||
|
||||
if((e.Key == Key.Left || e.Key == Key.Down) && Value > MinValue)
|
||||
{
|
||||
Value -= HalfStarEnabled ? 0.5D : 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is called when mouse is cliked down.
|
||||
/// </summary>
|
||||
protected override void OnMouseDown(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseDown(e);
|
||||
|
||||
var currentPossition = e.GetPosition(this);
|
||||
var mouseOffset = currentPossition.X * 100 / ActualWidth;
|
||||
|
||||
if(e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
UpdateStarsOnMouseClick(mouseOffset);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is called when mouse is moved away from the control.
|
||||
/// </summary>
|
||||
protected override void OnMouseLeave(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseLeave(e);
|
||||
|
||||
UpdateStarsFromValue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is called when mouse is moved around the control.
|
||||
/// </summary>
|
||||
protected override void OnMouseMove(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseMove(e);
|
||||
|
||||
var currentPossition = e.GetPosition(this);
|
||||
var mouseOffset = currentPossition.X * 100 / ActualWidth;
|
||||
|
||||
if(e.LeftButton != MouseButtonState.Pressed)
|
||||
{
|
||||
UpdateStarsOnMousePreview(mouseOffset);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is called when <see cref="Value"/> changes.
|
||||
/// </summary>
|
||||
protected virtual void OnValueChanged(double oldValue)
|
||||
{
|
||||
if(Value > MaxValue)
|
||||
{
|
||||
SetCurrentValue(ValueProperty, MaxValue);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(Value < MinValue)
|
||||
{
|
||||
SetCurrentValue(ValueProperty, MinValue);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(!Value.Equals(oldValue))
|
||||
{
|
||||
RaiseEvent(new RoutedEventArgs(ValueChangedEvent));
|
||||
}
|
||||
|
||||
UpdateStarsFromValue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is called when Template is changed.
|
||||
/// </summary>
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
if(GetTemplateChild("PART_Star1") is SymbolIcon starOne)
|
||||
{
|
||||
_symbolIconStarOne = starOne;
|
||||
}
|
||||
|
||||
if(GetTemplateChild("PART_Star2") is SymbolIcon starTwo)
|
||||
{
|
||||
_symbolIconStarTwo = starTwo;
|
||||
}
|
||||
|
||||
if(GetTemplateChild("PART_Star3") is SymbolIcon starThree)
|
||||
{
|
||||
_symbolIconStarThree = starThree;
|
||||
}
|
||||
|
||||
if(GetTemplateChild("PART_Star4") is SymbolIcon starFour)
|
||||
{
|
||||
_symbolIconStarFour = starFour;
|
||||
}
|
||||
|
||||
if(GetTemplateChild("PART_Star5") is SymbolIcon starFive)
|
||||
{
|
||||
_symbolIconStarFive = starFive;
|
||||
}
|
||||
|
||||
UpdateStarsFromValue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether half of the star can be selected.
|
||||
/// </summary>
|
||||
public bool HalfStarEnabled
|
||||
{
|
||||
get => (bool)GetValue(HalfStarEnabledProperty);
|
||||
set => SetValue(HalfStarEnabledProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum allowed rating value.
|
||||
/// </summary>
|
||||
public int MaxRating { get => (int)GetValue(MaxRatingProperty); set => SetValue(MaxRatingProperty, value); }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the rating value.
|
||||
/// </summary>
|
||||
public double Value { get => (double)GetValue(ValueProperty); set => SetValue(ValueProperty, value); }
|
||||
|
||||
private enum StarValue
|
||||
{
|
||||
Empty,
|
||||
HalfFilled,
|
||||
Filled,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user