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 } }