# Thursday, June 03, 2010

Désactiver le bouton d’une ScrollBar quand elle est au Minimum ou au Maximum

Le contrôle ScrollBar de Silverlight ne désactive pas les boutons des ScrollBars lorsque le curseur arrive à une extrémité.

image

Ce design n’est en général pas satisfaisant dès l’instant ou on veut faire une application un peu jolie et ergonomique.

image

Pour activer ce comportement sur une ScrollBar, il faut éditer son template et utiliser la classe ScrollBarExtensions. Dans le Template nous commençons par activer la classe d’extensions sur la ScrollBar. Sur n’importe quel élément du template il suffit de déclarer l’AttachedProperty suivante:

                        <Grid x:Name="Root"

                             slec:ScrollBarExtensions.RegisterScrollbar="{Binding RelativeSource={RelativeSource TemplatedParent}}">

 

Une fois initialisée, la classe d’extensions calcule si la valeur est différente du Minimum ou Maximum et pose deux AttachedProperty sur la ScrollBar : CanIncrease et CanDecrease. Il ne reste plus en suite qu’à Binder les propriété IsEnabled des boutons à ces valeurs.

IsEnabled="{Binding (slec:ScrollBarExtensions.CanDecrease), RelativeSource={RelativeSource TemplatedParent}}"

La classe ScrollBarExtensions:

    /// <summary>

    /// Provide behavior extensions to a ScrollBar control. When registered add CanIncrease and CanDecrease attached property

    /// </summary>

    public static class ScrollBarExtensions

    {

        /// <summary>

        /// Get the epsillon value arround the Maximum or Minimum value that activate or deactive the CanIncrease and CanDecrease property

        /// </summary>

        /// <param name="obj"></param>

        /// <returns></returns>

        public static double GetEpsillon(DependencyObject obj)

        {

            return (double)obj.GetValue(EpsillonProperty);

        }

 

        /// <summary>

        /// Set the epsillon value arround the Maximum or Minimum value that activate or deactive the CanIncrease and CanDecrease property

        /// </summary>

        /// <param name="obj"></param>

        /// <param name="value"></param>

        public static void SetEpsillon(DependencyObject obj, double value)

        {

            obj.SetValue(EpsillonProperty, value);

        }

 

        // Using a DependencyProperty as the backing store for Epsillon.  This enables animation, styling, binding, etc...

        public static readonly DependencyProperty EpsillonProperty =

            DependencyProperty.RegisterAttached("Epsillon", typeof(double), typeof(ScrollBarExtensions), new PropertyMetadata(0.001d));

 

        /// <summary>

        /// Get the registered ScrollBar

        /// </summary>

        /// <param name="obj"></param>

        /// <returns></returns>

        public static ScrollBar GetRegisterScrollbar(DependencyObject obj)

        {

            return (ScrollBar)obj.GetValue(RegisterScrollbarProperty);

        }

 

        /// <summary>

        /// Register a ScrollBar

        /// </summary>

        /// <param name="obj"></param>

        /// <param name="value"></param>

        public static void SetRegisterScrollbar(DependencyObject obj, ScrollBar value)

        {

            obj.SetValue(RegisterScrollbarProperty, value);

        }

 

 

        public static readonly DependencyProperty RegisterScrollbarProperty =

            DependencyProperty.RegisterAttached("RegisterScrollbar", typeof(ScrollBar), typeof(ScrollBarExtensions), new PropertyMetadata(RegisterScrollbarChangedCallback));

 

        private static void RegisterScrollbarChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)

        {

            var sb = (ScrollBar)e.OldValue;

            if (sb != null)

            {

                sb.ValueChanged -= storyBoardPropertyChanged;

                sb.RemoveChangedHandler("Maximum", storyBoardDependecyPropertyChanged);

                sb.RemoveChangedHandler("Minimum", storyBoardDependecyPropertyChanged);

            }

 

            sb = (ScrollBar)e.NewValue;

            if (sb != null)

            {

                sb.ValueChanged += storyBoardPropertyChanged;

                sb.AddChangedHandler("Maximum", storyBoardDependecyPropertyChanged);

                sb.AddChangedHandler("Minimum", storyBoardDependecyPropertyChanged);

                RefreshValue(sb);

            }

        }

 

        private static void RefreshValue(ScrollBar sb)

        {

            var epsillon = GetEpsillon(sb);

            SetCanDecrease(sb, (sb.Value - sb.Minimum) > epsillon);

            SetCanIncrease(sb, (sb.Maximum - sb.Value) > epsillon);

        }

 

        static void storyBoardDependecyPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)

        {

            RefreshValue((ScrollBar)sender);

        }

        static void storyBoardPropertyChanged(object sender, EventArgs e)

        {

            RefreshValue((ScrollBar)sender);

        }

 

        /// <summary>

        /// true when the Value of a ScrollBar is greater than the Minimum + the Epsillon value

        /// </summary>

        /// <param name="obj"></param>

        /// <returns></returns>

        public static bool GetCanDecrease(DependencyObject obj)

        {

            return (bool)obj.GetValue(CanDecreaseProperty);

        }

 

        public static void SetCanDecrease(DependencyObject obj, bool value)

        {

            obj.SetValue(CanDecreaseProperty, value);

        }

 

        public static readonly DependencyProperty CanDecreaseProperty =

            DependencyProperty.RegisterAttached("CanDecrease", typeof(bool), typeof(ScrollBarExtensions), null);

 

        /// <summary>

        /// true when the Value of a ScrollBar is loawer than the Maximum - the Epsillon value

        /// </summary>

        /// <param name="obj"></param>

        /// <returns></returns>

        public static bool GetCanIncrease(DependencyObject obj)

        {

            return (bool)obj.GetValue(CanIncreaseProperty);

        }

 

        public static void SetCanIncrease(DependencyObject obj, bool value)

        {

            obj.SetValue(CanIncreaseProperty, value);

        }

 

        public static readonly DependencyProperty CanIncreaseProperty =

            DependencyProperty.RegisterAttached("CanIncrease", typeof(bool), typeof(ScrollBarExtensions), null);

 

    }

#    Comments [1] |

Etre notifié du changement de valeur d’une DependencyProperty

Les contrôles n’implémentent pas tous des événements notifiant du changement de valeur de leurs propriétés. Un moyen d’être notifié est d’utiliser le Binding. Voici une classe fournissant des méthodes d’extensions sur les DependencyObject pour ajouter des handlers d’événements.

Ajouter un événement très simple :

            ScrollBar sb = new ScrollBar();

            sb.AddChangedHandler("Value", (o, e) =>

            {

                MessageBox.Show(Convert.ToString(e.NewValue));

            });

Code :

    public static class DependencyObjectExtensions

    {

        /// <summary>

        /// Inner class bound to the Property we want to monitor

        /// </summary>

        private class BindingSubscription : DependencyObject, IDisposable

        {

            /// <summary>

            /// Constructor

            /// </summary>

            /// <param name="source">The DependencyObject instance on which we want to monitor the property</param>

            /// <param name="name">The PropertyPath to the property we want to monitor</param>

            /// <param name="handler">The handler to invoke when the property changed</param>

            public BindingSubscription(DependencyObject source, string name, DependencyPropertyChangedEventHandler handler)

            {

                this.Handler = handler;

                binding = new Binding(name) { Source = source };

                this.PropertyPath = name;

                BindingOperations.SetBinding(this, ValueProperty, binding);

            }

 

            private Binding binding;

 

            /// <summary>

            /// The PropertyPath to the property we want to monitor

            /// </summary>

            public string PropertyPath { get; set; }

 

            /// <summary>

            /// The handler to invoke when the property changed

            /// </summary>

            public DependencyPropertyChangedEventHandler Handler { get; set; }

 

            /// <summary>

            /// Value depedency property.

            /// </summary>

            public static readonly DependencyProperty ValueProperty =

                DependencyProperty.Register(

                    "Value",

                    typeof(object),

                    typeof(BindingSubscription),

                    new PropertyMetadata((d, e) =>

                    {

                        var subscription = ((BindingSubscription)d);

                        var handler = subscription.Handler;

                        if (handler != null)

                            handler(subscription.source, e);

                    }));

 

            /// <summary>

            /// Release the binding from the source

            /// </summary>

            public void Dispose()

            {

                this.ClearValue(ValueProperty);

            }

        }

 

        /// <summary>

        /// Get the list of BindingSubscription stored on one object

        /// </summary>

        /// <param name="obj"></param>

        /// <returns></returns>

        private static List<BindingSubscription> GetListeners(DependencyObject obj)

        {

            var list = (List<BindingSubscription>)obj.GetValue(ListenersProperty);

            if (list == null)

            {

                list = new List<BindingSubscription>();

                SetListeners(obj, list);

            }

            return list;

        }

 

        /// <summary>

        /// Set the list of BindingSubscription on an object

        /// </summary>

        /// <param name="obj"></param>

        /// <param name="value"></param>

        private static void SetListeners(DependencyObject obj, List<BindingSubscription> value)

        {

            obj.SetValue(ListenersProperty, value);

        }

 

 

        /// <summary>

        /// Listeners dependency property

        /// </summary>

        private static readonly DependencyProperty ListenersProperty =

            DependencyProperty.RegisterAttached("Listeners", typeof(List<BindingSubscription>), typeof(DependencyObjectExtensions), null);

 

 

        /// <summary>

        /// Add a handler to a DependencyProperty for change notification

        /// </summary>

        /// <param name="source">The DependencyObject instance on which we want to monitor the property</param>

        /// <param name="name">The PropertyPath to the property we want to monitor</param>

        /// <param name="handler">The handler to invoke when the property changed</param>

        public static void AddChangedHandler(this DependencyObject source, string name, DependencyPropertyChangedEventHandler handler)

        {

            // Create the binding

            BindingSubscription subscriber = new BindingSubscription(source, name, handler);

            // Store the binding into de DependencyObject to prevent garbage collection of the subscriber

            GetListeners(source).Add(subscriber);

        }

 

        /// <summary>

        /// Remove a handler from a DependencyProperty

        /// </summary>

        /// <param name="source">The DependencyObject instance on which we want to monitor the property</param>

        /// <param name="name">The PropertyPath to the property we want to monitor</param>

        /// <param name="handler">The handler to invoke when the property changed</param>

        public static void RemoveChangedHandler(this DependencyObject source, string name, DependencyPropertyChangedEventHandler handler)

        {

            var subscribers = GetListeners(source);

            var subscriber = subscribers.FirstOrDefault(s => s.Handler == handler && s.PropertyPath == name);

            if (subscriber != null)

            {

                subscriber.Dispose();

                subscribers.Remove(subscriber);

            }

 

            if (subscribers.Count == 0)

                source.ClearValue(ListenersProperty);

        }

    }

#    Comments [0] |
# Sunday, May 30, 2010

SLExtensions – Création d’un player vidéo avec SLMedia – partie 1

La partie SLMedia du projet SLExtensions contient tout le nécessaire pour réaliser un player video. Contrairement à un projet comme SMF qui vous fourni un contrôle tout prêt, SLMedia vous fourni des classes “Controller” vous permettant de développer votre player à base de MVVM. Si un player packagé est simple à templater, il trouve vite ses limites lorsque vous voulez intégrer des comportements spécifiques.

Il y a quelque temps sur ce blog j’avais commencé la création d’un player video ici et ici. SLMedia a évolué je vais donc mettre à jour mes exemples.

Affichage de la vidéo

La classe principale de gestion reposant sur la notion de playlist, le passage d’un élément smoothstreaming à un élément video normal n’était pas aisé. Le conteneur affichant la vidéo (le MediaElement ou le SmoothStreamingMediaElement) et maintenant déterminé en fonction du IMediaElement courrant. Pour être plus facilement modulaire et pour permettre de séparer la librairie affichant du smoothstreaming, les éléments video reposent sur des VideoAdapters qui se chargent de la création et de s’attacher aux différents événements des composants videos.
Le design de l’application n’a plus qu’à créer un ContentControl et récupérer le contrôle d’affichage à partir du VideoController.

        <ContentControl Content="{Binding VideoAdapter.Content}"

                       VerticalContentAlignment="Stretch"

                       HorizontalContentAlignment="Stretch" />

Etats visuels

Grâce à Silverlight 4 qui permet maintenant de faire du binding sur des objets de type DependencyObject, l’écriture d’un MapState est un peu simple. Il suffit de binder la propriété Value sur l’objet à surveiller. Et lorsque la valeur change, en fonction des différents mappings définis, l’état visuel correspondant sera déclenché.

        <i:Interaction.Behaviors>

            <slint:MapState Value="{Binding IsPlaying}">

                <slint:MapStateMapping StateName="Playing"

                                      Value="true" />

                <slint:MapStateMapping StateName="Paused"

                                      Value="false" />

            </slint:MapState>

        </i:Interaction.Behaviors>

StringFormat

De même, grâce à Silverlight 4, exit le StringFormatConverter. Il est maintenant possible de directement spécifier la conversion en string dans l’objet Binding.

            <TextBlock Text="{Binding Path=BufferingProgress, Mode=TwoWay, StringFormat=p}" />

La playlist

Le MediaController fournit maintenant des commandes permettant de naviguer dans la playlist. Il suffit de relier deux boutons Précédent / Suivant à ces commandes pour passer d’un élément à l’autre. De même, pour enchainer les éléments à la fin de la lecture, il suffit d’activer la propriété IsChaining.

        <slvideo:VideoController x:Name="controller"

                                AutoPlay="True"

                                IsChaining="True">

            <slvideo:VideoController.Playlist>

                <slvideo:VideoItem Source="http://labs.ucaya.com/lake.wmv" />

                <slvideoSmooth:SmoothVideoItem Source="http://mediadl.microsoft.com/mediadl/iisnet/smoothmedia/Experience/BigBuckBunny_720p.ism/Manifest" />

                <slvideo:VideoItem Source="http://labs.ucaya.com/butterfly.wmv" />

                <slvideo:VideoItem Source="http://labs.ucaya.com/bear.wmv" />

            </slvideo:VideoController.Playlist>

        </slvideo:VideoController>

Navigation :

            <Button Content="Previous"

                   Command="{Binding PreviousItem}" />

            <Button Content="Next"

                   Command="{Binding NextItem}" />

Les sources

#    Comments [1] |