jeudi 23 février 2012

MEF - Managed Extensibility Framework

Mais qu'est ce que c'est que cet MEF? Voilà à peu de chose prêt ce que je me suis demandé lorsque l'on m'en a parlé. Oui mais comment ça marche? Est la seconde question qui m'a tenu pendant des heures.

En fait, MEF est une chose très simple mais possédant peut de documentation claire, chose que je vais tenter de faire de façon à éviter à d'autres que moi de perdre un temps précieux à trouver de l'aide sur la toile.

Le but de MEF est d'importer des plugins de façon simple, qu'il se trouve dans le même projet ou sous forme d'un fichier ".dll" et c'est là que ça devient intéressant. Car la particularité de MEF comparé à une autre méthode est le fait que lors d'un appel d'un plugin, si il est là il sera traité, si ils sont plusieurs, ils le seront tous et si malheureusement la recherche ne donne rien cela ne déclanche aucunes erreurs c'est plutôt "Je vais charger des plugins. Oh mais je trouve rien là où ils devraient être. Bon ben tampis, je continue ma route". Son comportement est similaire à un bus qui se stop à un arrêt et qu'il y ai des passagers ou non, ça ne change rien pour lui.
Dans le cas présent, on va chercher des ".dll" dans un dossier "plugins" et y récupérer des donneés.
Dans votre projet et dans vos plugins, il faut rajouter la référence de Visual Studio 2010 "System.ComponentModel.Composition" car, bien que basé sur de l'opensource, le framework est désormais inclus dans le framework .Net.

Le code de notre dll est le suivant:
Plugin10.cs
using System.ComponentModel.Composition;
using GPIInterface;

namespace Plugin10
{
    [Export(typeof(IGrany))]
    public class Plugin10 : IGrany
    {
        public string PluginTitle(){
            return "Plugin numéro 10";
        }
        public string PluginDescription(){
            return "Ceci est le premier plugin";
        }
        public string PluginImageName(){
            return "10.jpg";
        }
        public string PluginName(){
            return "Plugin10";
        }
    }
}

Comment on peut le voir, le plugin implémente une interface qui défini simplement les méthodes qui doivent se trouver dans tous les plugins. Mais cela ne change rien à notre projet.
La partie la plus vitale est la ligne "[Export(typeof(IGrany))]" où l'on explique ce que l'on exporte et quelle interface est utilisée.
Le fichier qui importe devra posséder la ligne :
[ImportMany(AllowRecomposition = true)]
        public List<IGrany> Applications { get; set; }

Ensuite dans le même fichier, se trouve une méthode qui possède le code suivant:

MainWindow.cs
AggregateCatalog catalog = new AggregateCatalog(new DirectoryCatalog("Plugins"),
                new AssemblyCatalog(Assembly.GetExecutingAssembly()));
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(this);
           
foreach (var apps in Applications)
{
   ArrayList plugin = new ArrayList();
   plugin.Insert(0, apps.PluginDescription());
   plugin.Insert(1, apps.PluginImageName());
   plugin.Insert(2, apps.PluginName());
   plugin.Insert(3, apps.PluginTitle());
   _dataPlugins.Add(plugin);
}

Les trois premières lignes sont indispensables pour aller chercher les plugins dans le dossier et le reste permet d'en extraire les données de façon indépendante à leur nombre.

On peut dés lors ajouter des dll en quantité dans le dossier et ils seront tous gérés sans modifications du code principale.

Pour une bonne explication sur les bases de MEF, je vous recommande la vidéo suivante: http://channel9.msdn.com/Events/TechDays/TechDays-2011-Belgium/TD029
Enjoy!

FluidKit - ElementFlow

Après avoir vu les transistions, arrêtons nous un instant sur une seconde capacité du FluidKit, l'ElementFlow. Cet élément permet de faire défiler des images de façon fluide et stylée sans avoir besoin de se plonger dans un code sans fin du Xaml.
Tout comme pour l'article précédent, il est indispensable d'ajouter la dll du kit avant tout opération.
Je ne vais pas détailler toutes les options de configuration disponible, mais me contenter de vous offrir une interface contenant l'éssentiel de ce que l'on peut attendre face à une libraire d'images, c'est à dire une interface de visualisation et un "slider".

Code Xaml
MainWindow.xaml

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:Controls="clr-namespace:FluidKit.Controls;assembly=FluidKit"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:Samples="clr-namespace:Fluid_ElementFlow2" x:Class="Fluid_ElementFlow2.MainWindow"
        mc:Ignorable="d">
    <Window.Resources>
        <Samples:StringCollection x:Key="DataSource" />

        <DataTemplate x:Key="TestDataTemplate"
                      DataType="{x:Type sys:String}">
            <Border x:Name="ElementVisual"
                    Background="White"
                    Padding="5"
                    BorderThickness="5"
                    BorderBrush="LightGray"
                    Grid.Row="0">
                <Image Source="{Binding}"
                       Stretch="Fill" />
            </Border>
        </DataTemplate>

    </Window.Resources>
    <Grid Background="#FF1A1A1A" FocusManager.FocusedElement="{Binding ElementName=_elementFlow}">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="0.5*" />
            <ColumnDefinition Width="0.5*" />
        </Grid.ColumnDefinitions>

        <StackPanel Orientation="Vertical"
                    Grid.Row="1"
                    Grid.Column="0"
                    Grid.ColumnSpan="2"
                    Margin="5">
            <Slider x:Name="_selectedIndexSlider"
                    Minimum="0"
                    Value="0"
                    ValueChanged="ChangeSelectedIndex" />
        </StackPanel>
        <Controls:ElementFlow x:Name="_elementFlow"
                              Grid.Row="0"
                              Grid.Column="0"
                              Grid.ColumnSpan="2"
                              ItemsSource="{DynamicResource DataSource}"
                              ItemTemplate="{DynamicResource TestDataTemplate}"
                              TiltAngle="45"
                              ItemGap="0.4"
                              FrontItemGap="1.0"
                              PopoutDistance="2.2"
                              ElementWidth="600"
                              ElementHeight="400"
                              Margin ="0,-120,0,0"
                              SelectedIndex="{Binding Value, ElementName=_selectedIndexSlider}">
            <Controls:ElementFlow.Layout>
                <Controls:CoverFlow />
            </Controls:ElementFlow.Layout>
            <Controls:ElementFlow.Background>
                <LinearGradientBrush EndPoint="0.5,1"
                                     StartPoint="0.5,0">
                    <GradientStop Color="#FF181818" />
                    <GradientStop Color="#FF7A7A7A"
                                  Offset="0.5" />
                    <GradientStop Color="#FF1A1A1A"
                                  Offset="1" />
                </LinearGradientBrush>
            </Controls:ElementFlow.Background>
            <Controls:ElementFlow.Camera>
                <PerspectiveCamera FieldOfView="50"
                                   Position="0,1,6"
                                   LookDirection="0,-1,-6" />
            </Controls:ElementFlow.Camera>
        </Controls:ElementFlow>

        <TextBlock x:Name="_elementTitle"
                           Foreground="White"
                           FontWeight="Bold"
                           VerticalAlignment="Top"
                           HorizontalAlignment="Center"
                           Margin="10,20,10,10"
                           Grid.Row="0"
                           Grid.Column="0"
                           Grid.ColumnSpan="2"
                           FontSize="30"
                           FontFamily="Arial"
                           Text=""/>
       
        <TextBlock x:Name="_osTitle"
                   Foreground="White"
                   FontWeight="Bold"
                   VerticalAlignment="Top"
                   HorizontalAlignment="Right"
                   Margin="10,10,10,10"
                   Grid.Row="0"
                   Grid.Column="1"
                   FontSize="30"
                   FontFamily="Times New Roman"
                   Text="Grany OS"/>
      </Grid>
</Window>




Code C#
MainWindow.cs

using System;
using System.IO;
using System.Windows;
using FluidKit.Controls;
using System.Collections.Generic;
using System.ComponentModel.Composition;

namespace Fluid_ElementFlow2
{
    public partial class MainWindow
    {
        private StringCollection _dataSource;

        [ImportMany(AllowRecomposition = true)]
        public List<IGrany> Applications { get; set; }
       
  public MainWindow()
  {
   InitializeComponent();
   Loaded += Window1Loaded;
  }

        /// <summary>
        /// Configuration of variables and environment
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
  private void Window1Loaded(object sender, RoutedEventArgs e)
  {
            _elementFlow.Layout = new CoverFlow();
            _dataSource = FindResource("DataSource") as StringCollection;
            AddCards(); // Loading of application's pictures
            _selectedIndexSlider.Maximum = _elementFlow.Items.Count - 1;
            _elementFlow.SelectedIndex = _elementFlow.Items.Count / 2;
  }

               
  /// <summary>
  /// Update informations during navigation between applications
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="args"></param>
        private void ChangeSelectedIndex(object sender, RoutedPropertyChangedEventArgs<double> args)
  {
            int index = (int)args.NewValue;
            string source = _dataSource[index];
            string[] titleWithExtension = source.Split(new Char[] { '\\' });
            string[] titleWithoutExtension = titleWithExtension[titleWithExtension.Length - 1].Split(new Char[] { '.' });
            _elementFlow.SelectedIndex = index;
            _elementTitle.Text = titleWithoutExtension[0];
  }
       
        /// <summary>
        /// Search all the plugins available
        /// </summary>
        private void AddCards()
        {
            string path = Directory.GetCurrentDirectory()+"\\Images";
            string[] fichiers = Directory.GetFiles(path);
            foreach (string fichier in fichiers)
            {
                _dataSource.Insert(_dataSource.Count, fichier);
            }
        }
    }
}



StringCollection.cs

using System.Collections.ObjectModel;
namespace GranyPluginsImplement
{
    public class StringCollection : ObservableCollection<string>
    {
        public StringCollection()
        {
        }
    }
}
 

Avec ces 3 fichiers, vous obtenez une interface simple et agréable que vous pourrez à votre guise personnaliser, le rendu étant le suivant:



Si vous sauhaitez d'avantages de possibilités de ElementFlow, je vous conseille de regarder l'exemple fourni avec le code source.