A lap around .NET4.0 & Visual Studio 2010 Beta 2

Jeudi 22 octobre 2009

schottgu

La disponibilité de la Beta 2 de Visual Studio 2010 et du .NET Framework 4.0 a été annoncé par Jason Zander, General Manager de Visual Studio ce lundi (19/10/2009) suivi de l’annonce de Scott Guthrie, General Manager de la division .Net chez MS Corp que j’ai eu la chance de rencontrer en Mars dernier à l’occasion du MIX09 à Las Vegas.

Parcourons ensemble ces nouvelles versions !

Visual Studio 2010

Visual Studio 2010

Après une 1ère CTP délivrée à Noël dernier qui nous a permis de découvrir les nouvelles possibilités du .NET 4.0 et une Beta 1 en milieu d’année, la Beta 2 diffusée en début de semaine est arrivée à maturation avec l’annonce d’une licence “Go-Live” pour le framework .NET 4.0 et Visual Studio 2010 !

Go-live signifie que vous avez le “GO” de Microsoft pour démarrer des projets de production dessus en vous garantissant un support sur ces versions ! La version finale de ces deux produits étant fixé pour le 22 Mars 2010 (surement à l’occasion du MIX 2010 ?).

L’arrivé de cette version 2010 (10) de Visual Studio fait aussi le ménage dans les nombreuses éditions des versions antérieures. Désormais il n’y aura plus que trois éditions en plus de la gamme “Express” gratuite :

  • Visual Studio Express
  • Visual Studio 2010 Professional avec MSDN
  • Visual Studio 2010 Premium avec MSDN :
  • Visual Studio 2010 Ultimate avec MSDN

A ce sujet je vous recommande la lecture de l’article de Christopher Maneu sur son blog : http://blog.christophermaneu.fr/2009/10/vs-2010-le-point-sur-les-versions/

Visual Studio 2008 / .NET 3.5 <> Visual Studio 2010 / .NET 4.0 “side-by-side” ! N’avez plus peur, l’environnement VS2010/NET4 est parfaitement compatible avec l’environnement VS08/NET35 sur la même machine ! Pas de risque de perturbation, vous pouvez comme moi, installer ce nouvel environnement en beta 2 sur votre poste tout en continuant vos développements sous VS2008. De plus avec la License “Go-Live”, vous pouvez y aller :=)

Pour les téléchargements c’est ici : http://msdn.microsoft.com/en-us/vstudio/dd582936.aspx

Installation

Après récupération de l’ISO, vous pourrez l’installer comme à votre habitude,

welcome start step1

step3 install_dotnet4 install_restart1

install_fin install_errorSL finnish

Vous noterez une erreur à la fin de l’installeur en ce qui concerne le SDK de Silverlight 3.0 ! N’en tenez pas compte ;)

Et en prime, une nouvelle icone pour cette nouvelle version !

windowsBar

Premier lancement

Nouvelle icone et nouveau ”Splash Screen” :

SplashScreen firstStart

… et nouvelle “Start Page” qui a encore évoluée par rapport à la CTP et Beta 1 :

Firstlook StartPage

Vous trouverez aussi de nouveau “Project template” en rapport avec Sharepoint 2007/2010, Office 2010, F#, Silverlight 3.0 et Windows Azure* par défaut.

NewPorject FSharpProject       

En ce qui concerne le “project template” pour les services Azure il faudra patienter jusqu’en Novembre pour la disponibilité des tools Azure pour VS 2010. Actuellement, la création d’un projet Azure sous VS 2010 vous ouvrira la page suivante : http://blogs.msdn.com/jnak/archive/2009/10/18/windows-azure-tools-and-visual-studio-2010.aspx

CloudProject

CoudCommingSoon

Les nouveautés

Editeur de code et designers

Le look & feel de l’éditeur de code et des designer a un peu évolué par rapport aux versions précédentes avec notamment un rendu WPF permettant par exemple de pouvoir zoomer très facilement avec la molette de la souris tout en appuyant sur la touche “Control”. Cela va être très (très .. très) pratique pour les présentations publiques !

 WpfDesigner Coding

Debug zoom

 intellisense2

Online Template & Recent Project

Fonctionnalité bête mais qui m’a beaucoup plus, la possibilité de “Pin”(ner) un projet dans la liste des projets récents à la Windows Seven permettant d’avoir ses projets principaux à porté de main.

recentProject

Dans la création de nouveau projet, nous avons la possibilité de lister des “Project template” en ligne, notamment pour accéder à des Starter Kits

onlineTemplate

Multi-targeting

Cette fonctionnalité a fait son apparition dans Visual Studio 2008 pour nous permettre de sélectionner le framework de destination de notre application (2.0, 3.0 ou 3.5). Etant donné que ces différentes versions du framework exploité la même CLR (qui n’a pas bougé depuis la 2.0), le multi-targeting de VS2008 s’occupé de filtrer les assemblies disponibles dans le Framework choisi. Mais dans certains cas, l’IntelliSense montrait des membres et/ou types des versions supérieures ce qui pouvait engendrer l’utilisation de méthodes du 3.0 ou 3.5 dans un projet 2.0 !

Sous Visual Studio 2010, le multi-targeting a été profondément revu avec ce qu’ils ont appelé les “reference assemblies” qui contiennent les méta données des assemblies d’un framework donné (en quelque sorte la cartographie des assemblies d’un framework).

Grace à cela, les “Property grid” des designers, le profiler, compilateur, Object browser, et tout ce qu’il se trouve dans VS010 se basent sur ces “reference assemblies” pour ne refléter que les types et membres du framework cible sélectionné.

 Properties_Target  

Multi-Monitor

En tant qu’adapte du multi-moniteur, j’ai l’habitude de travailler avec deux écrans en mode étendu sous mon Windows Seven au travail comme à la maison ! J’avoue avoir déjà été gâté lors de l’arrivé de Windows Seven par le “Windows+P” permettant de sélectionner très rapidement et simplement le mode d’affichage.

WindowsP

Avec Visual Studio 2010 vous avez la possibilité de “sortir” vos documents et autres fenêtres de votre Visual Studio comme une fenêtre Windows pour la placer à l’endroit que vous voulez, sur l’écran que vous voulez.

Je peux donc exploiter au mieux mes écrans en plaçant par exemple mon designer WPF sur l’un des écrans pendant que j’édite son code source sur le deuxième :

multiscreen 

Navigate To

Nommé initialement “Quick Search”, le “Navigate To” permet de rechercher facilement des symboles (variables, méthodes, classes, types, etc…) dans votre solution. (A l’inverse du “Search” qui recherche du texte brut dans des fichiers !).

NavigateTo

Call Hierarchy

Cette fonctionnalité fort pratique nous permet d’avoir un affichage de la hiérarchie des appels sur une méthode.

CallHiearchy3 CallHiearchy2

Highlighted reference

Cette fonctionnalité permet de surligner dans mon code toutes les références d’une variable ou paramètres dans l’éditeur de code ! Assez pratique pour voir d’un seul coup d’œil où votre paramètre/variable est utilisé !

Highlighted

Parallel Programming and Debugging

Avec l’arrivé de Parallel Fx dans le .NET 4.0, VS2010 est doté d’outils de debugging permettant la visualisation et le débogage de chaque thread.

thread

Dependency Graph

Permet de construire des graphs à partir du code source et/ou assemblies de votre solution pour représenter les différentes relations par assembly, namespace, classe ou manière personnalisée en spécifiant ce que l’on veut y voir et avec quel filtre. 

AssemblyDependecyClassDependencygraphCustom

Les Diagrams

On y trouve un nouveau designer permettant de concevoir deux types de diagrammes : les “Layer Diagram” (architecture logique qui nous permettent d’organiser nos classes, namespaces, fichier de code, projets de notre solution, …) et les “UML Diagram” que l’on ne présentent plus.

DiagramULM

Architecture Explorer

Cette nouvelle fenêtre nous permet de parcourir les relations dans notre code.

ArchitectureExplorer

CallHiearchy

Et bien d’autre…

  • Nouvelle version de TFS 2010, le serveur de contrôle de code source et de son client dans VS2010
  • Un nouveau profiler de performance
  • Nouveau outils à destination de testeur (Microsoft Test and Lab Manager entre autre)
  • Nouveau designer pour les Workflows
  • Un explorer pour Sharepoint
  • Le “Code Optimized Web Development Profile” : un profile sans le designer web qui permet d’éditer le code source directement
  • De nouveaux snippets

.NET framework 4.0

Traiter du nouveau framework 4.0 dans son ensemble serait chose impossible dans un seul article tant les nouveautés sont importantes. Tentons d’en donner un petit aperçu.

.NET

Un nouveau moteur d’exécution : la CLR 4.0

Le framework 4.0 embarque un nouveau moteur d’exécution, la CLR 4.0 qui n’avait pas évolué depuis la version 2.0 en 2005. Rappelez-vous que le framework 3.0 puis 3.5 n’ont apporté que de nouvelles assemblies (comme WCF, WF, WPF, Linq, Data Service, etc…) mais tout cela reposant sur la CLR 2.0.

Avec ce nouveau framework la CLR à dû évoluer pour améliorer le support du multi-coeur, du garbage collector et des langages dynamiques (DLR).

Nouveau langages : C# 4.0 et VB 2010

Vous trouverez une série sur les nouveautés de VB 2010 sur le blog de Redo : http://blogs.developpeur.org/redo/archive/2009/02/10/nouveaut-s-de-visual-basic-2010-vb10-le-livre-blanc-recueil-de-blog.aspx

Concernant C# 4.0, nous pouvons citer entre autre :

  • Les types “dynamic”
  • La co- et contra- variance sur les collections
  • Les paramètres nommées et optionnels
  • L’amélioration de l’interopérabilité COM

ADO.NET 4.0

Englobant :

  • La beta 2  de Entity Framwork 4
  • La beta 2 de ADO.NET Data Service 4 (ex Astoria)
  • Lire : http://bit.ly/3zWNB1

ASP.NET 4.0

  • Nouveaux contrôles pour les graphiques
  • Nouveau framework pour ASP.NET Ajax 4.0
  • ASP.NET MVC 2
  • L’URL Routing pour Web Forms

WPF 4.0

  • Support du multi-touch et extensibilité de la taskbar pour Windows Seven
  • SDK de Surface 2.0
  • Nouveaux contrôles (dont le Rubbon)
  • Amélioration des performances, stabilité, ..

WCF & WF 4.0

  • WF : Nouvelles Activities (lire http://bit.ly/12Htlb)
  • WCF : Service de découverte, support du REST, service de routing

Et bien d’autre

  • ParallelFx : Nouvelle API pour simplifier le développement parallèle comme TPL (Task Parrallel Library) ou PLINQ (Parrallel LINQ)
  • MEF : Managed Extensibility Framework dont j’ai traité un article à ce sujet en début d’année

 

Et avant de finir, quelques liens :

[Astuce] Deviner le type MIME d’un FileInfo

Mercredi 18 mars 2009

Depuis la nouvelle CTP du Live Framework vous avez remarqué qu’on était désormais « obligé » de passer le type MIME d’un fichier que l’on ajoute via la méthode DataEntryCollection.Add/AddAsync (voir mon post précédent).

Pour récupérer le type MIME d’un fichier, le plus simple est de regarder dans le HKEY_CLASSES_ROOT du registre pour récupérer la clé ayant pour nom l’extension et de regarder la valeur du « Content Type » :

image

Côté code, voici comment faire cela simplement :

1
2
  var fileClass = Registry.ClassesRoot.OpenSubKey(".jpg");
  var contentType = fileClass.GetValue("Content Type").ToString();

A l’exécution contentType vaudra « image/jpeg », le type MIME d’un fichier .JPG !

Afin de faciliter nos développements, nous pouvons, à partir de ces deux lignes de code, créer une méthode d’extension sur la classe FileInfo. Voici son code :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  using System.IO;
  using Microsoft.Win32;
 
  public static class MimeTypeExtension
  {
      public static string GetMimeType(this FileInfo fileInfo)
      {
          var contentType = "application/octet-stream";
          try {
              var fileClass = Registry.ClassesRoot.OpenSubKey(fileInfo.Extension);
              contentType = fileClass.GetValue("Content Type").ToString();
          } catch { }
          return contentType;
      }
  }

De façon a pouvoir l’utiliser simplement de la manière suivante :

1
2
FileInfo fileInfo = new FileInfo({MON_FICHIER});
Console.WriteLine("Filename: {0} - Type MIME : {1}", fileInfo.Name, fileInfo.GetMimeType());

Vous remarquerez que si le type MIME n’est pas trouvé nous renverrons un « application/octet-stream« .

Bon développement ;)

[.NET 4.0] Introduction à MEF : Managed Extensibility Framework

Samedi 21 février 2009

image Bientôt disponible dans la nouvelle version de la plateforme .NET de Microsoft, le .NET 4.0 introduira le framework MEF actuellement disponible en code ouvert sur CodePlex.

MEF pour Managed Extensibilty Framework, que vous trouverez sous le namespace System.ComponentModel.Composition, est une librairie .NET facilitant le développement d’application extensible.

C’est le travail résultant de l’équipe « Application Framework Core«   responsable d’harmoniser les frameworks applicatifs (type WinForm, SL, WPF et ASP.NET) de la même façon que le fait l’équipe de la BCL (Base Class Library) au niveau le plus bas de .NET.

Développer avec MEF, comment ca marche ?

Aujourd’hui pour concevoir nos applications de façon extensible, nous sommes souvent obligé de développer notre propre système d’extention. L’arrivée, fin 2007, du .NET 3.5 a ouvert une brèche dans la mise à disposition d’un framework générique pour le développement de telle application. Le System.Addin (encore nommé MAF, Managed Addin Framework) met à disposition des mécanismes simplifiant la mise en place d’un système d’extension basé sur des AddIns. Je traiterai un peu plus bas dans cet article des différences entre MAF et MEF.

MEF offre quant à lui un mécanisme d’import/export assez original. Le concept est de marquer des classes, variables, propriétés ou méthode par l’attribut Export. Dans d’autre classes vous pouvez alors créer des propriétés marquées de l’attribut Import pour venir importer les classes, variables, propriétés ou méthodes exportés.

MEF n’est pas qu’un simple framework d’ IoC (Inversion de contrôle) bien qu’il propose des fonctionnalités similaires comme l’injection de dépendance (comme le fait aussi StuctureMap ou Unity). MEF met surtout le focus sur la découverte de composant et permet à votre application de se composer par elle-même et cela à l’exécution.

Pour mieux comprendre, voyons un peu de code :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[Export]
public class Part1
{
    [Import]
    public Part2 Part { get; set; }
 
    public void Demo()
    {
        Part.WriteLine("Enjoy MEF");
    }
}
 
[Export]
public class Part2
{
    public void WriteLine(string s)
    {
        Console.WriteLine(s);
    }
}

Si j’appelle la méthode Part1.Demo(), je verrai apparaître sur l’écran « Enjoy MEF« . Cela vous parait-il correct ?

Ne connaissant pas MEF j’aurais tendance à dire non ! En effet, dans ma classe Part1, qui a instancié ma propriété nommée Part ?

La réponse est MEF. En fait pour être exacte, voici le code de ma classe Main qui effectue l’appel à Part1.Demo() :

1
2
3
4
5
6
7
8
9
10
11
12
// Creation du container
var container = new CompositionContainer(
    new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly()));
// Instanciation de notre Part1
var part1 = new Part1();
// Creation du batch contenant notre Part2
var batch = new CompositionBatch();
batch.AddPart(part1);
// Composition !
container.Compose(batch);
// Demo
part1.Demo();

Comme vous le constatez, nous créerons ici un container basé sur un AssemblyCatalog. C’est à dire que toutes mes classes de mon assembly (GetExecutingAssembly) marquées par l’attribut Export seront chargées dans mon container.

Ensuite nous créons un CompositionBatch dans lequel nous allons ajouter les classes à « composer » qui dans notre cas est Part1. Le batch est ensuite exécuté (Compose) dans notre container. A ce moment là, les Parts de notre batch sont analysées pour y réaliser les Imports nécessaire.

Pour finir j’appelle la méthode Demo de notre Part1. Celle-ci appelle la méthode WriteLine de la classe importée Part2 pour afficher le texte à l’écran.

L’ Import/Export

Dans l’exemple ci-dessus, l’export est de type Part2 car nous n’avons rien précisé (l’export se fera alors sur le type exporté). Je pourrais aussi préciser le type de l’export dans le constructeur de l’attribut Export. Voyons l’exemple de la calculette :

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface ICalculate
{
    double Compute(double x, double y);
}
 
[Export(typeof(ICalculate))]
public class Addition : ICalculate
{
    public double Compute(double x, double y)
    {
        return x + y;
    }
}

Dans cet exemple je veux exporter mon Addition comme un ICalculate car je ne connais pas toutes les implémentations qu’il y aura derrière un ICalculate. J’ai donc précisé explicitement le type de l’export.

Du coté de mon Import, je préciserai aussi le type à importer :

1
2
[Import(typeof(ICalculate))]
public ICalculate MonOperation { get; set; }

MEF introduit aussi le concept de « Naming and Activation service » permettant de récupérer un objet à partir d’un nom (chaîne de caractère). Je pourrais par exemple exporter mon addition sous le nom « MonAddition » par le code :

1
2
3
4
5
6
7
8
[Export("MonAddition")]
public class Addition : ICalculate
{
    public double Compute(double x, double y)
    {
        return x + y;
    }
}

Et l’importer depuis n’importe quelle classe chargée dans mon container par l’import :

1
2
[Import("MonAddition")]
public ICalculate MonAddition { get; set; }

Collection d’exportation

Reprenons l’exemple de la calculette. Je suis susceptible d’exporter plusieurs implémentations d’ICalculate pour la soustraction, multiplication ou division par exemple.

Si je tente de rajouter un export pour la multiplication en ajoutant le code :

1
2
3
4
5
6
7
8
[Export(typeof(ICalculate))]
public class Multiplication : ICalculate
{
    public double Compute(double x, double y)
    {
        return x * y;
    }
}

Une exception sera levée :

1
2
3
4
5
6
System.ComponentModel.Composition.CompositionException: The composition produced a single composition error. The root cause is provided below. Review the CompositionException.Errors property for more detailed information.
 
1) More than one exports were found that match the constraint '(exportDefinition
.ContractName = "MEFDemo.ICalculate")'.
 
Resulting in: Cannot set import 'MEFDemo.Calculette.MonOperation (ContractName="MEFDemo.ICalculate")' on part 'MEFDemo.Calculette'. Element: MEFDemo.Calculette.MonOperation (ContractName="MEFDemo.ICalculate") --> MEFDemo.Calculette

Il est normal que plusieurs classes ne puissent pas « rentrer » dans une propriété. Pour ce faire nous allons simplement typer notre import comme une IEnumerable de ICalculate :

1
2
[Import(typeof(ICalculate))]
public IEnumerable<ICalculate> MesOperations { get; set; }

Je pourrais alors jouer le code suivant :

1
2
3
4
foreach (var operation in MesOperations)
{
    Console.WriteLine(operation.Compute(2, 3));
}

S’afficheront à l’écran les nombres : 5 et 6, pour le résultat de l’addition et de la multiplication de 2 et 3.

Import/Export de membres

Jusqu’ à présent nous nous sommes contenté d’exporter des classes soit à partir d’un nom ou d’un type (implicite ou non). Mais MEF ne s’arrête pas là en proposant la possibilité d’exporter un membre d’une classe :

  • un champ (variable)
  • une propriété
  • une méthode

La visibilité du membre n’a aucune conséquence sur l’export. Vous pouvez en effet exporter un membre déclaré comme « private » par exemple.

Pour les champs ou propriétés cela est très simple, voici un court exemple :

1
2
3
4
5
class Settings
{
    [Export("VERSION")]
    private const string VERSION = "1.0";
}

Nous exportons ici une constante privée de type string sous le nom VERSION. Dans une de mes parts, je pourrais alors importer cette constante par le code :

1
2
3
4
5
6
7
[Import("VERSION")]
public string Version { get; private set; }
 
public void Demo()
{
   Console.WriteLine(Version);
}

Pour les méthodes, nous devons l’importer sous forme d’un délégué. Dans les cas simples, les délégués Action<> (pour une procédure) et Func<> (pour les fonctions) conviennent parfaitement. Voici un petit exemple :

1
2
3
4
5
6
7
8
class Addition
{
    [Export("MaMethodeAddition")]
    public int MonAddition(int n1, int n2)
    {
        return n1 + n2;
    }
}

Ici nous exportons une méthode qui prend deux entier en entrée et retourne la somme des deux. Son délégué peut s’exprimer sous forme d’un Func<int, int, int>. Voici l’importation :

1
2
3
4
5
6
7
[Import("MaMethodeAddition")]
public Func<int, int, int> Addition { get; set; }
 
public void Demo()
{
    Console.WriteLine(Addition(2, 3).ToString());
}

Ne sachant pas forcement si nous aurons dans notre container les exports nécessaires pour satisfaire toutes les importations, nous pouvons préciser si nous acceptons ou pas qu’une importation soit laissée vide par le paramètre AllowDefault :

1
2
3
4
5
6
7
8
[Import("MaMethodeAddition", AllowDefault=true)]
public Func<int, int, int> Addition { get; set; }
 
public void Demo()
{
    if(Addition != null)
        Console.WriteLine(Addition(2, 3).ToString());
}

Lazy-Export et les métadonnées

Le Lazy-Export

Dès lors que mon application devient importante, il se peut que MEF se retrouve avec beaucoup d’importation à satisfaire et le démarrage de mon application en prend un coup coté performance.

MEF propose du « lazy-loading » permettant de réaliser l’ importation quand on tente d’y accéder. Sa mise en place est relativement facile puisse qu’il s’agit, sur l’importation, de déclarer un Export<> sur mon type à importer réellement :

1
2
[Import("MonAddition")]
public Export<icalculate> MonAddition { get; set; }

Dans mon code, je n’aurais qu’à appeler la méthode GetExportedObject() pour récupérer l’instance typée de mon import :

1
MonAddition.GetExportedObject().Compute(2, 3);

Ceci est tout à fait compatible dans le cas d’un IEnumerable. Reprenons notre calculette de démo :

1
2
[Import(typeof(ICalculate))]
public IEnumerable<Export<ICalculate>> MesOperations { get; set; }

ou plus simplement :

1
2
[Import(typeof(ICalculate))]
public ExportCollection<ICalculate> MesOperations { get; set; }
Les métadonnées

Lorsque je marque une classe ou membre pour l’exportation, il se peut que j’ai besoin de donner plus d’information que simplement le type ou nom de l’exportation. Je pourrais par exemple avoir plusieurs composants réalisant la même fonction mais de façon différente. Comment pourrais-je les distinguer s’ils s’exportent de la même façon ?

Reprenons l’exemple de la calculette. Je voudrais afficher dans ma console l’opération ainsi que le résultat. Faut-il encore que je sache quel est le signe de mon opération. Nous déclarons cela comme métadonnées de notre composant. Voyez le code suivant :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[Export(typeof(ICalculate))]
[ExportMetadata("Operator", "+")]
public class Addition : ICalculate
{
    public double Compute(double x, double y)
    {
        return x + y;
    }
}
 
[Export(typeof(ICalculate))]
[ExportMetadata("Operator", "*")]
public class Multiplication : ICalculate
{
    public double Compute(double x, double y)
    {
        return x * y;
    }
}

Nous avons ici créé une métadonnée nommée « Signe » pour contenir le signe de mon opération.

Du coté de l’importation, nous utiliserons le type Export<ICalculate> (qui assure aussi la fonction de « lazy-loading ») pour permettre d’accéder aux métadonnées sous la propriété Metadata.

1
2
3
4
5
6
foreach (var operation in MesOperations)
{
    Console.WriteLine("2 {0} 3 = {1}",
                       item.Metadata["Signe"].ToString(),
                       item.GetExportedObject().Compute(2, 3));
}

La propriété Metadata est en fait un Dictionnary<string, object> permettant de placer autant de métadonnées que l’on veut sur nos composants.

Vous avez également la possibilité au niveau de l’importation, de préciser les métadonnées obligatoire pour satisfaire l’importation par l’attribut ImportRequiredMetadata. Ici nous allons préciser que nous voulons obligatoirement la métadonnée « Signe » sur nos composants :

1
2
3
[Import(typeof(ICalculate))]
[ImportRequiredMetadata("Signe")]
public ExportCollection<ICalculate> MesOperations { get; set; }

MEF propose aussi de typer fortement les métadonnées par la création d’attribut personnalisé (simple classe héritant d’Attribute et marquée par l’attribut MetadataAttribute). Voici un exemple simple :

1
2
3
4
5
6
7
8
9
10
11
12
13
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class)]
public class MonOperationAttribute : Attribute
{
    public string Signe { get; set; }
}
 
[Export(typeof(ICalculate))]
[MonOperation(Signe="+")]
public class Addition : ICalculate
{
  // ....
}

Et du coté de l’importation :

1
2
3
4
5
6
foreach (var operation in MesOperations)
{
    Console.WriteLine("2 {0} 3 = {1}",
                       ((string)item.Metadata["Signe"]),
                       item.GetExportedObject().Compute(2, 3));
}

Dans notre cas, avec comme simple métadonnée le signe de l’opération, il est inutile de créer son propre attribut, le ExportMetadata était suffisant mais ceci est dit à titre d’exemple :)

Cycle de vie des composants et recomposition dynamique

Lorsqu’on l’on développe ce genre d’application, il est indispensable de comprendre comment sont gérées les instances de nos composants. Il se peut par exemple que je veuille qu’une seule instance d’un composant dans mon container, ou à l’inverse que je veuille autant d’instance que d’importation de mon composant.

Pour cela MEF va mettre à disposition un attribut CompositionOption permettant de définir la CreationPolicy de mon composant. Il y a trois types de CreationPolicy :

  • Shared : une seule et unique instance par container.
  • NonShared : nouvelle instance pour chaque importation.
  • Any : peut être Shared ou NonShared en fonction de ce que demande l’importation ou de ce que propose l’exportation.

Pour indiquer à mon composant de notification par mail d’avoir une seule et unique instance dans mon container, je pourrais écrire :

1
2
3
4
5
[Export(typeof(INotify))]
[CompositionOptions(CreationPolicy=CreationPolicy.Shared)]
public class SmtpNotification : INotify
{
}

Du coté de l’importation, je pourrais choisir la CreationPolicy à respecter. Ici nous allons importer un INotify (par exemple) de type Shared :

1
2
3
4
5
6
[Export]
public class Part1
{
    [Import(RequiredCreationPolicy=CreationPolicy.Shared)]
    public INotify MonComposantNotifyShared { get; set; }
}

Si je ne précise rien sur l’export ou l’import, la CreationPolicy par défaut est à Any. Voici le tableau des différentes possibilités :

  Export Any Export Shared Export NonShared
Import Any Shared Shared NonShared
Import Shared Shared Shared Incompatible
Import NonShared NonShared Incompatible NonShared
La Recomposition

Nous serons peut être amené durant l’exécution de notre application, à ajouter ou supprimer des composants dans notre container. MEF propose alors la possibilité de recomposer automatiquement les importations par le paramètre AllowRecomposition :

1
2
3
4
5
6
7
8
[Export]
public class Part1
{
    [Import(AllowRecomposition=true)]
    public ExportCollection<ICalculate> MesOperations { get; set; }
 
    // ....
}

De ce fait, dans notre calculette de démo, si on injecte à l’exécution de nouvelle opération, ma collection d’opérations (MesOperations) sera automatiquement mis à jour proposant la nouvelle opération fraîchement injectée. Je pourrais surveiller tout cela en implémentant l’interface INotifyImportSatisfaction pour être averti des importations satisfaites.

Les containers et catalogues

Nous avons survolé dans l’introduction les concepts de containers, catalogues et batchs. Laissez-moi revenir un peu plus en détail sur ces points là.

Pour mettre à disposition nos composants dans le container nous allons nous servir d’un catalogue. Un catalogue contient un ensemble de composants exportés.

Nous avons plusieurs types de catalogue dans MEF actuellement :

  • Assembly Catalog : se base sur une assembly pour découvrir les composants
  • Directory Catalog : se base sur un répertoire pour découvrir les assembly pouvant contenir des composants
  • Aggregating Catalog : agrège plusieurs catalogues en un seul
  • Type Catalog : découvre seulement les types précisés dans le constructeur de ce catalogue

Par exemple, pour créer un catalogue se basant sur l’assembly en cours d’exécution et autres assembly dans le répertoire « MesComposants », nous écrirons :

1
2
3
4
// Create Catalog
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly()));
catalog.Catalogs.Add(new DirectoryCatalog(@"MesComposants\"));

Une fois notre catalogue créé, nous allons créer notre container sur base de ce catalogue par la ligne :

1
var container = new CompositionContainer(catalog);

Je pourrais ensuite exécuter un batch permettant de composer les « Parts » de mon application  :

1
2
3
var batch = new CompositionBatch();
batch.AddPart(monComposant1);
container.Compose(batch);

MEF vs le reste du monde (Unity, StructureMap, MAF, …)

Il est important de ne pas tout mélanger !

MEF et MAF

MAF disponible depuis le .NET Framework 3.5 sous le namespace System.Addin est un framework permettant de simplifier le développement d’application à plugin (addin) par la mise en place de contrats entre l’application hôte et les addins.

Le focus est mis sur l’isolation (via des domaines d’application différents) et sur le versionning d’addin permettant de concevoir facilement des applications extensible par addin.

MAF ne fourni pas des fonctionnalités comme l’injection de dépendance comme MEF ou autre framework d’IoC.

Pour résumer c’est une question de philosophie, concevez-vous une application extensible via des plugins en devant assurer l’isolation et le versionning des différents plugins ou concevez-vous une application architecturée par composant ?

Mais il est aussi possible de concevoir une application exploitant ces deux frameworks (par exemple un composant extensible par des plugins ou un plugin conçu par composant).

MEF et les autres framework d’IoC (Unity, StuctureMap & co)

Bien que MEF assure des fonctionnalités comme on les retrouvent dans des frameworks d’IoC, MEF met surtout l’accent sur la découverte de composant : nous ne connaissons pas à l’avance les composants que l’on aura lors de l’exécution !

En trois mot, MEF : Extensibilité, Découverte et Composition.

Conclusion

Vous voilà désormais introduit au framework MEF. Si tout se passe normalement, MEF devrait être intégré dans la prochaine version de .NET, le framework .NET 4.0 qui devrait arriver fin d’année voir début d’année prochaine !

Vous pouvez retrouver actuellement MEF en version Preview 4 et en code ouvert sur la plateforme CodePlex à l’adresse http://www.codeplex.com/MEF/.

Bonne composition à tous ;)

[VS Extensibility] Part 3 : Quelques interactions avec Visual Studio

Jeudi 5 février 2009

Après avoir vu le fonctionnement d’un Add-In au sein de Visual Studio (voir part 1) et comment créer ses propres fenêtres « VS like » hébergeant nos UserControls .NET (voir part 2), nous allons voir dans cette 3ème partie quelques interactions possibles avec l’IDE.

C’est depuis notre objet DTE2 (le _applicationObject dans notre classe Connect) que nous avons la possibilité d’accéder à notre IDE depuis le code. Voyons tout cela plus détails avant d’illustrer quelques interactions avec la StatusBar, Propeties Window ou encore les Window Panes.

L’objet DTE

L’objet DTE2 (EnvDTE80.DTE2 ou anciennement EnvDTE.DTE) est l’objet de haut niveau du modèle objet de Visual Studio. C’est lui qui nous permet d’interagir avec l’IDE depuis notre code.

Nous nous en sommes déjà servi dans notre part 1 et 2 afin de créer des fenêtres VS (DTE2.Windows) ou encore de rajouter le bouton de notre Add-In dans le menu Tools (DTE2.Commands).

image

Je vous laisserai observer l’ensemble des membres de notre objet DTE2 que vous pourrez retrouver dans l’intégralité sur la MDSN à l’adresse : http://msdn.microsoft.com/en-us/library/envdte80.dte2_members(VS.80).aspx

Les membres qui nous intéresserons dans cet article :

  • ActiveDocument : permet de récupérer le document actuel.
  • StatusBar : représente la StatusBar de Visual Studio.
  • ItemOperations : permettant d’effectuer des opérations sur des éléments dans VS (ex: créer/ouvrir des documents)
  • Windows : permet d’accéder aux fenêtres de VS.

Informer sur l’état de notre Add-In dans la StatusBar

Sans rentrer dans les détails, nous nous servirons principalement de trois membres :

  • Clear : permet de reset la StatusBar
  • Progress : permet d’afficher une barre de progression dans la Status Bar
  • Text : défini le texte à afficher dans la Status Bar

Prenons pour ‘exemple de rajouter le code ci-dessus après la création de notre fenêtre (cf. Part 2) :

1
_applicationObject.StatusBar.Text = "MonAddinDemo lancé !";

Observez le résultat dans notre VS une fois notre Add-In lancé :

image

Nous avons aussi la possibilité d’afficher une barre de progression avec la méthode Progress. Exemple :

1
_applicationObject.StatusBar.Progress(true, "Etape 1", 33, 100);

Le 1er argument précise si l’on doit afficher la barre de progression. Le 2eme argument est le texte à afficher dans la StatusBar et les deux derniers arguments définissent l’état d’avancement de notre barre de progression (ici 33 pour 100 !).

Le résultat dans notre Visual Studio :

image

N’oubliez pas de rappeler la méthode Progress en précisant le 1er argument (InProgress) à false pour masquer la barre de progression une fois la tache terminée !

Ouvrir un site web avec les ItemOperations

Les ItemOperations vont permettre de manipuler l’équivalent des fenêtres des dialogues « Ajouter un élément » ou « Créer/Ouvrir un fichier ».

On retrouvera parmi les méthodes de cet objet :

Pour l’exemple nous allons simplement ouvrir un site web dans une nouvelle fenêtre. (Cela ressemblera étrangement à notre UC contenant notre Web Browser !)

1
2
_applicationObject.ItemOperations.Navigate("http://sebastien.warin.fr",
    vsNavigateOptions.vsNavigateOptionsNewWindow);

image

Écrire dans les Output Window Panes

Nous nous souvenons de l’objet EnvDTE80.Windows pour son CreateToolWindow qui nous avait servi dans notre Part 2 pour la création de fenêtre dans Visual Studio.

Nous avons aussi une méthode forte intéressante nommé Item ! Elle permet de récupérer une fenêtre (objet Window) à partir de son Guid. Pour nous aider, l’énumération EnvDTE.Constants contient tous les GUID des fenêtres principales de VS :

image

Nous allons dans notre exemple, créer un nouveau « Pane » dans la fenêtre de sortie (Output) de Visual Studio. Ajoutons le code ci-dessous :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private OutputWindowPane monWindowPane = null;
private void EcrireDansMonWindowPane(string texte)
{
    if (monWindowPane == null)
    {
        // Récupération de la fenetre Output (vsWindowKindOutput)
        var window = _applicationObject.Windows.Item(EnvDTE.Constants.vsWindowKindOutput) as Window2;
        var outputWindow = window.Object as OutputWindow;
        // Pour tous les Window Panes dans mon Output
        foreach (OutputWindowPane pane in outputWindow.OutputWindowPanes)
        {
            // Si il existe deja un pane nommée "Mon Addin Demo"
            if (pane.Name == "Mon Addin Demo")
            {
                // sauvegarde la référence dans monWindowPane
                monWindowPane = pane;
                break;
            }
        }
        // Si pas trouvé, on le crée (OutputWindowPanes.Add)
        if (monWindowPane == null)
            monWindowPane = outputWindow.OutputWindowPanes.Add("Mon Addin Demo");
    }
    // Activation du Pane
    monWindowPane.Activate();
    // Ecriture de l'heure et du texte dasn notre pane "Mon Addin Demo"
    monWindowPane.OutputString(string.Format("{0} : {1}\n", DateTime.Now.ToLongTimeString(), texte));
}

Cette méthode cherchera à récupérer notre « Pane » nommé « Mon Addin Demo » ou le créera si celui ci n’existe pas. Une fois récupéré, elle écrira le texte passé en paramètre ainsi que l’heure.

Ajoutons maintenant, après la création de notre fenêtre dans notre méthode OnConnection, le code ci-dessus pour tester notre méthode :

1
2
EcrireDansMonWindowPane("MonAddinDemo lancé");
EcrireDansMonWindowPane("Ceci est un test !");

Observons le résultat dans notre Visual Studio :

image

Afficher les propriétés d’un objet dans la Properties Window

La Properties Window, bien connu des développeurs, permet d’afficher les propriétés d’un objet dans une sorte de tableau (en WinForm on utilise le PropertyGrid pour reproduire le même rendu) :

image

Pour passer un objet à cette fenêtre nous utiliserons la méthode SetSelectionContainer en passant la référence à un tableau d’objet (object[]).

Pour cela nous allons créer un nouveau UserControl dans lequel nous placerons un bouton qui appellera le SetSelectionContainer pour afficher un objet de type Personne que nous créerons également :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public partial class UserControl1 : UserControl
{
    public class Personne
    {
        public string Nom { get; set; }
        public string Prenom { get; set; }
        public int Age { get; set; }
    }
 
    public Window Window { get; set; }
 
    public UserControl1()
    {
        InitializeComponent();
    }
 
    private void button1_Click(object sender, EventArgs e)
    {
        object[] tmpl = new object[] { new Personne() { Age=22, Prenom="Sebastien", Nom = "Warin"} };
        Window.SetSelectionContainer(ref tmpl);
    }
}

Dans notre classe Connect, l’appel à la méthode CreateToolWindow2 diffère un peu ! (N’oubliez pas de passer la référence de la Window créée à votre UC !)

1
2
3
4
5
6
7
8
9
10
11
object monUC = null;
var vsWindows = _applicationObject.Windows as Windows2;
// Creation de la fenetre VS
_maWindow = vsWindows.CreateToolWindow2(_addInInstance, Assembly.GetExecutingAssembly().Location,
    typeof(UserControl1).FullName, "Ma 1er fenetre VS", Guid.NewGuid().ToString("B"), ref monUC);
// On passe la ref de notre Window
(monUC as UserControl1).Window = _maWindow;
// Affichage de la fenetre VS
_maWindow.IsFloating = false;
_maWindow.Linkable = false;
_maWindow.Visible = true;

Le résultat dans Visual Studio :

image

Attention, la méthode SetSelectionContainer ne peut être appelée que par une fenêtre ayant été créée par un CreateToolWindow. C’est pour cela que nous passons l’objet _maWindow à notre UC et que notre UC se sert de cet objet pour appeler le SetSelectionContainer.

Nous aurions pu penser à récupérer la Properties Window pour appeler le SetSelectionContainer comme ci-dessous :

1
2
3
4
// Récuperation de la fenetre de Propriété
var propWin = DTE.Windows.Item(EnvDTE.Constants.vsWindowKindProperties) as Window2;
// Affichage de l'objet dans la Properties Window
propWin.SetSelectionContainer(ref tmpl);

Mais une exception se lèvera vous indiquant que cela n’est possible que par une fenêtre ayant été créée par CreateToolWindow :

1
2
System.Runtime.InteropServices.COMException was caught
   Message="Only tool windows created by Add-ins (Window.CreateToolWindow) can offer Selection"

Récupérer le document en cours : le ActiveDocument

Le ActiveDocument permet de récupérer le document en cours. Vous pouvez par exemple tester si le type du document en cours est de type « Text ». Si oui vous pourrez, grâce à la méthode Object() le récupérer sous forme un TextDocument. Vous pourrez ensuite interagir avec le contenu du document très facilement.

A ce sujet, en Février 2006 (il y a déjà 3 ans !!), je publiais sur mon blog une macro permettant de générer des propriétés très rapidement (http://sebastien.warin.fr/2006/02/07/7-macrosebgenererproprietes/).  La macro utilisait le DTE.ActiveDocument pour générer le code dans vos fichiers sources.

Pour notre exemple nous allons simplement afficher e texte sélectionné dans une MessageBox. Le code est :

1
2
3
4
5
if (_applicationObject.ActiveDocument.Type == "Text")
{
    var txtDoc = _applicationObject.ActiveDocument.Object("TextDocument") as TextDocument;
    MessageBox.Show(txtDoc.Selection.Text);
}

Exécutons notre projet, et ouvrons un fichier de type « Text » dans VS. Sélectionnons du texte au hasard et activons notre Add-In. Le résultat en image :

image

Conclusion

S’achève ainsi notre découverte des possibilités d’extension par Add-In de votre Visual Studio. Nous avons vu ici quelques interactions possibles avec celui-ci pour s’intégrer le plus possible dans l’IDE.

Nous verrons par la suite quelques tips (astuces) sur le packaging d’Add-In ou encore la personnalisation de l’icône de votre Add-In dans le menu Tools.

[VS Extensibility] Part 2 : créez vos fenêtres d’outils avec CreateToolWindow2 !

Mercredi 4 février 2009

image Continuons notre découverte sur développement d’add-ins pour Microsoft Visual Studio, l’IDE de prédilection pour les développements sur plateforme Microsoft/.NET. Dans la 1ere partie nous avons vu de quoi était constitué un add-in et comment il fonctionnait. Dans cette 2ème partie nous verrons comment créer des fenêtres d’outils dans Visual Studio.

Nous nous étions contenté, dans la partie 1, d’afficher une simple MessageBox lors du clique sur le bouton de notre add-in présent dans le menu Tools (ou Outils) de VS. Nous avons vu que la gestion du clique se faisait dans la méthode Exec (implémentée par l’interface IDTCommandTarget) quand l’argument commandName était égal au nom de notre commande (ex : commandName == « MonAddinDemo.Connect.MonAddinDemo »).

Mais un Add-In lançant une MessageBox n’est pas forcement très utile :) Nous avons plutôt besoin d’afficher notre propre fenêtre embarquant toute la GUI de notre Add-In.

Fenêtre Visual Studio ou simple WinForm ?

imageEt si à la place de notre MessageBox.Show() je faisais un new MaWinForm().Show() afin d’afficher une WinForm que j’aurais préalablement créée dans mon projet ?

La réponse serait que cela marcherai sans aucun problème, il en résulterait l’affichage notre propre fenêtre dans depuis Visual Studio.

Vous allez me dire « mais pourquoi écrire un article sur l’affichage d’une simple WinForm ? ». La réponse est ci-dessus :  un new MaWinForm().Show()  permet simplement d’afficher la fenêtre depuis VS. Il ne s’agit en aucun cas d’une fenêtre dans Visual Studio.

En d’autre terme vous ne bénéficiez pas du look&feel VS, vous ne pouvez pas non plus la docker dans l’IDE ou l’afficher en tant que document VS. Pour ce faire, il faut créer la fenêtre par Visual Studio.

D’ailleurs nous ne créerons pas de Winform mais un UserControl qui sera docké dans une fenêtre Visual Studio.

CreateToolWindow ou CreateToolWindow2 ?

La création d’une fenêtre par VS se réalise en appelant la méthode EnvDTE80.Windows2.CreateToolWindows2() ou EnvDTE.Windows.CreateToolWindow().

Avant de détailler son utilisation, nous voyons pourquoi y a t-il deux CreateToolWindow ?

On retrouve en fait deux namespaces presque identiques :

  • EnvDTE : une assembly « wrapper » vers le composant COM du Core Automation de Visual Studio (inchangée depuis VS2003).
  • EnDTE80 : une extension de EnvDTE apparut depuis VS 2005 (8.0) pour supporter de nouvelles fonctionnalités.

Dans le 1er, EnvDTE, on retrouve la classe Windows contenant la méthode CreateToolWindow.

Dans la 2ème, EnvDTE80, on retrouve la classe Windows2 contenant la méthode CreateToolWindow2. (note: on retrouve aussi CreateToolWindow dans Windows2).

Comme vous l’aurait comprit, CreateToolWindow est un peu obsolète par rapport à CreateToolWindow2. Dans des soucis de versionning, beaucoup de classes ou d’interfaces ont le suffixe « 2″ dans le namespace EnvDTE80 pour pouvoir les différencier avec ceux d’EnvDTE.

Voyons leurs signatures :

1
2
3
4
5
6
7
Window CreateToolWindow (
	AddIn AddInInst,
	string ProgID,
	string Caption,
	string GuidPosition,
	out Object DocObj
)
1
2
3
4
5
6
7
8
Window CreateToolWindow2 (
	AddIn Addin,
	string Assembly,
	string Class,
	string Caption,
	string GuidPosition,
	out Object ControlObject
)

Hormis le fait que les noms des paramètres sont un peu différents, nous remarquerons surtout l’apparition d’un nouveau paramètre de type string nommé Assembly.

Analysons les différents arguments que nous trouvons dans la méthode CreateToolWindow2 :

  • Addin (ex-AddInInst): l’instance de l’addin passée dans la méthode OnConnection (variable _AddInInst dans notre Connect).
  • Assembly : répertoire de notre assembly
  • Class (ex-ProgID) :  nom complet du UserControl à afficher dans la fenêtre VS.
  • Caption : titre de la fenêtre
  • GuidPosition : un GUID permettant d’ identifier la fenêtre.
  • ControlObject (ex-DocObj) : référence vers l’instance de votre usercontrol.

Dans les deux cas, ces méthodes retournent un objet Window représentant la fenêtre VS crée.

La principale différence entre les deux méthodes est qu’ anciennement nous affichons un ActiveX dans les fenêtres VS. Depuis VS2005 (et l’arrivé du namespace EnvDTE80) nous avons la possibilité de passer directement un UserControl .NET (d’où l’ajout de l’argument Assembly).

Vous pouvez encore utiliser la méthode CreateToolWindow mais il faudra marquer votre UC ComVisible ou enregistrer l’assembly pour l’interop. COM (dans les propriétés du projet). Développeurs .NET comme nous sommes, en 2009, je pense que nous pouvons oublier les anciennes versions de Visual Studio antérieures à 2005, le CreateToolWindow et les ActiveX au profit d’une méthode plus récente et plus sûre ;)

Attention, notez aussi qu’il faut absolument que votre UC soit dans la même assembly que votre Add-In (classe Connect). Sinon lors de l’appel de la méthode CreateToolWindow2, la référence vers votre UC (ControlObject) restera à null.

Cas concret : créer des fenêtres d’outils VS

Pour détailler cela, reprenons notre projet « MonAddinDemo » vu en partie 1, et ajoutons un UserControl nommé MaFenetre.

Pour l’exemple nous allons, pour faire très simple, créer un mini browser Web. Dans votre UserControl déposez un WebBrowser docké dans l’UC. Plaçons ensuite une méthode publique NavigateTo pour définir l’adresse du WebBrowser :

1
2
3
4
public void NavigateTo(string url)
{
	webBrowser1.Navigate(url);
}

Notre UC est prêt ! Voyons comment le lancer dans une fenêtre VS.

Pour cela reprenons notre classe Connect et dans la méthode Exec, ajoutons pour le clique de notre bouton le code ci-dessus :

1
2
3
4
5
6
7
8
9
10
object monBrowser = null;
var vsWindows = _applicationObject.Windows as Windows2;
// Creation de la fenetre VS de type "MaFenetre"
var maWindow = vsWindows.CreateToolWindow2(_addInInstance, Assembly.GetExecutingAssembly().Location,
   typeof(MaFenetre).FullName, "Ma 1er fenetre VS", "{EC7011CF-A49F-4f06-9C2D-7FF32511DE7F}", ref monBrowser);
// La ref. vers notre UC "MaFenetre" est dans la variable monBrowser !
// On le recupere pour appeler la méthode NavigateTo de ntore UC
(monBrowser as MaFenetre).NavigateTo("http://sebastien.warin.fr");
// Affichage de la fenetre VS
maWindow.Visible = true;

Le résultat devient alors « VS like » :)

image

Étant donné que nous avons désormais une fenêtre VS, nous avons la possibilité de la docker dans un endroit de notre IDE :

image image

Et même la possibilité de la définir en tant que Document VS :

image

Nous pouvons aussi, avant d’afficher notre fenêtre (Visible = true) définir des propriétés à notre fenêtre comme par exemple :

  • Linkable (booléen) : permet de définir si la fenêtre peut être dockée dans l’IDE comme vu ci-dessus.
  • IsFloating (booléen) : permet de définir si la fenêtre peut être « volante ».

Par exemple, pour créer la fenêtre sous forme d’un document VS comme le montre la dernière capture, nous écrirons :

1
2
3
4
// Affichage de la fenetre VS
maWindow.IsFloating = false;
maWindow.Linkable = false; 
maWindow.Visible = true;

GuidPosition dynamique : attention au Guid.ToString()

Le GUID que l’on passe à la méthode CreateToolWindow2 permet d’identifier de manière unique la fenêtre. Visual Studio se sert de cela pour mémoriser l’état et la position de la fenêtre.

Remarquez que si vous cliquez une deuxième fois sur votre add-in dans le menu Tools, aucune nouvelle fenêtre n’est créée. En fait la méthode CreateToolWindow2 vous renverra la même référence que lors du 1er appel. Cela est normal étant donné que nous avons mis en dur le GUID pour l’argument GuidPosition.

Dans certain cas nous souhaitons créer plusieurs fenêtres du même type. Par exemple nous pouvons imaginer d’avoir la possibilité de créer plusieurs fenêtres pour plusieurs sites web. Dans ce cas là il nous faut générer un GUID dynamiquement !

Nous allons donc penser à faire un :

1
Guid.NewGuid().ToString()

au niveau de l’argument GuidPosition.

Mais à l’execution nous aurons une erreur de type :

1
[System.Runtime.InteropServices.COMException] = {"Invalid class string (Exception from HRESULT: 0x800401F3 (CO_E_CLASSSTRING))"}

En effet VS attent un GUID au format {<guid>} or un ToString() sur un Guid n’inclut pas les { } !

Pour cela nous avons deux solutions :

  1. Faire de manière « peu propre », en concaténant :
    • 1
      
      "{" + Guid.NewGuid().ToString() + "}"
  2. Modifier le format du Guid (cf. MSDN) :
    • 1
      
      Guid.NewGuid().ToString("B")

Du fait si l’on remplace notre appel à CreateToolWindow2 par :

1
2
3
// Creation de la fenetre VS de type "MaFenetre"
var maWindow = vsWindows.CreateToolWindow2(_addInInstance, Assembly.GetExecutingAssembly().Location,
       typeof(MaFenetre).FullName, "Ma 1er fenetre VS", Guid.NewGuid().ToString("B"), ref monBrowser);

… dès que l’on clique plusieurs fois sur notre add-in, plusieurs fenêtres seront créées :

 image

Aller un peu plus loin dans la gestion de la fenêtre

La création des fenêtres dans la méthode Exec n’est pas « très propre ». Il est préférable de les créées dans la méthode OnConnection quand le connectMode est à Startup ou AfterStartup.

Dans notre dernière exemple nous allons permettre de créer une fenêtre volante (toujours de notre UC ‘MaFenetre’ contenant le WebBrowser vers mon site !).

Ajoutons tout d’abord une variable privée à notre classe Connect :

1
private Window _maWindow;

Dans notre méthode OnConnection nous avons actuellement le code permettant d’ajouter notre addin au menu Tools (comme vu en partie 1) lorsque le connectMode est à UISetup.

En dessus de cela ajoutons la création de notre fenêtre lorsque nous sommes à Sartup ou AfterStartup :

1
2
3
4
5
6
7
8
9
10
11
12
13
if (connectMode == ext_ConnectMode.ext_cm_AfterStartup || connectMode == ext_ConnectMode.ext_cm_Startup)
{
    object monBrowser = null;
    var vsWindows = _applicationObject.Windows as Windows2;
    // Creation de la fenetre VS de type "MaFenetre"
    _maWindow = vsWindows.CreateToolWindow2(_addInInstance, Assembly.GetExecutingAssembly().Location,
        typeof(MaFenetre).FullName, "Ma 1er fenetre VS", Guid.NewGuid().ToString("B"), ref monBrowser);
    // La ref. vers notre UC "MaFenetre" est dans la variable monBrowser !
    // On le recupere pour appeler la méthode NavigateTo de notre UC
    (monBrowser as MaFenetre).NavigateTo("http://sebastien.warin.fr");
    // Affichage de la fenetre VS
    _maWindow.Visible = true;
}

Dans notre méthode Exec maintenant, nous allons simplement nous contenter d’afficher _maWindow si celle ci n’est pas Visible. Voici le code complet de cette méthode : 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
{
    handled = false;
    if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
    {
        if (commandName == "MonAddinDemo.Connect.MonAddinDemo")
        {
            // Afficher seulement si elle n'est pas déjà visible
            if (_maWindow != null &amp;&amp; _maWindow.Visible == false)
                _maWindow.Visible = true;
            handled = true;
            return;
        }
    }
}

Pour terminer nous allons compléter la méthode QueryStatus, qui je le rappelle, définit l’état de notre bouton dans le menu Tools. Nous allons en effet griser le bouton si la fenêtre est déjà lancée. Voici le code complet de notre méthode QueryStatus :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText, ref vsCommandStatus status, ref object commandText)
{
    if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
    {
        if (commandName == "MonAddinDemo.Connect.MonAddinDemo")
        {
            if (_maWindow.Visible)
                status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported;
            else
                status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
            return;
        }
    }
}

Observez le résultat une fois la fenêtre affichée :

 image

Conclusion

Vous voilà maintenant capable de créer vos fenêtres dans Visual Studio, de les disposer dans l’IDE de manière volantes, dockées ou comme de simples documents ouverts dans Visual Studio. Dans chacune de ces fenêtres se trouve un UserControl .NET qui contiendra l’interface graphique de votre add-in. En passant l’objet DTE à votre UC, vous avez tout à votre disposition pour interagir avec l’IDE depuis celui-ci.

Dans une troisième partie nous illustrerons quelques interactions possibles avec l’IDE comme par les Window Panes, la Status Bar ou encore la Properties Window.