using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
namespace Developpez.Dotnet.Windows.Controls
{
///
/// Représente un contrôle permettant d'affecter une note à un contenu
///
[TemplatePart(Name = "PART_RatingHost", Type = typeof(FrameworkElement))]
public class RatingControl : Control
{
#region Private data
private FrameworkElement _ratingHost;
#endregion
#region Constructors
static RatingControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(RatingControl), new FrameworkPropertyMetadata(typeof(RatingControl)));
}
#endregion
#region Dependency properties
///
/// Identifiant de la propriété ImageOn
///
public static readonly DependencyProperty ImageOnProperty =
DependencyProperty.Register("ImageOn", typeof(ImageSource), typeof(RatingControl), new UIPropertyMetadata(null));
///
/// Identifiant de la propriété ImageOff
///
public static readonly DependencyProperty ImageOffProperty =
DependencyProperty.Register("ImageOff", typeof(ImageSource), typeof(RatingControl), new UIPropertyMetadata(null));
///
/// Identifiant de la propriété Stretch
///
public static readonly DependencyProperty StretchProperty =
DependencyProperty.Register("Stretch", typeof(Stretch), typeof(RatingControl), new UIPropertyMetadata(Stretch.Uniform));
///
/// Identifiant de la propriété Value
///
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value",
typeof(double),
typeof(RatingControl),
new FrameworkPropertyMetadata(
0.0,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnValueChanged,
CoerceRatingValue));
///
/// Identifiant de la propriété RatingMode
///
public static readonly DependencyProperty RatingModeProperty =
DependencyProperty.Register(
"RatingMode",
typeof(RatingMode),
typeof(RatingControl),
new UIPropertyMetadata(
RatingMode.Integer,
OnRatingModeChanged));
///
/// Identifiant de la propriété IsReadOnly
///
public static readonly DependencyProperty IsReadOnlyProperty =
DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(RatingControl), new UIPropertyMetadata(false));
private static readonly DependencyPropertyKey HoverValuePropertyKey =
DependencyProperty.RegisterReadOnly("HoverValue", typeof(double), typeof(RatingControl), new UIPropertyMetadata(0.0));
#endregion
#region Routed events
///
/// Identifiant de l'évènement ValueChanged
///
public static readonly RoutedEvent ValueChangedEvent =
EventManager.RegisterRoutedEvent(
"ValueChanged",
RoutingStrategy.Bubble,
typeof(RoutedPropertyChangedEventHandler),
typeof(RatingControl));
#endregion
#region Public properties
///
/// Obtient ou définit l'image utilisée pour afficher les points "allumés"
///
public ImageSource ImageOn
{
get { return (ImageSource)GetValue(ImageOnProperty); }
set { SetValue(ImageOnProperty, value); }
}
///
/// Obtient ou définit l'image utilisée pour afficher les points "éteints"
///
public ImageSource ImageOff
{
get { return (ImageSource)GetValue(ImageOffProperty); }
set { SetValue(ImageOffProperty, value); }
}
///
/// Obtient ou définit une valeur indiquant comment le contrôle doit être étiré
/// pour remplir le rectangle de destination.
///
/// Une des valeurs de Stretch. La valeur par défaut est Uniform.
public Stretch Stretch
{
get { return (Stretch)GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); }
}
///
/// Obtient ou définit la valeur courante du contrôle
///
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
///
/// Obtient ou définit l'aperçu de la valeur quand la souris survole le contrôle
///
/// Cette propriété est utile pour définir un template personnalisé pour le contrôle
public double HoverValue
{
get { return (double)GetValue(HoverValuePropertyKey.DependencyProperty); }
}
///
/// Obtient ou définit le mode de notation (entier, demi-point ou décimal)
///
public RatingMode RatingMode
{
get { return (RatingMode)GetValue(RatingModeProperty); }
set { SetValue(RatingModeProperty, value); }
}
///
/// Obtient ou définit une valeur indiquant si le contrôle est en lecture seule.
///
/// true si le contrôle est en lecture seule, false sinon. La valeur par défaut est false.
public bool IsReadOnly
{
get { return (bool)GetValue(IsReadOnlyProperty); }
set { SetValue(IsReadOnlyProperty, value); }
}
#endregion
#region Public events
///
/// Se produit quand la valeur change
///
public event RoutedPropertyChangedEventHandler ValueChanged
{
add { AddHandler(ValueChangedEvent, value); }
remove { RemoveHandler(ValueChangedEvent, value); }
}
#endregion
#region Private methods
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RatingControl ctl = d as RatingControl;
if (ctl == null)
return;
double oldValue = (double)e.OldValue;
double newValue = ctl.Value;
if (oldValue != newValue)
{
var args = new RoutedPropertyChangedEventArgs(oldValue, newValue, ValueChangedEvent);
ctl.OnValueChanged(args);
}
}
private static object CoerceRatingValue(DependencyObject d, object basevalue)
{
RatingControl ctl = d as RatingControl;
if (ctl == null)
return basevalue;
double value = (double)basevalue;
return ctl.CoerceRatingValue(value);
}
private static void OnRatingModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RatingControl ctl = d as RatingControl;
if (ctl == null)
return;
ctl.CoerceValue(ValueProperty);
}
private double CoerceRatingValue(double value)
{
switch (RatingMode)
{
case RatingMode.Integer:
value = Math.Ceiling(value);
break;
case RatingMode.HalfPoint:
value = Math.Round(value * 2) / 2;
break;
}
return value;
}
private double GetValue(MouseEventArgs e)
{
double pos = e.GetPosition(_ratingHost).X;
double totalWidth = _ratingHost.ActualWidth;
if (pos < 0)
pos = 0;
if (pos > totalWidth)
pos = totalWidth;
double value = 5 * pos / totalWidth;
return value;
}
void _ratingHost_MouseDown(object sender, MouseButtonEventArgs e)
{
if (!IsReadOnly)
{
SetValue(ValueProperty, CoerceRatingValue(GetValue(e)));
}
}
void _ratingHost_MouseMove(object sender, MouseEventArgs e)
{
if (!IsReadOnly)
{
SetValue(HoverValuePropertyKey, CoerceRatingValue(GetValue(e)));
}
}
private void SubscribeEvents(FrameworkElement ratingHost)
{
ratingHost.MouseDown += _ratingHost_MouseDown;
ratingHost.MouseMove += _ratingHost_MouseMove;
}
private void UnsubscribeEvents(FrameworkElement ratingHost)
{
ratingHost.MouseDown -= _ratingHost_MouseDown;
ratingHost.MouseMove -= _ratingHost_MouseMove;
}
#endregion
#region Virtual methods and overrides
///
/// Génère l'arbre visuel pour le contrôle lorsqu'un nouveau modèle est appliqué.
///
public override void OnApplyTemplate()
{
if (_ratingHost != null)
{
UnsubscribeEvents(_ratingHost);
}
base.OnApplyTemplate();
_ratingHost = Template.FindName("PART_RatingHost", this) as FrameworkElement;
if (_ratingHost != null)
{
SubscribeEvents(_ratingHost);
}
}
///
/// Déclenche l'évènement ValueChanged
///
/// Paramètres de l'évènement
protected virtual void OnValueChanged(RoutedPropertyChangedEventArgs e)
{
this.RaiseEvent(e);
}
#endregion
#region RatingWidthConverter
///
/// Convertisseur utilisé pour calculer la largeur de la zone "allumée"
///
public static readonly IMultiValueConverter WidthConverter = new RatingWidthConverter();
private class RatingWidthConverter : IMultiValueConverter
{
public RatingWidthConverter()
{
_maxValue = 5;
}
private readonly double _maxValue;
#region Implementation of IMultiValueConverter
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values != null && values.Length >= 2
&& values[0] is double && values[1] is double)
{
double rating = (double) values[0];
double totalActualWidth = (double)values[1];
return rating * totalActualWidth / _maxValue;
}
return Binding.DoNothing;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
#endregion
}
///
/// Indique le mode de notation utilisée
///
public enum RatingMode
{
///
/// La note est arrondie à l'entier supérieur
///
Integer,
///
/// La note est arrondie au demi-point le plus proche
///
HalfPoint,
///
/// La note n'est pas arrondie
///
Decimal
}
}