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.