[Silverlight] Fichiers de styles et erreur « Cannot find a Resource with the Name/Key xxxx »

By Aymeric on juin 7th, 2011

Lorsque votre application grossit (son style aussi), il est intéressant de séparer vos styles dans différents fichiers pour gagner en clareté. Il vous suffit ainsi de déclarer vos fichiers dans le fichier App.xaml comme ceci :

<Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Color.xaml" />
                <ResourceDictionary Source="Control.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>

Ceci ne fonctionne que si le fichier Control.xaml n’utilise pas de ressources déclarées dans Color.xaml et inversement. Contrairement à WPF qui tient compte de l’ordre de déclaration (Color.xaml est avant Control.xaml), Silverlight charge les ressources de manière indépendante interdisant ainsi les références entre les diffèrents dictionnaires.

Dans notre exemple, Color.xaml contient :

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Color x:Key="MyColor">#FFF98110</Color>

</ResourceDictionary>

Alors que Control.xaml contient :

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Style TargetType="TextBlock">
        <Setter Property="Foreground"
                Value="{StaticResource MyColor}" />
    </Style>

</ResourceDictionary>

Control.xaml fait donc référence à une ressource déclarée dans Color.xaml. Pour faire fonctionner ce code, il faut déclarer Color.xaml dans Control.xaml comme ceci :

<ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Color.xaml" />
    </ResourceDictionary.MergedDictionaries>

Imaginer ceci dans 10 autres fichiers de style faisant référence à MyColor déclarée dans Color.xaml. Le contenu de Color.xaml serait duppliqué et inclus 10 fois (1 fois par fichier). Inutile de dire que cette pratique est plutôt mauvaise d’un point de vue performance. K.M s’est basé sur cet article pour créer une classe fonctionnant en Silverlight :

public class SharedResourceDictionary : ResourceDictionary
    {
        public static Dictionary<Uri, ResourceDictionary> _sharedDictionaries =
           new Dictionary<Uri, ResourceDictionary>();

        private Uri _sourceUri;
        public new Uri Source
        {
            get { return _sourceUri; }
            set
            {
                _sourceUri = value;
                if (!_sharedDictionaries.ContainsKey(value))
                {
                    Application.LoadComponent(this, value);
                    _sharedDictionaries.Add(value, this);
                }
                else
                {
                    CopyInto(this, _sharedDictionaries[value]);
                }
            }
        }

        private static void CopyInto(ResourceDictionary copy, ResourceDictionary original)
        {
            foreach (var dictionary in original.MergedDictionaries)
            {
                var mergedCopy = new ResourceDictionary();
                CopyInto(mergedCopy, dictionary);
                copy.MergedDictionaries.Add(mergedCopy);
            }
            foreach (DictionaryEntry pair in original)
            {
                copy.Add(pair.Key, pair.Value);
            }
        }
    }

Explications :

Au lieu de recharger le code de Color.xaml dans chaque fichier, il sera chargé la 1re fois puis stocké dans un Dictionaire (Dictionary<> C#). Lorsqu’un autre fichier essayera d’inclure Color.xaml, son contenu sera directement récupéré depuis le Dictionary<>.

Le code précédemment ajouté à Control.xaml devient :

<ResourceDictionary.MergedDictionaries>
        <shared:SharedResourceDictionary Source="/MergedDictionary;Component/Color.xaml" />
    </ResourceDictionary.MergedDictionaries>

sans oublier la déclaration du namespace shared :

xmlns:shared="clr-namespace:MergedDictionary"

Attention, dans la propriété Source du SharedResourceDictionary, « Color.xaml » ne fonctionne plus, il faut impérativement mettre « /MergedDictionary;Component/Color.xaml« .

Projet utilisé dans l’article : ici.

Code et explications originals : ici.

 

[Silverlight] Ce qu’il faut retenir de la semaine du 23/05/2011

By Aymeric on mai 31st, 2011
  • Mango officialisé par FredZone
  • Le 24 avril Microsoft a officialisé la prochaine version de Windows Phone 7. Au programme : Internet explorer 9, multi-tâches, support des groupes, intégration du chat Facebook et environ 500 nouveautés. A noter que les développeurs auront accès à Mango dans moins d’un mois alors que le grand public aura cette mise à jour qu’à l’automne.

  • Mettre un nom par défaut au SaveFileDialog par XAMLGeek
  • Petite nouveauté de Silverlight 5 plutôt intéressante bien que mineure : la possibilité de mettre un nom par défaut lorsque vous souhaitez sauvegarder un fichier grâce à un SaveFileDialog.

  • Sortie des outils de développement Windows Phone 7.1 par Cheryl Simmons
  • Avec l’annonce de Mango, Microsoft sort les outils de développement pour Mango. Il est maintenant possible d’utiliser le GPS ainsi que l’acceléromètre dans l’émulateur pour faciliter le développement de vos applications.

  • Une version web du MarketPlace annoncé par Todd Brix
  • Toujours suite à l’annonce de Mango, Microsoft annonce une version web du MarketPlace avec la possibilité d’installer des applications via Email ou SMS. Cette nouveauté devrait sortie courant d’année 2011.

  • Prendre des captures d’écran sur Windows Phone 7 par Charles Petzold
  • Par défaut il n’est pas possible de prendre une capture d’écran sur un Windows Phone 7. Cette fonctionnalité plutôt utile pour les auteurs de livres, pour publier vos applications sur le MarketPlace, etc. Voici un morceau de code permettant de faire des captures d’écran.

    [Silverlight] Utiliser la molette de la souris avec le Windowless activé sous Firefox et Chrome

    By Aymeric on mai 24th, 2011

    En Silverlight, la molette de la souris fonctionne très bien sous tous les principaux navigateurs (Internet Explorer, Firefox, Chrome, etc.) lorsque le Windowless n’est pas activé. Lorsque ce dernier est activé, la tâche se complique puisque les navigateurs qui se basent sur NAPI (Firefox et Chrome) ne permettent plus à Silverlight de gérer la molette de la souris. Cet article permet grâce au DOM de récupérer l’événement lié à la molette auprès du navigateur.

    Cet article se base sur le code publié sur Compiled Experience auquel j’ai ajouté quelques modifications pour qu’il prenne en compte les éléments dérivant de ItemsControl.

    Pour faciliter l’intégration de ce comportement dans votre application, nous allons créer un « Behavior » à ajouter à votre Listbox, Combobox, etc.

    Le code se divise en 2 parties :

    • Le code « helper » permettant d’aller chercher les informations liées à l’utilisation de la molette auprès du DOM.
    • Le Behavior en lui même faisant appel au « helper » et qui sera ajouter aux contrôles en XAML.

    Le code « Helper »

    public class MouseWheelEventArgs : EventArgs
        {
            public Point Location { get; private set; } public double Delta { get; private set; }
            public bool Handled { get; set; }
            public MouseWheelEventArgs(double delta)
                : this(delta, new Point())
            {
            }
            public MouseWheelEventArgs(double delta, Point location)
            {
                Delta = delta;
                Location = location;
            }
        }
    
        public class MouseWheelHelper
        {
            private static Worker MouseWheelWorker;
            private bool isMouseOver;
    
            public MouseWheelHelper(UIElement element)
            {
                if (MouseWheelWorker == null)
                    MouseWheelWorker = new Worker();
    
                MouseWheelWorker.Moved += HandleMouseWheel;
    
                element.MouseEnter += HandleMouseEnter;
                element.MouseLeave += HandleMouseLeave;
                element.MouseMove += HandleMouseMove;
            }
    
            public event EventHandler<MouseWheelEventArgs> Moved;
    
            private void HandleMouseWheel(object sender, MouseWheelEventArgs args)
            {
                if (isMouseOver)
                    Moved(this, args);
            }
    
            private void HandleMouseEnter(object sender, EventArgs e)
            {
                isMouseOver = true;
            }
    
            private void HandleMouseLeave(object sender, EventArgs e)
            {
                isMouseOver = false;
            }
    
            private void HandleMouseMove(object sender, EventArgs e)
            {
                isMouseOver = true;
            }
    
            private class Worker
            {
                public Worker()
                {
                    if (!HtmlPage.IsEnabled)
                        return;
                    HtmlPage.Window.AttachEvent("DOMMouseScroll", HandleMouseWheel);
                    HtmlPage.Window.AttachEvent("onmousewheel", HandleMouseWheel);
                    HtmlPage.Document.AttachEvent("onmousewheel", HandleMouseWheel);
                }
    
                public event EventHandler<MouseWheelEventArgs> Moved;
    
                private void HandleMouseWheel(object sender, HtmlEventArgs args)
                {
                    double delta = 0;
                    var eventObj = args.EventObject;
    
                    if (eventObj.GetProperty("wheelDelta") != null)
                    {
                        delta = ((double)eventObj.GetProperty("wheelDelta")) / 120;
                        if (HtmlPage.Window.GetProperty("opera") != null)
                            delta = -delta;
                    }
                    else if (eventObj.GetProperty("detail") != null)
                    {
                        delta = -((double)eventObj.GetProperty("detail")) / 3;
    
                        if (HtmlPage.BrowserInformation.UserAgent.IndexOf("Macintosh") != -1)
                            delta = delta * 3;
                    }
    
                    if (delta == 0 || Moved == null)
                        return;
    
                    var wheelArgs = new MouseWheelEventArgs(delta);
                    Moved(this, wheelArgs);
    
                    if (wheelArgs.Handled)
                        args.PreventDefault();
                }
            }
        }
    
    • La classe MouseWheelEventArgs permet de remonter les informations du défilement de la molette de la classe Worker à la classe MouseWheelHelper.
    • La classe MouseWheelHelper est la classe qui sera appelée par le « Behavior« .
    • La classe Worker quant à elle va chercher dans le DOM les informations du défilement de la molette en fonction du navigateur utilisé par le client.

    Pour récupérer ces informations on s’abonne aux événements JavaScript DOMMouseScroll pour Firefox et onmousewheel pour Chrome et les autres. Lorsque la molette est utilisée, les informations sont transmises du JavaScript vers le Silverlight.

    Le code suivant représente le « Behavior » qui sera appliqué aux contrôles en XAML. Dans l’article de base, ce « Behavior » ne s’appliquait qu’à un ScrollViewer, ce qui rend l’utilisation de la molette sur un TreeView impossible. En appliquant ce « Behavior » sur n’importe quel UIElement, on permet ce comportement.

    En revanche, pour bénéficier des propriétés VerticalOffset, HorizontalOffset et des méthodes ScrollToVerticalOffset et ScrollToHorizontalOffset, il nous faut un objet de type ScrollViewer. Heureusement, pour les contrôles dérivant de ItemsControl (TreeView, ListBox, etc.) le toolkit Silverlight fournit la méthode d’extension GetScrollHost() permettant de récupérer leurs objets ScrollViewer. N’oubliez pas d’ajouter une référence vers la DLL System.Windows.Controls.Toolkit à votre projet.

    public class MouseWheelScrollBehavior : Behavior<UIElement>
        {
            private MouseWheelHelper helper;
    
            protected override void OnAttached()
            {
                base.OnAttached();
    
                helper = new MouseWheelHelper(AssociatedObject);
                helper.Moved += OnMouseWheelMoved;
            }
    
            private void OnMouseWheelMoved(object sender, MouseWheelEventArgs e)
            {
                ScrollViewer sv;
                double verticalOffset;
                double horizontalOffset;
    
                if (AssociatedObject is ItemsControl)
                {
                    ItemsControl tr = AssociatedObject as ItemsControl;
                    sv = tr.GetScrollHost();
                }
                else
                {
                    sv = AssociatedObject as ScrollViewer;
                }
    
                if (sv != null)
                {
                    verticalOffset = sv.VerticalOffset;
                    horizontalOffset = sv.VerticalOffset;
                    sv.ScrollToVerticalOffset(sv.VerticalOffset + (e.Delta * -10));
                    sv.ScrollToHorizontalOffset(sv.HorizontalOffset + (e.Delta * -10));
                }
            }
    
            protected override void OnDetaching()
            {
                base.OnDetaching();
    
                helper.Moved -= OnMouseWheelMoved;
            }
        }
    

    Une fois le « Behavior » créé, il faut l’appliquer à chaque ListBox, TreeView, ComboBox, etc… de l’application comme ceci :

    <ListBox>
        <i:Interaction.Behaviors>
            <behaviors:MouseWheelScrollBehavior/>
        </i:Interaction.Behaviors>
        <!-- Code -->
    </ListBox>
    

    i représente System.Windows.Interactivity (n’oubliez pas d’ajouter la référence au projet) et behavior représente le namespace qui contient le « Behavior » créé ci-dessus.

    Pour éviter cette tâche fastidieuse et sujette à l’erreur, la meilleure solution est de créer un style par défaut pour ces contrôles :

    <Style TargetType="ListBox">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="ListBox">
                            <ScrollViewer>
                                <i:Interaction.Behaviors>
                                    <behaviors:MouseWheelScrollBehavior/>
                                </i:Interaction.Behaviors>
                                <ItemsPresenter/>
                            </ScrollViewer>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
    

    Ainsi toutes les ListBox de l’application auront ce comportement.

    Le projet utilisé pour cet article est disponible ici.

    [Silverlight] Ce qu’il faut retenir de la semaine du 16/05/2011

    By Aymeric on mai 23rd, 2011
  • MediaQueries allégé pour Silverlight par Samuel Blanchard
  • Vous souhaitez pouvoir adapter votre page à la taille de la fenêtre du navigateur en ajoutant ou supprimant des contrôles lors du changement de la taille ? Samuel Blanchard vous explique comment faire.

  • Obtenir un itinéraire routier en Silverlight par Oren Gal
  • Silverlight permet de rendre vos applications web riches. Oren Gal propose un article expliquant la démarche pour proposer à l’utilisateur de trouver un itinéraire routier entre 2 points sur une carte.

  • Webinar sur les nouveautés de Silverlight 5 par Michael Crump
  • Encore un lien sur les nouveautés de Silverlight 5, mais cette fois-ci en vidéo avec des démos pour chaque nouveauté (3D, Debug du XAML, etc.).

  • SilverlightSpy, un outil pour analyser votre application Silverlight par Manas Patnaik
  • Que ce soit pour débugger ou analyser les performances de votre application Silverlight, SilverlightSpy est un outil qui fournit un ensemble d’informations sur l’application (Nombre de frames par seconde, événements déclenchés, arbre des contrôles, etc.). Seul inconvénient, ce logiciel est payant.

     

    [Silverlight] Ce qu’il faut retenir de la semaine du 09/05/2011

    By Aymeric on mai 15th, 2011
  • Annonce de Windows Phone 7.5 par le Nouvel Observateur
  • Microsoft a annoncé la date de la présentation de la 1re version majeure de Windows Phone 7, ce sera le 24 mai à New York. Au programme de cette mise à jour : multitâche, Bing Audio et bien d’autres.

  • Qu’est ce que le AppManifest.xaml ? par Kunal Chowdhury
  • Le fichier AppManifest.xaml est un fichier comportant les informations nécessaires au déploiement de l’application : la version de Silverlight nécessaire, le point d’entrée de l’application, les DLL externes, etc.

  • Gérer les ressources en Silverlight par Manas Patnaik
  • La gestion des ressources est un point crucial dans une application Silverlight. Ajouter directement les ressources (images par exemple) dans le fichier XAP, charger les ressources à la demande pour réduire la taille du XAP ou encore les placer dans une assembly, à vous de choisir.

  • Un exemple simple d’implémentation de INotifyPropertyChanged par Michael Crump
  • INotifyPropertyChanged est une interface très importante dans la mise en place du MVVM. Son but est de notifier la vue (View) qu’une propriété a été changée dans le ViewModel. Si vous utilisez des outils comme MVVM Light, cette implémentation est déjà faite pour vous. Si vous souhaitez l’implémenter par vous-même, voici un exemple.

  • Travailler avec les collections et WCF RIA Services (partie 1) par Kevin Dockx
  • Kevin Dockx a commencé une série d’articles sur l’utilisation des collections avec WCF RIA Services.