Affichage des articles dont le libellé est XAML. Afficher tous les articles
Affichage des articles dont le libellé est XAML. Afficher tous les articles

26/07/2013

[XAML] INotifyPropertyChanged

Bonjour à toutes et à tous,
aujourd'hui je vais vous présenter l'interface INotifyPropertyChanged. Donc pour celles et ceux qui ne la connaîtrait pas c'est par ici.

Ca sert à quoi ?

Très utilisée dans les technologies XAML (Windows Phone, Silverlight, WPF), cette interface va nous permettre de signaler qu'une propriété d'un objet a été modifiée par le biais d'un événement et cela va permettre de notifier à votre View(MVVM) ce changement et permettre le rafraîchissement d'informations dans le cadre de Binding.
Du coup c'est plutôt utile. ^^

Comment on fait ?

Premièrement en l'implémentant l'interface.

public class User : System.ComponentModel.INotifyPropertyChanged
{

}
Puis vous faites un clic droit sur INotifyPropertyChanged et "Implémenter l'interface". Vous allez récupérer l'évént handler automatiquement dans votre classe :

public event PropertyChangedEventHandler PropertyChanged;

Et c'est tout ce que vous allez récupérer ! Libre à vous de composer la méthode qui va lever l'événement !

Les accesseurs

Pour petit rappel, pour coder nos accesseurs avant C# 3 :

private string _name;
public string Name
{
   get
   {
      return _name;
   }
   set
   {
      _name = value;
   }
}

Pour coder nos accesseurs après C# 3 :

public string Name { get; set; }

Nos accesseurs avec INotifyPropertyChanged :

private string _name;
public string Name
{
   get
   {
      return _name;
   }
   set
   {
      if(_name != value)
      {
         _name = value;
         // Ici on notifie
      }
   }
}

Pourquoi vérifier si la valeur est changé, simplement dans un souci de performances. "Trop de notifications, tue la notification". De plus et toujours pour les mêmes raisons, ne coder vos accesseurs de cette manière que si les propriétés ont besoins de signaler qu'elles ont été modifiées.

Méthodes de notification

Bon pour les méthodes je vous en propose aujourd'hui 2.
La première est celle proposée dans la MSDN :

private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

private string _name;
public string Name
{
   get
   {
      return _name;
   }
   set
   {
      if(_name != value)
      {
         _name = value;
         NotifyPropertyChanged();
      }
   }
}

La seconde est celle que j'ai trouvé dans le livre écrit par Jonathan ANTOINE et Thomas LEBRUN intitulé "MVVM - De la découverte à la maîtrise" que vous pouvez retrouver ici


protected void RaisePropertyChanged<T>(Expression<Func<T>> exp)
{
   var memberExpression = exp.Body as MemberExpression;

   if (memberExpression != null)
   {
      string propertyName = memberExpression.Member.Name;
      PropertyChangedEventHandler handler = PropertyChanged;
      if (handler != null)
      {
         handler(this, new PropertyChangedEventArgs(propertyName));
      }
   }
}

private string _name;
public string Name
{
   get
   {
      return _name;
   }
   set
   {
      if(_name != value)
      {
         _name = value;
         RaisePropertyChanged<string>(() => Name);
      }
   }
}

C'est par exemple cette deuxième implémentation que j'ai utilisé pour mon application "Gâteaux de Laetitia".

Conclusion

Pour plus d'information sur l'interface, c'est par ici
Les deux méthodes présentées ici, ont le mérite de pouvoir continuer à fonctionner même si vous renommez vos variables. Voilà c'est tout pour aujourd'hui.
A bientôt

12/07/2013

[Converter] BoolToVisibility

Allez c'est sans conteste, le converter dont vous vous servirez le plus. Convertir un Boolean en Visibility

Bon je pense qu'il n'y a pas besoin d'explication supplémentaire sur l'intérêt de ce converter donc :

public class BoolToVisibilityConverter : IValueConverter
    {

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            // Vérification de la valeur
            if (value == null)
            {
                return null;
            }
            else
            {
                var myBoolean = (bool)value;

                // Vérification du paramètre
                if (parameter != null)
                {
                    // Autant de paramètre qu'on veut
                    switch (parameter.ToString())
                    {
                        case "invert":
                            return myBoolean ? Visibility.Collapsed : Visibility.Visible;
                        default:
                            return myBoolean ? Visibility.Visible : Visibility.Collapsed;
                    }
                }
                else
                {
                    return myBoolean ? Visibility.Visible : Visibility.Collapsed;
                }
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

Pour en savoir plus sur les converter c'est par ici

Une notion abordée ici qui n'est pas abordée dans mon précédent article est le ConverterParameter qui sert tout simplement à passer un paramètre à mon converter. Dans ma classe, le paramètre invert permet d'inverser le résultat.


    
        
    


Il est à noter qu'en WPF le framework embarque directement le converter donc, il reste plus qu'à l'instancier dans notre code côté XAML :


      

A Lundi pour une nouvelle vidéo.

05/07/2013

[Converter] ImagePicker

On inaugure aujourd'hui la catégorie des Converter en démarrant avec un sélecteur d'image.

Pour finir cette semaine tranquillement, je vous propose un sélecteur d'image :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Data;

namespace MyApp.WP8.View.Converter
{
    public class ImagePicker : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            string matchString = Regex.Match(value.ToString(), @"(?<=<img.*?src=\"")[^\""]*(?=\"".*?((/>)|(>.*</img)))").Value;
            if (matchString == "")
            {
                return "https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPgjvN03Mt9fpkGDo4AnQtOImddidJGYXbT18Yz7EyjBpt2NNrxZ1IvJXnCMNQEY-lNftZb66KYRznXWTCZSyH5HAkiRtD-JrMfbRaZQUzWUUz4PqOQlvlVFgu2zjV5YYlM3M0FMkyRUMN/s220/AvatarFB.png";
            }
            else
            {
                return matchString;
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

Très utile notamment pour les flux RSS, il va nous permettre de renvoyer une image par défaut si notre item ne contient pas d'image.

Bon WE. A lundi pour un nouveau Tuto vidéo.

04/11/2012

[Surface] Premières impressions


Commandée le jour même de l'annonce de la sortie officielle de Windows 8, 3 jours ont suffit pour recevoir ma Surface. Petite précision, le modèle testé est la version WinRT 32 Go avec un Clavier "Touch Cover". La version sans le clavier portait le délai de livraison à 3 semaines.

Donc avec presque un semaine de pratique, je vous livre mes impressions.

Le Hardware



La Surface

Dès la saisie, on sent la surface présente de part son poids. Equilibrée, elle se laisse prendre en main sans déconvenue. A noter la présence d'une prise USB. Chez certains concurrents ce genre de détails n'est pas pris en compte.

D'autre part et personnellement, je n'ai jamais réussi à utiliser une couverture d'iPad pour faire tenir le devise. Avec la surface, pas de souci, même pour les mecs comme moi y arrivent. Le support se sort et se range facilement.

Côté finition, on est sur un objet épurée, comme le sont presque toute les tablettes. La jointure coque, écran laisse présager une certaine solidité dans le temps mais à voir.

Le Touch Cover

Le Touch Cover était vraiment l'élément technique de la surface qui m'intriguait le plus. Et ma première réaction a été vraiment de me dire qu'ils avaient bien bossé. Mais très vite je suis resté dubitatif qu'en au ressenti sur les touches.

Dès la première utilisation, le fait de ne pas avoir de retour sensitif sur les touches m'a vraiment perturbé. Après plusieurs heures d'utilisation avec l'ensemble de la tablette, on constate un véritable contraste entre la fluidité et la réactivité qui est à mon sens excellente (à noter aussi performante que sur Windows Phone) sur la tablette et l'utilisation de certaines touches du clavier qui nécessite d'appuyer un peu plus franchement.

En écrivant ces lignes, je me rends compte qu'au final, à part en protection d'écran, je n'ai plus utilisé le Touch Cover depuis quelques jours. En revanche, mon entourage lui a très vite adopté le clavier, notamment avec le côté rassurant du Touch Pad pour une utilisation souris.

l'OS

Donc, je ne vais pas faire un listing des nouvelles fonctionnalités de Windows 8, d'autres sites spécialisés ont déjà fait ça et certainement mieux que moi. Pour avoir découvert Windows 8 lors de sa première version béta, la plus grande surprise à résider dans l'efficacité du devise à mettre l'OS.

En revanche, un point qui a mon sens risque de coincer avec le grand public est le faible espace disque. Là vous vous dites, oui mais il a pris une version 32Go, je vous réponds oui mais l'OS annonce 17Go de libre alors qu'aucune application tierce n'est installée...

Enfin, le côté frustrant dès le déballage du produit est la mise à jour de pas moins de 17 applications. Du coup, il faut soit paramétrer avant de lancer les mises à jour ou bien patienter 2 heures.

Conclusion

Cette Surface est un beau produit qui sait bien mettre en avant les nouvelles fonctionnalités de Windows 8 et on en attendait pas moins de Microsoft. Maintenant, espérons que les fabricants de hardware emboitent leur pas pour l'innovation.

27/10/2012

[XAML] Bubbling, Tunneling et Konami

Allez aujourd'hui on fait un tout petit topo technique sur une particularité XAML à savoir le bubbling et le tunneling et ensuite on va voir une mise en application ludique et amusante.

Alors on commence par le point théorique.
Lorsque que l'on gère les événements en XAML, on va utiliser 3 types différents d'événements routés

Les événements directs, les événements de type bulle (bubbling) et les événements de type tunnel (tunneling).

Donc pour illustrer rapidement ces trois concepts, on part du postulat que vous avec ceci :

<Grid x:Name="myGrid">
  <StackPanel x:Name="myStackPanel">
    <TextBox x:Name="myTextBox"/>          
  </StackPanel>
</Grid>

Evénements directs

Ici rien de nouveau neuf, on est sur ce qu'il a de plus classique comme cela était géré avec les Forms (Win ou Web). En exemple :

<Grid x:Name="myGrid">
  <StackPanel x:Name="myStackPanel">
    <TextBox x:Name="myTextBox" TextChanged="myTextBox_TextChanged"/>          
  </StackPanel>
</Grid>
Ici l'événement levé sera celui de la modification du texte de la TextBox.


Evénements de type bulle (bubbling)

Le Bubbling va nous permettre de lever un événement ou des événements dans un ordre précis. Ici chaque élément graphique s'abonne au même événement.

<Grid x:Name="myGrid" KeyDown="myGrid_KeyDown" >
  <StackPanel x:Name="myStackPanel" KeyDown="myStackPanel_KeyDown">
    <TextBox x:Name="myTextBox" KeyDown="myTextBox_KeyDown"/>          
  </StackPanel>
</Grid>
Ici le premier élément graphique a lever l'événement sera la TextBox puis le StackPanel et enfin la Grid.


Evénements de type tunnel (tunneling)

Le Tunneling va nous permettre la même granularité :

<Grid x:Name="myGrid" PreviewKeyDown="myGrid_PreviewKeyDown" >
  <StackPanel x:Name="myStackPanel" PreviewKeyDown="myStackPanel_PreviewKeyDown">
    <TextBox x:Name="myTextBox" PreviewKeyDown="myTextBox_PreviewKeyDown"/>          
  </StackPanel>
</Grid>
Ici le premier élément graphique a lever l'événement sera la Grid puis le StackPanel et enfin la TextBox.

Conclusion

D'une manière générale, les événements qui nous permettent de gérer le bubbling sont suffixé de "Down" et ceux pour le tunneling sont préfixé de "Preview". Voilà pour le petit topo. Maintenant on s'amuse...

Coding4Fun

Maintenant qu'on sait faire ça, on fait quoi ? Pour une fois je suis parti d'une idée et pas d'une particularité technique. Donc Aujourd'hui, je vous propose de mettre en place dans nos applications .NET, une fonctionnalité totalement old school, totalement geek, totalement inutile donc totalement indispensable, c'est à dire un Konami Code. C'est parti

On commence par créer notre classe qui va gérer le Code Konami.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;

namespace KonamiWPF
{
    public class KonamiCode
    {
        
        //La belle séquence Konami
        List Konami = new List
        {
            Key.Up, Key.Up,
            Key.Down, Key.Down,
            Key.Left, Key.Right,
            Key.Left, Key.Right,
            Key.B, Key.A
        };

        //notre index pour analyser la séquence
        public int myIndex { get; set; }

        public bool IsCompletedBy(Key myKey)
        {
            //on vérifie que la nouvelle touche pressé correspondent à la série
            if (Konami[myIndex + 1] == myKey)
            {
                myIndex++;
            }

            //mis en place si 3 Key.Up sont rentrés, il ne réinitialise pas analyse de la séquence
            else if (myIndex == 1 && myKey == Key.Up)
            {

            }
            
            // Démarrage de l'analyse de la séquence
            else if (Konami[0] == myKey)
            {
                myIndex = 0;
            }
            else
            {
                myIndex = -1;
            }
            // Si on arrive au bout on renvoie true et on réinitialise
            if (myIndex == Konami.Count -1)
            {
                myIndex = -1;
                return true;
            }
            return false;
        }
    }
}

Maintenant on reprends la même exemple de code XAML :

<Grid x:Name="myGrid" PreviewKeyUp="myGrid_PreviewKeyUp" >
  <StackPanel x:Name="myStackPanel">
    <TextBox x:Name="myTextBox"/>          
  </StackPanel>
</Grid>

Du coup, on fait un petit traitement juste pour vérifier si le Konami est correctement pris en charge.

public partial class MainWindow : Window
    {
        private KonamiCode seq;
        public MainWindow()
        {
            InitializeComponent();
            seq =  new KonamiCode();
        }

        private void myGrid_PreviewKeyUp(object sender, KeyEventArgs e)
        {
            if (seq.IsCompletedBy(e.Key))
            {
                myTextBox.Text = "Konami !!!";
            }
        }
    }

Maintenant je vous laisse faire preuve d'inventivité pour vos créations graphiques et bon code (Konami bien sûr ^^).

12/02/2012

[XAML] Les Converters

Bon aujourd'hui un petit topo sur les converters en WPF, c'est simple, rapide et ça peut rapporter gros !!
Après une expérience de conversions tout à fait improbable (sur 4 couches applicatives!!!) sur un projet en Debug, il va être bon de rappeller les fondemmentaux :

a - Conversion monétaire


Allez on assume que les prix sont stockés dans notre base en Euros sous un format decimal !
Alors on va créer notre converter :
Using ...

namespace MyConverterApp
{
   [System.Windows.Data.ValueConversion(typeof(decimal), typeof(string))]
   class ConvertMoneyInEuros : System.Windows.Data.IValueConverter
   {
       public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo cultureInfo)
       {
           decimal myPrice = decimal.Parse(value.ToString());
           return myPrice.ToString("C");
       }

       public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo cultureInfo)
       {
           string aMyPrice = value.ToString();
           decimal result;
           if(decimal.TryParse(aMyPrice, System.GLobalization.NumberStyles.Any, null, out result)
           {
              return result;
           }
           else
              return 0;
       }
   }
}

Ensuite on instancie dans le XAML :
<Window x:Class="MyConverterApp"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

 xmlns:local="clr-namespace:MyConverterApp"

        Title="MainWindow" Height="350" Width="525">
<Window.Resources>
   <local:ConvertMoneyInEuros x:Key="DisplayInEuros"/>
</Window.Resources>
 ...
<Window>

Puis il ne nous reste plus qu'à l'utiliser dans notre Elément d'interface (en supposant un DataContext dans la Grid) :
<Grid Name="Grid1">
   <StackPanel>
      <label Content={Binding Path=UnitPrice, Converter={StaticResource DisplayInEuros}}
   </StackPanel>

Et cela nous affiche un joli 12,50 € !

Supposons maintenant que nous souhaitions l'afficher en dollars US mais pas comme font certains, qu'un produit à $6.00 ne devienne pas un produit vendu à 6,00 €... Mais bien que notre "12,50 €" devienne un "$9.52" Alors on créé simplement un nouveau converter :
Using ...

namespace MyConverterApp
{
   [System.Windows.Data.ValueConversion(typeof(decimal), typeof(string))]
   class ConvertMoneyInUSDollars : System.Windows.Data.IValueConverter
   {
      readonly decimal _rate = decimal.Parse("1,3131");
      readonly System.Globalization.CultureInfo _usCulture = new System.Globalization.CultureInfo("en-US");
      readonly System.Globalization.CultureInfo _currentcult = System.Globalization.CultureInfo.CurrentCulture;

      public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo cultureInfo)
      {
         decimal myPrice = decimal.Parse(value.ToString());
         myPrice = myPrice /_rate
         return myPrice.ToString("C", _usCulture);
         
      }

      public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo cultureInfo)
      {
         string aMyPrice = value.ToString();
         decimal result;
         if(decimal.TryParse(aMyPrice, System.GLobalization.NumberStyles.Any, null, out result)
         {
            result = result * _rate;
            return result;
         }
         else
            return 0;
      }
   }
}

Supposons maintenant souhaite basculer dynamiquement dans le code d'un affichage en Euros à un affichage en Dollars US avec un bouton radio par exemple :
        private void RadioButtonEURClick(object sender, RoutedEventArgs e)
        {
            var bd = new Binding();
            bd.Converter = new ConvertMoneyInEuros();
            bd.Path = new PropertyPath("UnitPrice");
            bd.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            bd.Mode = BindingMode.TwoWay;

            MyDynamicTextBox.SetBinding(TextBox.TextProperty, bd);
        }

        private void RadioButtonUSDClick(object sender, RoutedEventArgs e)
        {
            var bd = new Binding();
            bd.Converter = new ConvertMoneyInUsDollars();
            bd.Path = new PropertyPath("UnitPrice");
            bd.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            bd.Mode = BindingMode.TwoWay;

            MyDynamicTextBox.SetBinding(TextBox.TextProperty, bd);
        }

b - Conversion d'image


Les exemples présentés ici sont uniquement pour illustrer à nouveau les principes des converters à vous de les réadapter.
Voilà un converter pour remplacer le chemin d'accès à une image en image :
Using ...

namespace MyConverterApp
{
   [System.Windows.Data.ValueConversion(typeof(string), typeof(BitmapImage))]
   class ConvertStringToBitmap : System.Windows.Data.IValueConverter
   {

      public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo cultureInfo)
      {
         try
         {
            string myPath = (string)value;
            Uri myUri = new Uri(myPath);
            BitmapImage myImage = new BitmapImage(myUri);
            return myImage;
         }
         catch
         {
            return new BitmapImage(new Uri("C:\\ImageNotAvailable.jpg"));
         }
      }
      
      public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo cultureInfo)
      {
         throw new NotImplementedException();
      }
   }
}

Pour résumer, les converters sont un moyens efficaces d'effectuer diverses convertions au niveau de l'IHM.

12/10/2011

[XAML] Notifications dans la barre de tâches

En dépannage (qui devient souvent permanent), on a parfois besoin de créer de petits outils avec un accès rapide à 2-3 fonctionnalitées sans forcément nécessiter une (grosse) interface graphique. Moi j'obte dans ce cas pour une application silencieuse avec des accès direct depuis la zone de notification.

Et bien voilà, en WPF rien de natif pour créer des icônes dans la zone de notification et pour celles et ceux qui me lise déjà, vous savez que je ne suis pas fan de réutiliser des Winforms pour pallier à ce problème. J'utilise donc une API qui a été développée par Philipp Sumi. Son API est très efficace pour avoir rapidemment un résultat et en même temps permets d'affiner très précisément si besoin.

On démarre par télécharger le projet ici

On ouvre, on compile, et on va chercher la merveilleuse : Hardcodet.Wpf.TaskbarNotification.dll

Donc notre projet, on rajoute la dll en référence ("Project", "Add Reference...")

Puis on l'instancie dans le XAML :

xmlns:systray="clr-namespace:Hardcodet.Wpf.TaskbarNotification;assembly=Hardcodet.Wpf.TaskbarNotification"

Ensuite on rajoute notre composant :
<systray:TaskbarIcon x:Name="mySystrayIcon" IconSource="/Images/test.ico" ToolTipText="Test">
</systray:TaskbarIcon>

Pour rajouter un menu contextuel, rien de plus simple, celui ci est disponible :
<systray:TaskbarIcon x:Name="mySystrayIcon" IconSource="/Images/test.ico" ToolTipText="Test">
   <systray:TaskbarIcon.ContextMenu>
      <ContextMenu>
         <MenuItem Header="Test"/>
      </ContextMenu>
   </systray:TaskbarIcon.ContextMenu>
</systray:TaskbarIcon>

Ce qui m'a de suite séduit dans cette API c'est le fait de pouvoir utiliser des UserControl pour effectuer ses notifications.

Vous créez donc votre notification dans un Userontrol :


Ensute on rajoute bien évidemment la référence vers le local :
xmlns:local="clr-namespace:MonApplication"

Et on l'intègre de cette façon :
<systray:TaskbarIcon x:Name="mySystrayIcon" IconSource="/Images/test.ico" ToolTipText="Test">
   <systray:TaskbarIcon.TrayPopup>
      <local:MyUserControl/>
   </systray:TaskbarIcon.TrayPopup>
   <systray:TaskbarIcon.ContextMenu>
      <ContextMenu>
         <MenuItem Header="Test"/>
      </ContextMenu>
   </systray:TaskbarIcon.ContextMenu>
</systray:TaskbarIcon>
Ensuite si vous souhaitez utiliser votre User Control à partir du code behind, vous allez procéder comme ceci :

private void ShowMyUserControl()
{
   mySystrayIcon.ShowCustomBalloon(new MyUserControl(), System.Windows.Controls.Primitives.PopupAnimation.Slide, 2500);
}
Personnellement j'aime utiliser mes petites applications de cette manière là c'est à dire avec juste un menu contextuel personnalisé qui s'efface lorsqu'il perd le focus. Ce qui donne :
<systray:TaskbarIcon x:Name="mySystrayIcon" IconSource="/Images/test.ico" ToolTipText="Test" PopupActivation="RightClick" TrayLeftMouseUp="mySystrayIcon_TrayLeftMouseUp" >
   <systray:TaskbarIcon.TrayPopup>
      <local:UC LostFocus="UC_LostFocus"/>
   </systray:TaskbarIcon.TrayPopup>
</systray:TaskbarIcon>
Et dans mon code behind:

public MainWindow()
{
   this.Visibility = System.Windows.Visibility.Hidden;
}

private void UC_LostFocus()
{
   this.Close();
}

private void mySystrayIcon_TrayLeftMouseUp()
{
   //Traitement sur le clic gauche.
}
Vous avez également remarqué que je me suis abonné à TrayLeftMouseUp qui me permet de gérer mon clic gauche.

Vous pouvez retrouver le tutoriel intégral de Philipp Sumi en anglais sur The Code Project, il couvre vraiment la plupart des cas (Du moins les miens ont trouvés toutes les réponses) et notamment approfondie la partie command, routed event, databindig.

02/07/2011

[XAML] Bouton sexy

Voilà une de mes créations réalisez avec Expression Blend, je vous laisse soin de me mettre un petit mot pour me dire ce que vous en pensez !

<UserControl
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 mc:Ignorable="d"
 x:Class="Blog.MainControl"
 x:Name="UserControl"
 d:DesignWidth="640" d:DesignHeight="480">
<UserControl.Resources>
   <ControlTemplate x:Key="BtSexy" TargetType="{x:Type Button}">
      <ControlTemplate.Resources>
         <Storyboard x:Key="OnClick1">
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" Storyboard.TargetName="Anneau_de_couleur">
               <EasingDoubleKeyFrame KeyTime="0" Value="1"/>
               <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="1.45">
                  <EasingDoubleKeyFrame.EasingFunction>
                     <PowerEase EasingMode="EaseIn"/>
                  </EasingDoubleKeyFrame.EasingFunction>
               </EasingDoubleKeyFrame>
               <EasingDoubleKeyFrame KeyTime="0:0:1" Value="1.75">
                  <EasingDoubleKeyFrame.EasingFunction>
                     <PowerEase EasingMode="EaseOut"/>
                  </EasingDoubleKeyFrame.EasingFunction>
               </EasingDoubleKeyFrame>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" Storyboard.TargetName="Anneau_de_couleur">
               <EasingDoubleKeyFrame KeyTime="0" Value="1"/>
               <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="1.45">
                  <EasingDoubleKeyFrame.EasingFunction>
                     <PowerEase EasingMode="EaseIn"/>
                  </EasingDoubleKeyFrame.EasingFunction>
               </EasingDoubleKeyFrame>
               <EasingDoubleKeyFrame KeyTime="0:0:1" Value="1.75">
                  <EasingDoubleKeyFrame.EasingFunction>
                     <PowerEase EasingMode="EaseOut"/>
                  </EasingDoubleKeyFrame.EasingFunction>
               </EasingDoubleKeyFrame>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="Anneau_de_couleur">
               <EasingDoubleKeyFrame KeyTime="0" Value="1"/>
               <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="1">
                  <EasingDoubleKeyFrame.EasingFunction>
                     <PowerEase EasingMode="EaseIn"/>
                  </EasingDoubleKeyFrame.EasingFunction>
               </EasingDoubleKeyFrame>
               <EasingDoubleKeyFrame KeyTime="0:0:1" Value="0">
                  <EasingDoubleKeyFrame.EasingFunction>
                     <PowerEase EasingMode="EaseOut"/>
                  </EasingDoubleKeyFrame.EasingFunction>
               </EasingDoubleKeyFrame>
            </DoubleAnimationUsingKeyFrames>
         </Storyboard>
      </ControlTemplate.Resources>
      <Grid>
         <Border x:Name="Anneau_de_couleur" BorderBrush="{TemplateBinding Background}" BorderThickness="10" CornerRadius="180" RenderTransformOrigin="0.5,0.5">
            <Border.RenderTransform>
               <TransformGroup>
                  <ScaleTransform/>
                  <SkewTransform/>
<RotateTransform/>
                  <TranslateTransform/>
               </TransformGroup>
            </Border.RenderTransform>
         </Border>
         <Border x:Name="Cerclage" BorderThickness="6" CornerRadius="180">
            <Border.BorderBrush>
               <LinearGradientBrush EndPoint="0.181,0.885" StartPoint="0.819,0.115">
                  <GradientStop Color="White" Offset="0.47"/>
                  <GradientStop Color="#FF434343" Offset="0.965"/>
                  <GradientStop Color="#FF434343" Offset="0.07"/>
               </LinearGradientBrush>
            </Border.BorderBrush>
         </Border>
         <Border x:Name="Background" BorderBrush="Black" BorderThickness="0" CornerRadius="180" Background="{TemplateBinding Background}" Margin="6"/>
      </Grid>
      <ControlTemplate.Triggers>
         <EventTrigger RoutedEvent="ButtonBase.Click">
            <BeginStoryboard Storyboard="{StaticResource OnClick1}"/>
</EventTrigger>
      </ControlTemplate.Triggers>
   </ControlTemplate>
</UserControl.Resources>


 <Grid x:Name="LayoutRoot">
  <Button Content="Button" Margin="194,152,262,173" Template="{DynamicResource BtSexy}">
   <Button.Background>
    <RadialGradientBrush>
     <GradientStop Color="#FFF3F3F3" Offset="0"/>
     <GradientStop Color="#FFEBEBEB" Offset="0.5"/>
     <GradientStop Color="#FFDDDDDD" Offset="0.657"/>
     <GradientStop Color="#FF5AA90F" Offset="1"/>
    </RadialGradientBrush>
   </Button.Background>
   <Button.BorderBrush>
    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
     <GradientStop Color="Black" Offset="0"/>
     <GradientStop Color="White" Offset="1"/>
    </LinearGradientBrush>
   </Button.BorderBrush>
  </Button>
 </Grid>
</UserControl>

[XAML] Afficher des images animées .gif dans une IHM WPF

Nativement, WPF ne permet pas d'afficher des images animées au format .gif avec le contrôle Image. Il faut donc trouver une autre solution. Voici les solutions que j'ai pu expérimenter :

1 - Utiliser le Contrôle PictureBox (WinForm).

On commence tout d'abord par ajouter les références nécessaires au projet :

- WindowsFormsIntegration
- System.Windows.Forms

Puis on définit System.Windows.Forms dans le namespace en XAML :

xmlns:wfc="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"

Puis le code XAML nous donne :
<Grid>
   <WindowsFormHost Height="100" Width="100">
      <wfe:PictureBox x:Name="pictureBox1" Height="100" Width="100" ImageLocation="C:\Users\UserID\MesImages\Blog.gif"/>
   </WindowsFormHost>
</Grid>

Pour rappel la PictureBox ne gère pas les URI donc pour alimenter votre PictureBox avec une image stockée sur le Web, on l'alimente dans le code behind comme ceci:

    Public MainWindow()
{
   InitializeComponent();
   Uri url = new Uri("http://t0.gstatic.com/images?q=tbn:ANd9GcSAheOd0oNGdBUtJhTMSDz08_9lpW8rgg2_TBFi3DMAVfsgYYRU4g"); 
   pictureBox1.Image = System.Drawing.Image.FromStream(System.Net.HttpWebRequest.Create("url").GetResponse().GetResponseStream());
}


Ce n'est pas une solution que j'affectionne dans la mesure où l'on utilise une technologie "ancienne génération" dans une IHM WPF et que l'on place un contrôle non vectoriel dans un container, lui-même dans une IHM vectorielle.

2 - Utiliser une Classe dédiée aux Gifs

Cette classe, je ne l'ai pas écrite moi même, je l'ai trouvée ici.
Elle permet :
 - d'afficher des gifs animés tout en ayant les images en "Embedded Resource".
 - Les gifs s'affichent et s'animent directement dans l'IDE (c'est le cas dans VS2010) et à l'exécution bien évidemment. :)
 - Lors de l’exécution un clic de souris permet d'arrêter le gif :

    public class GIFImageControl : Image

    {

        public static readonly DependencyProperty AllowClickToPauseProperty =
        DependencyProperty.Register("AllowClickToPause", typeof (bool), typeof (GIFImageControl),
        new UIPropertyMetadata(true));


        public static readonly DependencyProperty GIFSourceProperty =
        DependencyProperty.Register("GIFSource", typeof (string), typeof (GIFImageControl),
        new UIPropertyMetadata("", GIFSource_Changed));

        public static readonly DependencyProperty PlayAnimationProperty =
        DependencyProperty.Register("PlayAnimation", typeof (bool), typeof (GIFImageControl),
        new UIPropertyMetadata(true, PlayAnimation_Changed));

        private Bitmap _Bitmap;

        private bool _mouseClickStarted;

        public GIFImageControl()

        {
            MouseLeftButtonDown += GIFImageControl_MouseLeftButtonDown;
            MouseLeftButtonUp += GIFImageControl_MouseLeftButtonUp;
            MouseLeave += GIFImageControl_MouseLeave;
            Click += GIFImageControl_Click;
            //TODO:Future feature: Add a Play/Pause graphic on mouse over, and possibly a context menu
        }

        public bool AllowClickToPause

        {
            get { return (bool) GetValue(AllowClickToPauseProperty); }
            set { SetValue(AllowClickToPauseProperty, value); }
        }

        public bool PlayAnimation

        {
            get { return (bool) GetValue(PlayAnimationProperty); }
            set { SetValue(PlayAnimationProperty, value); }
        }

        public string GIFSource

        {
            get { return (string) GetValue(GIFSourceProperty); }
            set { SetValue(GIFSourceProperty, value); }
        }

        private void GIFImageControl_Click(object sender, RoutedEventArgs e)

        {
            if (AllowClickToPause)
            PlayAnimation = !PlayAnimation;
        }

        private void GIFImageControl_MouseLeave(object sender, MouseEventArgs e)

        {
            _mouseClickStarted = false;
        }

        private void GIFImageControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)

        {
            if (_mouseClickStarted)
            FireClickEvent(sender, e);
            _mouseClickStarted = false;
        }

        private void GIFImageControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        
        {
            _mouseClickStarted = true;
        }

        private void FireClickEvent(object sender, RoutedEventArgs e)

        {
            if (null != Click)
                Click(sender, e);
        }

        public event RoutedEventHandler Click;

        private static void PlayAnimation_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)

        {
            var gic = (GIFImageControl) d;
            if ((bool) e.NewValue)

            {

                //StartAnimation if GIFSource is properly set
                if (null != gic._Bitmap)
                    ImageAnimator.Animate(gic._Bitmap, gic.OnFrameChanged);
                
            }

            else
            
                //Pause Animation
                ImageAnimator.StopAnimate(gic._Bitmap, gic.OnFrameChanged);
            
        }

        private void SetImageGIFSource()

        {
                if (null != _Bitmap)
                
                {
                    ImageAnimator.StopAnimate(_Bitmap, OnFrameChanged);
                    _Bitmap = null;
                }

                if (String.IsNullOrEmpty(GIFSource))
                {
                    //Turn off if GIF set to null or empty
                    Source = null;
                    InvalidateVisual();
                    return;
                }

                if (File.Exists(GIFSource))
                _Bitmap = (Bitmap) System.Drawing.Image.FromFile(GIFSource);
                else
                {
                    //Support looking for embedded resources
                    Assembly assemblyToSearch = Assembly.GetAssembly(GetType());
                    _Bitmap = GetBitmapResourceFromAssembly(assemblyToSearch);
                    
                    if (null == _Bitmap)
                    {
                        assemblyToSearch = Assembly.GetCallingAssembly();
                        _Bitmap = GetBitmapResourceFromAssembly(assemblyToSearch);
                        
                        if (null == _Bitmap)
                        {
                            assemblyToSearch = Assembly.GetEntryAssembly();
                            _Bitmap = GetBitmapResourceFromAssembly(assemblyToSearch);
                            
                            if (null == _Bitmap)
                                throw new FileNotFoundException("GIF Source was not found.", GIFSource);
                            
                        }
                        
                    }
                    
                }
                if (PlayAnimation)
                    ImageAnimator.Animate(_Bitmap, OnFrameChanged);
                    
        }
     
        private Bitmap GetBitmapResourceFromAssembly(Assembly assemblyToSearch)
   
        {
            string[] resourselist = assemblyToSearch.GetManifestResourceNames();
            if (null != assemblyToSearch.FullName)
                        
            {        
                string searchName = String.Format("{0}.{1}", assemblyToSearch.FullName.Split(',')[0], GIFSource);     
                if (resourselist.Contains(searchName))        
                {
                                
                    Stream bitmapStream = assemblyToSearch.GetManifestResourceStream(searchName);        
                    if (null != bitmapStream)       
                        return (Bitmap)System.Drawing.Image.FromStream(bitmapStream);     
                }       
            }    
            return null;    
        }
            
        private static void GIFSource_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)

        {
            ((GIFImageControl) d).SetImageGIFSource();
        }

        private void OnFrameChanged(object sender, EventArgs e)

        {
            Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                new OnFrameChangedDelegate(OnFrameChangedInMainThread));

        }

        private void OnFrameChangedInMainThread()

        {
            if (PlayAnimation)
            {
                ImageAnimator.UpdateFrames(_Bitmap);
                Source = GetBitmapSource(_Bitmap);
                InvalidateVisual();

            }
        }

        [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]

        public static extern IntPtr DeleteObject(IntPtr hDc);
        private static BitmapSource GetBitmapSource(Bitmap gdiBitmap)
        {

            IntPtr hBitmap = gdiBitmap.GetHbitmap();
            BitmapSource bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(hBitmap,
                IntPtr.Zero,
                Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions());
            DeleteObject(hBitmap);

            return bitmapSource;
        }

        private delegate void OnFrameChangedDelegate();
    }

Cette classe a été pour moi sur plusieurs petits projets une bonne solution.

3- Créer son gif en WPF

Au bout d'un moment, on utilise quoi ? WPF ! On sait faire des animations avec WPF ? Oui, évidemment ! Alors, la solution consiste à créer d'un côté toute les images qui composent votre gif et de l'autre à créer un storyboard :

<Image Name="Image1">
   <Image.Triggers>
      <EventTrigger RoutedEvent="Image.Loaded"
         <EventTrigger.Actions>
            <BeginStoryboard>
               <Storyboard>
                   <ObjectAnimationUsingKeyFrames Duration="0:0:1" Storyboard.TargetProperty="Source" RepeatBehavior="Forever">
                      <DiscreteObjectKeyFrames KeyTime="0:0:0">
                         <DiscreteObjectKeyFrame.Value>
                            <BitmapImage UriSource="Images/Image1.gif"/>
                         </DiscreteObjectKeyFrame.Value>
                      </DiscreteObjectKeyFrames>
                     <DiscreteObjectKeyFrames KeyTime="0:0:0.2">
                        <DiscreteObjectKeyFrame.Value>
                           <BitmapImage UriSource="Images/Image2.gif"/>
                        </DiscreteObjectKeyFrame.Value>
                     </DiscreteObjectKeyFrames>
                     <DiscreteObjectKeyFrames KeyTime="0:0:0.4">
                        <DiscreteObjectKeyFrame.Value>
                           <BitmapImage UriSource="Images/Image3.gif"/>
                        </DiscreteObjectKeyFrame.Value>
                     </DiscreteObjectKeyFrames>
                     <DiscreteObjectKeyFrames KeyTime="0:0:0.6">
                        <DiscreteObjectKeyFrame.Value>
                           <BitmapImage UriSource="Images/Image4.gif"/>
                        </DiscreteObjectKeyFrame.Value>
                     </DiscreteObjectKeyFrames>
                     <DiscreteObjectKeyFrames KeyTime="0:0:0.8">
                        <DiscreteObjectKeyFrame.Value>
                           <BitmapImage UriSource="Images/Image5.gif"/>
                        </DiscreteObjectKeyFrame.Value>
                     </DiscreteObjectKeyFrames>
                     <DiscreteObjectKeyFrames KeyTime="0:0:1">
                        <DiscreteObjectKeyFrame.Value>
                           <BitmapImage UriSource="Images/Image6.gif"/>
                        </DiscreteObjectKeyFrame.Value>
                     </DiscreteObjectKeyFrames>
                  </ObjectAnimationUsingKeyFrames>
               </Storyboard>
            </BeginStoryboard>
         </EventTrigger.Actions>
      </EventTrigger>
   </Image.Triggers>
</Image>


Evidemment ici on est clairement dans le cas figure où soit vous, soit votre équipe maîtrise en intégralité la phase design.

Voilà, maintenant vous avez toutes les cartes en main.

Bonne Journée.