Accueil > Silverlight, Silverlight 5, WPF > [SL5] : Silverlight WPF’s {x:Static} MarkupExtension

[SL5] : Silverlight WPF’s {x:Static} MarkupExtension

Silverlight 5 Beta fournit à présent la classe de base MarkupExtension qui lorsqu’on implémente la méthode ProvideValue(IServiceProvider), permet de définir une MarkupExtension customisée, qui peut être interprétée par le parseur de XAML de Silverlight 5.

Ceci permet de réaliser des tâches qui n’étaient pas possible, plutot complexes ou verbeuse à mettre en place. Par exemple il est à présent possible de réaliser un comportement similaire à ce que l’on connait en WPF tel que {x:Type}, {x:Static}, etc.

Dans ce post nous allons voir comment réaliser l’équivalent de x:Static connu en WPF utilisé dans certains cas comme le référencement de constantes dans le XAML. Les adeptes de PRISM l’utilisent généralement pour nommer les régions suivant des constantes définies dans l’infrastructure du projet.

Généralités

Pour faire une MarkupExtension vous devez implémenter une classe dérivant de l’interface IMarkupExtension<T> ou la classe de base MarkupExtension.


    public class YourMarkupExtension: IMarkupExtension<Object>
    {
        public Object ProvideValue(IServiceProvider serviceProvider)
        {
                throw new NotImplementedException();
        }
    }

Implémentation du comportement de {x:Static}


using System;
using System.Windows.Markup;
using System.Reflection;
using System.Xaml;

namespace SL5MarkupExtension.MarkupExtensions
{
    /// <summary>
    ///  Classe permettant de faire une MarkupExtension XAML pour récupérer les valeurs de champs ou propriétés.
    /// </summary>
    public class Static : IMarkupExtension<Object>
    {
        #region Data
        private String _member;
        private Type _memberType;
        #endregion

        #region Ctors
        /// <summary>
        ///  Crée une instance de la classe permettant de faire une MarkupExtension XAML pour récupérer les valeurs de champs ou propriétés.
        /// </summary>
        public Static()
        {
        }
        #endregion

        #region MarkupExtension implementation
        /// <summary>
        ///  Obtient un objet correspondant au type fournit en paramètre. Permet de récupérer un champ ou une propriété
        /// </summary>
        /// <param name="serviceProvider">Service permettant de récuperer le service utilisé pour les MarkupExtension.</param>
        /// <returns>
        ///  Un objet correspondant à la chaine fournie en paramettre (Member)
        /// </returns>
        public Object ProvideValue(IServiceProvider serviceProvider)
        {
            Object ret = null;
            Boolean typeResolveFailed = true;
            Type type = MemberType;
            String fieldMemberName = null;
            String fullFieldMemberName = null;

            if (Member != null)
            {
                if (MemberType != null)//on a le type et le membre
                {
                    fieldMemberName = Member;
                    fullFieldMemberName = String.Format("{0}.{1}", type.FullName, Member);
                }
                else //on a pas le type, on regarde si la chaine est bien formatée ex : local:MyEnum.MyEnumeValue et on essaie de résoudre le type
                {
                    Int32 index = Member.IndexOf('.');

                    if (index >= 0)
                    {
                        string typeName = Member.Substring(0, index);

                        if (!String.IsNullOrEmpty(typeName))
                        {
                            IXamlTypeResolver xamlTypeResolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;

                            if (xamlTypeResolver != null)
                            {
                                type = xamlTypeResolver.Resolve(typeName);
                                fieldMemberName = Member.Substring(index + 1); //, Member.Length - index - 1
                                typeResolveFailed = String.IsNullOrEmpty(fieldMemberName);
                            }
                        }
                    }
                }

                if (typeResolveFailed)
                {
                    throw new InvalidOperationException("Member");
                }
                else
                {
                    if (type.IsEnum) //si c'est un enum alors on essaie de résoudre le membre
                    {
                        ret = Enum.Parse(type, fieldMemberName, true);
                    }
                    else //ce n'est pas un enum : probablement un champ ou une propriété
                    {
                        Boolean fail = true;

                        FieldInfo field = type.GetField(fieldMemberName, BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Static);

                        if (field != null)
                        {
                            fail = false;
                            ret = field.GetValue(null);
                        }
                        else//ce n'est pas un champ, on regarde si c'est une propriété
                        {
                            //on regarde si c'est une propriété
                            PropertyInfo property = type.GetProperty(fieldMemberName, BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Static);

                            if (property != null)//c'est une propriété
                            {
                                fail = false;
                                ret = property.GetValue(null, null);
                            }
                        }

                        if (fail)
                        {
                            throw new ArgumentException(fullFieldMemberName);
                        }
                    }
                }
            }
            else
            {
                throw new InvalidOperationException();
            }

            return ret;
        }
        #endregion

        #region Membres
        /// <summary>
        /// Obtient ou définit le membre statique à résoudre ex : local:MyEnum.MyEnumValue si pas de MemberType sinon MyEnumValue
        /// </summary>
        public string Member
        {
            get
            {
                return _member;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("Member value");
                }

                _member = value;
            }
        }

        /// <summary>
        /// Obtient ou définit le type du membre à résoudre
        /// </summary>
        public Type MemberType
        {
            get
            {
                return _memberType;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("MemberType value");
                }

                _memberType = value;
            }
        }
        #endregion
    }
}

Action !

Sample objects

Nous définissons ici une classe Module contenant 3 constantes


public class Modules
    {
        public static readonly string ModuleA = "NewsModule";
        public static readonly string ModuleB = "MediasModule";
        public static readonly string ModuleC = "CalendarModule";
    }

Puis un enum contenant 3 valeurs


    public enum Gender
    {
        Homme,
        Femme,
        Indefini
    }

Mise en pratique

Tout d’abord vous devez référencer votre MarkupExtension et les objets que vous souhaitez référencer.

xmlns:markup="clr-namespace:SL5MarkupExtension.MarkupExtensions"
xmlns:local="clr-namespace:SL5MarkupExtension"

Puis vous n’avez plus qu’à l’utiliser dans votre XAML avec la syntaxe suivante :

{markup:Static Member=local:Modules.ModuleA}

Exemple complet

<UserControl x:Class="SL5MarkupExtension.MainPage"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:markup="clr-namespace:SL5MarkupExtension.MarkupExtensions"
             xmlns:local="clr-namespace:SL5MarkupExtension"
             xmlns:app="clr-namespace:System.Windows;assembly=System.Windows">

    <Grid x:Name="LayoutRoot"
          Background="White">
        <StackPanel VerticalAlignment="Center"
                    HorizontalAlignment="Center">
            <!--CONST-->
            <TextBlock Text="{markup:Static Member=local:Modules.ModuleA}" />
            <TextBlock Text="{markup:Static Member=local:Modules.ModuleB}" />

            <!--CLASS-->
            <TextBlock Text="{Binding Path=InstallState, Source={markup:Static Member=app:Application.Current}}" />

            <!--ENUM-->
            <TextBlock Text="{Binding Source={markup:Static Member=local:Gender.Homme}}" />
        </StackPanel>
    </Grid>
</UserControl>

Résultat

silverlight-x-static-markupextension-like-wpf

Le résultat est très intéressant et ouvre de nouvelles possibilités dans le développement Silverlight 🙂

 

Update 4/7/2012 :
Said Outgajjouft m’a fait part d’une refactorisation du code pour le rendre plus clair, merci à lui 😉

using System;
    using System.Reflection;
    using System.Windows.Markup;
    using System.Xaml;


    /// <summary>
    /// Provides WPFs StaticExtension to Silverligth.
    /// The Static class implements a markup extension that returns static field and property references. 
    /// </summary>
    public class StaticExtension : IMarkupExtension<object>
    {
        /// <summary>"StaticExtension  must have Member property set before ProvideValue can be called.</summary>
        private const string MarkupExtensionStaticMember = "StaticExtension must have Member property set before ProvideValue can be called.";
        /// <summary>'{0}' StaticExtension value cannot be resolved to an enumeration, static field, or static property.</summary>
        private const string MarkupExtensionBadStatic = "'{0}' StaticExtension value cannot be resolved to an enumeration, static field, or static property.";
        /// <summary>Markup extension '{0}' requires '{1}' be implemented in the IServiceProvider for ProvideValue.</summary>
        private const string MarkupExtensionNoContext = "Markup extension '{0}' requires '{1}' be implemented in the IServiceProvider for ProvideValue.";

        private string _member;
        private Type _memberType;


        /// <summary>Gets or sets a member name string that is used to resolve a static field or property based on the service-provided type resolver.</summary>
        /// <exception cref="ArgumentNullException">Attempted to set <see cref="Member"/> to <b>null</b>.</exception>
        public string Member
        {
            get { return _member; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");

                _member = value;
            }
        }


        /// <summary>Gets or sets the <see cref="Type"/> that defines the static member to return.</summary>
        /// <exception cref="ArgumentNullException">Attempted to set <see cref="MemberType"/> to <b>null</b>.</exception>
        public Type MemberType
        {
            get { return _memberType; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");

                _memberType = value;
            }
        }


        /// <summary>
        /// Returns an object value to set on the property where you apply this extension.
        /// For <see cref="StaticExtension"/>, the return value is the static value that is evaluated for the requested static member.
        /// </summary>
        /// <param name="serviceProvider">
        /// An object that can provide services for the markup extension. The service provider is expected to provide a service 
        /// that implements a type resolver (<see cref="IXamlTypeResolver"/>).
        /// </param>
        /// <returns>The static value to set on the property where the extension is applied.</returns>
        /// <exception cref="InvalidOperationException">The <see cref="Member"/> for the extension is <b>null</b> at the time of evaluation.</exception>
        /// <exception cref="ArgumentException">
        /// <p>Some part of the <see cref="Member"/> string did not parse properly</p>
        /// <p>-or-</p>
        /// <p><paramref name="serviceProvider"/> did not provide a service for <see cref="IXamlTypeResolver"/></p>
        /// <p>-or-</p>
        /// <p><see cref="Member"/> value did not resolve to a static member.</p>
        /// </exception>
        /// <exception cref="ArgumentNullException"><paramref name="serviceProvider"/> is <b>null</b>.</exception>
        public object ProvideValue(IServiceProvider serviceProvider)
        {
            var member = Member;
            var memberType = MemberType;
            string memberName;


            if (member == null)
                throw new InvalidOperationException(MarkupExtensionStaticMember);

            if (memberType != null)
            {
                memberName = member;
            }
            else
            {
                var index = member.IndexOf('.');
                if (index == -1)
                    throw new ArgumentException(string.Format(MarkupExtensionBadStatic, member));

                var qualifiedTypeName = member.Substring(0, index);
                if (qualifiedTypeName.Length == 0)
                    throw new ArgumentException(string.Format(MarkupExtensionBadStatic, member));

                if (serviceProvider == null)
                    throw new ArgumentNullException("serviceProvider");

                var xamlTypeResolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;
                if (xamlTypeResolver == null)
                    throw new ArgumentException(string.Format(MarkupExtensionNoContext, GetType().Name, typeof(IXamlTypeResolver).Name));

                memberType = xamlTypeResolver.Resolve(qualifiedTypeName);
                memberName = member.Substring(index + 1);
                if (memberName.Length == 0)
                    throw new ArgumentException(string.Format(MarkupExtensionBadStatic, member));
            }

            if (memberType.IsEnum)
                return Enum.Parse(memberType, memberName, true);

            var field = memberType.GetField(memberName, BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Static);
            if (field != null)
                return field.GetValue(null);

            var property = memberType.GetProperty(memberName, BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Static);
            if (property != null)
                return property.GetValue(null, null);

            throw new ArgumentException(string.Format(MarkupExtensionBadStatic, memberType.FullName + "." + memberName));
        }
    }

 

Publicités
Catégories :Silverlight, Silverlight 5, WPF
  1. Aucun commentaire pour l’instant.
  1. 21 avril 2011 à 18 h 13

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion /  Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s

%d blogueurs aiment cette page :