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.

13/12/2011

[TryAndLikeIt] Un jour, une appli

Grâce à l'opportunité qui m'est offert par le programme Try and like it de Microsoft, je vais vous faire découvrir les applications Nokia qui sont disponible avec le Nokia 800 :

Nokia Musique

09/12/2011

[TryAndLikeIt] Quelques semaines à la découverte du Nokia Lumia 800



La nouvelle est tombée juste avant de partir en week-end : Je viens d'être sélectionné par Microsoft pour promouvoir le Windows Phone 7.5 sur la ville de Toulouse durant les prochaines semaines !

Si tout ce passe comme convenu je devrais aller chercher mon Nokia Lumia 800 la semaine prochaine ! Ca fait vraiment plaisir ! Donc je pense poster très prochainement poster une critique de ce téléphone et faire certainement une comparaison avec le Samsung Omnia 7 que je possède déjà ! :)

Si vous êtes sur le ville de Toulouse et que vous souhaitez une démonstration du Téléphone, n'hésitez pas à me le faire savoir. (Par contre il faut m'en commander 10 pour que je me déplace !!) ^^

Voilà normalement des news très prochainement !

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/09/2011

[SL5] Nouveautés de Silverlight 5 - SL5 RC

Voilà la nouvelle version de Silverlight est sortie depuis quelques heures en version RC.
Pour rappel la version Release Candidate n'est pas la version définitive. Je vous conseille d'autant plus de prendre vos précautions, (genre d'installer cette version dans une VM), car certains développeurs de la communauté ont d'ores et déjà remonté des problèmes pour faire fonctionner ensuite correctement leur précédentes applications (Framework antérieur). Le runtime est pourtant sensé supporter toutes les versions... :( A suivre avec la version finale...
Allez c'est parti, on commence par ici, et on mets à jour son Visual Studio (SP1) si cela n'a pas déjà été fait avec le SDK Windows Phone.

Bref donc Prérequis VS2010 SP1.
Puis SL5 Tools for VS2010 SP1, etc... etc...,

Bon alors quelles sont les nouveautés de cette version :

 - Prise en charge des versions X64.
 - Amélioration des performances réseaux.
 - Amélioration du temps de latence du flux audio.
 - Prise en charge du format H264
 - Prise en charge de l'impression vectorielle
 - Amélioration des rendus 3D et de la prise en charge du GPU. Pour plus d'infos sur ce sujet je ne serais trop vous recommander le blog de David Catuhe ici.
 - Full Trusted Application : Accès complet aux fichiers systèmes de la machine depuis notre application Silverlight. (BDR modifié, certificat de l'éditeur accepté dans le navigateur.)

La liste n'est pas complète. Je ne note ici que les innovations qui me sont apparues les plus significatives

Pour plus d'informations : http://go.microsoft.com/fwlink/?LinkId=214343

Maintenant au boulot, y'a plus qu'à s'amuser!

EDIT : Vous pouvez récupérer la documentation complète de cette nouvelle version ICI !

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.