Création d'un Addin pour Visual Studio .Net

Tutoriel pas à pas expliquant le principe, le développement et l'intégration d'un addin dans Visual Studio .Net

N'hésitez pas à commenter cet article ! Commentez Donner une note à l'article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

Avant-Propos

Visual Studio .Net est connu pour être le logiciel le plus complet du marché du développement. Non content de permettre le developpement d'un grand nombre de langages, il permet également l'utilisation des dernières technologies et fournit une interface intuitive et productive, facilitant le développement de toutes sortes de projets. Que vous developpiez un utilitaire, un logiciel de gestion, un progiciel, une interface avec une base de données, ou autre, Visual Studio .Net vous facilite les choses de nombreuses manières.
Néanmoins, malgré une évolution importante et constante (voir la prochaine version: Whidbey), Visual Studio .Net ne contient peut-être pas la fonction de vos rêves: celle qui vous permettrait de vous simplifier le travail et/ou réduire le temps de développement. Pourquoi alors ne pas prendre quelques heures pour développer son propre add-in pour, par la suite, gagner énormément de temps?
C'est ce que je vous propose ici de faire et nous allons donc voir comment développer son add-in pour Visual Studio .Net.

Notez que nous créerons un "add-in" et non pas un "add-on" car il sera totalement intégré à Visual Studio .Net et interagira directement avec lui. Dans ce tutoriel, j'appelerai également notre add-in un complément car c'est ainsi que l'on nomme un add-in qui ne fait pas partie des "assistants" de Visual Studio .Net (templates de projet, wizard,etc).

1. Création du projet

1.1. Introduction

Un add-in est une partie complémentaire d'un logiciel de base, il peut être sous la forme d'un menu principal, un menu contextuel ou même une fonction d'arrière plan. Ici, nous verrons le fonctionnement et la création de complément, puis nous verrons le développement d'un add-in permettant de fournir un rapport détaillé du projet en cours (arborescence, statistiques). Puis, dans un deuxième temps, nous verrons l'interaction d'un add-in avec les fichiers code via un menu contextuel.
Vous aurez, à la fin de ce cours, vu plusieurs des différentes manières de s'intégrer dans Visual Studio et interagir avec lui.

1.2. Le projet de complément

Lancez Visual Studio .Net puis cliquez sur le menu Fichier > Nouveau > Projet.
Dans la fenêtre qui s'ouvre, choisissez alors Autres Projets > Projets d'Extensibilité puis un complément de Visual Studio

Image non disponible


Donnez alors un nom à votre projet. Puis cliquez sur OK. Un nouveau panneau s'affiche alors.

Image non disponible


Cliquez sur suivant.

Image non disponible


Ici, nous développerons un add-in en C#, choisissez donc:
Créer un complément à l'aide de Visual C# (libre à vous de le faire dans un autre langage par la suite)

Image non disponible


Choisissez maintenant, où vous souhaitez que votre plugin s'intègre. Simplement dans l'ide Microsoft Visual .Net, dans l'éditeur de Macro ou dans les deux. L'éditeur de macros est un deuxième IDE servant à l'édition de macros principalement en VBA

Image non disponible


Choississez le nom de votre add-in (le nom sous lequel il apparaitra dans la liste des add-ins) ainsi qu'une rapide description.

Image non disponible


Vous pouvez alors personnaliser votre add-in:
Choisissez d'abord si vous souhaitez que votre add-in soit accessible via le menu outils. (note: vous pouvez ne pas la cocher et par la suite, je montrerai comment rajouter des menus. Mais la cocher autogénère le code qu'il est plus facile de modifier)
Choisissez si votre add-in pourra avoir une interface graphique ou non, puis choisissez s'il se charge directement au lancement de Visual Studio .Net ou non.
Enfin, cochez la dernière case si vous souhaitez qu'il soit intégré à Visual Studio .Net pour n'importe quelle personne utilisant l'ordinateur sur lequel il sera installé.

Image non disponible


Ces informations apparaîtront dans les détails de l'add-in dans la fenêtre "A propos" de l'application hôte (ici Visual Studio .Net)

Image non disponible


Le panneau récapitulatif. Cliquez alors sur Terminer et Visual Studio .Net se charge se générer automatiquement tout le squelette de votre add-in.

Image non disponible


Vous pouvez alors noter que toute la structure de votre add-in est déjà générée et commentée. Cette structure ne doit pas être modifiée car elle doit suivre certaines restrictions pour s'adapter à Visual Studio et possède certaines méthodes qui lui sont nécessaires pour fonctionner. Voyons maintenant le fonctionnement d'un add-in

2. Fonctionnement d'un add-in

Avant d'aller plus loin dans le développement de notre add-in, il est important de comprendre sa structure, tant au niveau de l'arborescence des fichiers et de la nomenclature de ceux-ci, que du code structuré et lui aussi nomenclaturé.

2.1. Structure d'un add-in

Regardons tout d'abord la structure du projet:

Image non disponible


Notons tout d'abord le fichier assembly.cs qui contient l'assembly de notre projet et qu'il nous faudra remplir (nom, description, version) par la suite. Notons ensuite le très important connect.cs. Ce fichier est obligatoire, il sert en sorte de super constructeur: c'est la classe qui est appelée en premier lieu lors du chargement de l'add-in. Il est indispensable de ne pas le renommer et c'est dans ce dernier que ce retrouvera une grosse partie du code fonctionnel à moins que vous préfériez (en fonction de la taille de l'add-in) séparer les fonctions dans diverses classes.
Notez également que Visual Studio Projet a automatiquement généré un projet de déploiement (setup) pour pouvoir redistribuer facilement l'add-in.

Ouvrons maintenant le fichier connect.cs:
Tout d'abord, le namespace. Même s'il est crée automatiquement il ne faut pas le modifier sous peine de voir arriver des problèmes avec les autres classes du projet.

 
Sélectionnez

namespace NarfAddin
                   


Puis l'utilisation d'autres namespaces permettant d'appeler des fonctions.

 
Sélectionnez

using System;
using Microsoft.Office.Core;
using Extensibility;
using System.Runtime.InteropServices;
using EnvDTE;


Puis vient un mini-readme qui explique que pour de nombreux cas, l'intégration de l'add-in peut ne plus se faire(bug reconnu :D). Le premier reflexe serait de le supprimer, néanmoins je vous conseille de le garder, au moins pour vous rappeler qu'il vous suffit d'executer le fichier .reg qui se trouve dans le dossier du projet pour "réparer" l'add-in.

 
Sélectionnez

#region Read me for Add-in installation and setup information.
// When run, the Add-in wizard prepared the registry for the Add-in.
// At a later time, if the Add-in becomes unavailable for reasons such as:
// 1) You moved this project to a computer other than which is was originally created on.
// 2) You chose 'Yes' when presented with a message asking if you wish to remove the Add-in.
// 3) Registry corruption.
// you will need to re-register the Add-in by building the MyAddin21Setup project
// by right clicking the project in the Solution Explorer, then choosing install.
#endregion


Voici ensuite notre classe principale intégrant des interfaces spécifiques à l'interaction: Extensibility.IDTExtensibility2, IDTCommandTarget. Notez aussi l'attribution automatique d'un GUID (unique) à notre add-in.

 
Sélectionnez

[GuidAttribute("A8989C04-130B-4F30-913C-7ED1BF6107B8"), ProgId("NarfAddin.Connect")]
public class Connect : Object, Extensibility.IDTExtensibility2, IDTCommandTarget


Nous avons ensuite le constructeur. C'est dans celui-ci que nous pouvons mettre notre code d'initialisation (que nous verrons par la suite)

constructeur
Sélectionnez

public Connect()
{
}


Celui-ci est vide mais comme tout constructeur il est forcément appelé et sert à mettre votre code d'initialisation, comme toute application normale.
Nous allons maintenant voir ce qu'il lui permet de s'intégrer dans Visual Studio .Net.

Un add-in utilise l'interface IDTExtensibility2 qui fournit 5 évènements de base et qui correspondent aux événements courants d'un add-in. Ces méthodes permettent à un programme de jouer le rôle de complément en répondant à des conditions de démarrage et d'arrêt de l'environnement, des événements de connexion de complément, etc.

L'évènement OnConnection est declenché lorsque l'add-in est chargé (connecté). Un add-in se charge dans différents cas:
- l'utilisateur charge l'add-in via la boite de dialogue des Add-ins
- l'add-in est chargé en même temps que l'appli, lorsqu'il a été configuré pour démarrer avec celle-ci
- la propriété Connect d'un objet COMAddIn correspondant est réglé sur : True. L'objet COMAddIn représente un add-in COM dans l'application Office

méthode OnConnection
Sélectionnez

public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, 
ref System.Array custom)
{
}


L'évènement OnDisconnection est déclenché lorsque l'add-in est déchargé. Il peut l'être de deux manières.
- l'utilisateur le décharge via la fenêtre des Add-ins
- l'application hôte est fermée, donc l'add-in aussi par la même occasion
Astuce: Si vous souhaitez que les actions faites par l'add-in dans l'application soient annulées pour le prochaine démarrage (recherche, autre), vous pouvez mettre le code de nettoyage dans cette méthode.

méthode OnDisconnection
Sélectionnez

public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)
{
}


L'évènements OnAddinsUpdate est déclenché lorsque la liste du Gestionnaire de compléments est modifiée; lorsqu'un add-in est chargé ou déchargé.

méthode OnAddInsUpdate
Sélectionnez

public void OnAddInsUpdate(ref System.Array custom)
{
}


L'évènement OnStartupComplete est déclenché lorsque l'add-in et l'application hôte ont fini de charger toutes leurs routines de démarrage. Si l'application est chargée complètement mais si l'add-in n'est pas chargé au démarrage alors cet évènement ne s'effectue pas, même si par la suite, l'add-in est chargé via la boite de dialogue des add-ins. Vous pouvez utiliser l'évènements OnStartupComplete pour interagir avec l'application. Par exemple, proposez des choix à l'utilisateur au démarrage de visual studio pour créer différents documents.

méthode OnStartupComplete
Sélectionnez

public void OnStartupComplete(ref System.Array custom)
{
}


L'évènement OnBeginShutdown est déclenché lorsque l'application hôte lance sa routine d'extinction. Cet évènement est utile pour agir avant que l'application ne se ferme (autosave par exemple).

méthode OnBeginShutdown
Sélectionnez

public void OnBeginShutdown(ref System.Array custom)
{
}


Un add-in contient également deux méthodes de base:
La première méthode: QueryStatus() est appelée par l'IDE pour vérifier si une commande est appropriée ou non dans l'état courrant.

méthode QueryStatus
Sélectionnez

public void QueryStatus(string commandName, EnvDTE.vsCommandStatusTextWanted neededText, 
ref EnvDTE.vsCommandStatus status, ref object commandText)
{
}


La méthode Exec est elle, appelée par l'IDE pour effectuer la commande courrante. (en fonction du menu choisi par exemple)

méthode Exec
Sélectionnez

public void Exec(string commandName, EnvDTE.vsCommandExecOption executeOption, ref object varIn, 
ref object varOut, ref bool handled)
{

}


Plus clairement, ces deux méthodes sont utilisées pour l'une pour interagir avant l'execution d'une commande, pour voir si le menu est valide dans un cas précis, l'autre pour lancer les commandes en fonction du menu sélectionné.

2.2. Intégration dans Visual Studio .Net


Un add-in est un petit programme intégré dans un second programme (ici dans Visual Studio) et peut s'intégrer de 2 manières.
La première, invisible, peut-être un utilitaire qui se lance en arrière-plan à chaque démarrage de Visual Studio.
La deuxième, se vérifie par la présence d'un menu ou plusieurs menus.
Pour un add-in qui tourne en arrière plan, il suffit simplement d'ajouter le code fonctionnel dans la méthode OnConnection et ainsi l'add-in sera lancé (selon vos options) avec Visual Studio .Net et débutera directement son fonctionnement.
Mais pour des raisons fonctionnelles et comme nous verrons les cas des deux chapitres suivants, il peut être interessant ou tout simplement nécessaire d'ajouter des menus à l'environnement de Visual Studio afin de récupérer les choix de l'utilisateur.
L'ajout d'un menu se fait toujours de la même manière: on crée un objet Command (qui représente le menu) et que l'on ajoute dans une CommandBar (menu déjà existant).

création d'un menu dans la méthode OnConnection
Sélectionnez

applicationObject = (_DTE)application;
addInInstance = (AddIn)addInInst;

if(connectMode == Extensibility.ext_ConnectMode.ext_cm_UISetup)
{
  object []contextGUIDS = new object[] { };
  Commands commands = applicationObject.Commands;
  _CommandBars commandBars = applicationObject.CommandBars;
  try
  {
    CommandBar commandBar = (CommandBar)commandBars["Code Window"];
    Command command = commands.AddNamedCommand(addInInstance, 
	                                           "NomUniqueDeMonMenu", 
	                                           "Texte du menu ", 
	                                           "Tooltip de mon menu ", 
	                                           true, 
	                                           1, 
	                                           ref contextGUIDS, 
	                                           (int)vsCommandStatus.vsCommandStatusSupported+
                                               (int)vsCommandStatus.vsCommandStatusEnabled);
    CommandBarControl commandBarControl = command.AddControl(commandBar, 1); // j'ajoute mon menu à la barre d'outils
  }
  catch
  {
  }
}


Reprenons la structure de l'ajout d'un menu via la méthode AddNamedCommand( ):

paramètres de création d'un menu
Sélectionnez

commands.AddNamedCommand(
addInInstance,    // Instance de notre Add-in (autogénéré pas VS et se trouvant au debut de OnConnection( )
"NomMenu",        // Nom unique du menu. Donnera : Addin.progid.<le nom choisi>
"Texte du menu",  // Texte du menu (le texte affiché :D)
"Tooltip",        // Tooltip pour donner plus d'explications
true,             // si le menu est un MSOButton ou non (bouton utilisant une image office)
1,                // id d'un bitmap dans les ressources Office (car true). Image dessinée devant le menu
ref contextGUIDS, // contextes d'environnement (mode débogage, mode design, etc.) activent la commande
(int)blabla....   // flags d'activation ou non de la commande
)


Si vous souhaitez lancer telle ou telle action en fonction du menu sélectionné (dans le cas de plusieurs menus pour le même add-in) il vous faudra le faire dans la méthode Exec( ) où le menu sera représenté par NomAddin.Connect.NomMenu.

exemple
Sélectionnez

public void Exec(string commandName, EnvDTE.vsCommandExecOption executeOption, ref object varIn, 
ref object varOut, ref bool handled)
{
  if(commandName == "MonAddin.Connect.Menu1")
  {
    // je fais l'action du Menu1
  }
  if(commandName == "MonAddin.Connect.Menu2")
  {
    // je fais l'action du Menu2
  }
}


Dernière chose: selon certains cas, pour mieux contrôler votre add-in, vous devrez désactiver un ou plusieurs menus.
Cela se fait généralement dans la méthode QueryStatus qui retourne (ou définit) le status d'un menu avant qu'il ne soit sélectionné (apparition du menu contextuel par exemple). status y represente l'état du menu en question:

 
Sélectionnez

 // activation
   status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
 // désactivation
   status = (vsCommandStatus)vsCommandStatus.vsCommandStatusUnsupported;

2.3. Interaction avec Visual Studio .Net

Visual Studio .Net contient une interface acceptant des compléments et propose également des méthodes pour agir avec lui. Il propose également certaines variables permettant d'accéder à un certain nombre d'informations de Visual Studio ou des projets qu'il contient. Voici quelques exemples de ces variables:

Nous avons tout d'abord, la variable de type _DTE qui représente Visual Studio en lui même. Elle est toujours déclarée ainsi

 
Sélectionnez

private _DTE applicationObject;


et se définit de la sorte:

 
Sélectionnez

applicationObject = (_DTE)application;

ApplicationObject vous fournira alors plusieurs méthodes permettant soit de récupérer des informations sur Visual Studio (projets chargés, options de ligne de commande) soit de modifier son environnement (afficher/cacher des barres, desactiver les tooltip, etc...)

Il existe également la variable de type Addin

 
Sélectionnez

private AddIn addInInstance;
addInInstance = (AddIn)addInInst;

Vous pourrez ainsi récupérer quelques informations sur l'addin en lui même comme par exemple, le chemin de sa dll(son chemin d'installation).
Ces deux objets vous permettre d'agir totalement sur Visual Studio, vous pouvez aller de la modification de son interface à son utilisation. par exemple, il est possible d'utiliser la fenetre de sortie pour y afficher du texte ou utiliser la progressbar de la barre de status pour y décrire l'avancement d'une action de votre complément:

 
Sélectionnez

applicationObject.StatusBar.Progress(parametres);

3. Add-in : Project Manager

3.1. Description

Un article étant plus parlant avec un exemple concret, nous allons donc voir le code complet d'un petit add-in. Dans ce chapitre, nous ferons un add-in avec une interface graphique permettant de faire des exports des informations du projet (arborescence, statistiques,etc).

3.2. Développement de l'interface de l'add-in

Pour cet add-in, nous aurons besoin d'une interface utilisateur bien plus développée que le simple menu dans outils. Créons donc un nouvelle fenêtre.
Dans l'explorateur de solution, faites bouton droit > ajouter > ajouter un formulaire windows.
Vous pouvez alors donner l'interface graphique que vous souhaitez. De mon côté, j'ai fait celle-ci pour qu'elle soit la plus simple et fonctionnelle possible, mais vous pouvez faire autrement, il vous faudra juste quelque peu éditer le code.

Image non disponible

Vous pouvez remarquer les futures options à implémenter: un export dans un dossier choisi contenant
- un fichier de rapport (arborescence, infos, détails)
- une copie du projet
- tout en gérant plusieurs langages(C#, VB.Net,etc) de projets

3.3. Développement du code fonctionnel

Il ne m'est pas possible ici de montrer entièrement le code de l'add-in et je ne montrerai que les parties du code que j'estime interessantes pour le fonctionnement de l'add-in ou pour leur "difficulté" relative. Néanmoins, je fourni les sources complètes et commentées à la fin de ce chapitre

La première chose dans notre add-in pour qu'il puisse fonctionner est de récupérer la liste de tous les projets chargés.

Récupération de la liste des projets chargés
Sélectionnez

System.Array theProjects = (System.Array)applicationObject.ActiveSolutionProjects;


Puis, pour ne travailler que sur un projet (dans mon cas d'exemple), je crée un objet représentant mon projet.

 
Sélectionnez

EnvDTE.Project theProject = null;
// On vérifie que la solution contient au moins un projet
if (theProjects.Length > 0)
{
  theProject = (EnvDTE.Project)(theProjects.GetValue(0)); // je récupère uniquement le premier projet
  // voir la source pour la gestion de plusieurs projets
}


Pour la suite, il nous faudra créer notre fichier d'export puis un streamWriter dessus qui, au long des différentes méthodes, permettra de le compléter.

 
Sélectionnez

FileInfo f= new FileInfo(theProject.FullName);
StreamWriter strW = new StreamWriter(TB_Chemin.Text+"\\[export] "+ f.Name.Substring(0,f.Name.Length-7) +".htm");

J'ai ici choisi délibérément de travailler sur le fichier du projet, puis formater son nom après car le FileInfo nous servira dans de nombreux cas par la suite. Dans d'autres types de projet, pour avoir le nom du projet, il est plus simple d'utiliser :

 
Sélectionnez

applicationObject.Solution.FileName


Nous allons maintenant récupérer la liste des objets du projet SANS passer par un listing du dossier afin d'avoir un listing propre ne contenant pas les fichiers "extérieurs", présents dans le dossier.

 
Sélectionnez

foreach (ProjectItem p in theProject.ProjectItems)
{
  try
  {
    Compteur=Compteur+1; // compteur de fichier

    FileInfo Fichier=new FileInfo(f.DirectoryName+"\\"+p.Name);
    string taille=(CKB_Det.Checked)?" ("+ FormatSize(Fichier.Length)+")":""; // formattage (ko,Mo,etc)
    strW.WriteLine("<FONT class=content>- "+p.Name+ taille+"</FONT><br>"); // j'écris le nom du fichier et sa taille

    // On vérifie que les fichiers correspondent au type d'export souhaité
    if (Fichier.Extension == sFiltre) // le filtre étant récupéré dans un switch en fonction de la combobox du langage
    {
      SpecCompteur = SpecCompteur+1; // compteur de fichier code
      TotLiCompteur=TotLiCompteur+CompteLigne(f.DirectoryName+"\\"+p.Name); // appel de la fonction CompteLigne( )
    }
  }
  catch{}
}


Voici le code de la fonction CompteLigne( ) retournant pour le fichier passé en paramètre, le nombre de lignes de ce fichier. On ne peut malheuresement pas "deviner" le nombre de linges d'un fichiers sans compter les lignes une par une. Heuresement, ce traitement reste très rapide.

 
Sélectionnez

#region Fonction CompteLigne
/// <summary>
/// Fonction retournant le nombre de lignes du fichier passé en paramètre.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
private int CompteLigne(string path)
{
   StreamReader srFile = new StreamReader(path);    // création d'un streamreader sur le fichier de code
   string strLine=string.Empty;                                    // variable contenant la ligne courrante
   int LiCompteur=0;                                                     // compteur de lignes
   while ((strLine = srFile.ReadLine()) != null)
   {
      LiCompteur++;
   }
   srFile.Close();                                                            // je ferme le streamreader
   return LiCompteur;                                                 // je retourne le nombre de lignes de ce fichier
}
#endregion


Voici maintenant une petite fonction qui va redessinée un arbre avec images représentant l'arborescence complète du projet. La récusivité étant nécessaire, il fallait ici représenter les différents niveaux de l'arborescence, de travailler sur les fichiers et les dossiers, et le tout en un minimum de ligne.

 
Sélectionnez

#region Fonction Arbo
/// <summary>
/// Génère un listing hiérachique à partir d'un dossier root
/// </summary>
/// <param name="path">string</param>
/// <param name="strW">StreamWriter</param>
/// <param name="count">int</param>
private void Arbo(string path,StreamWriter strW,int count)
{
  DirectoryInfo di = new DirectoryInfo(path);
  string tmp=string.Empty;
  for(int i=0;i < count; i++) tmp= tmp+"";
  strW.WriteLine(tmp+"<img src=data/folder.ico ><FONT class=content>"+di.Name+"</FONT><br>");
  tmp= string.Empty;
  for(int i=0;i < count+2; i++) tmp= tmp+"";
  foreach(FileInfo fi in di.GetFiles())
  {
    strW.WriteLine(tmp +"<img src=data/file.ico ><FONT class=content>" +fi.Name+"</FONT><br>");
  }
  foreach(DirectoryInfo dii in di.GetDirectories())
  {
    Arbo(dii.FullName,strW,count+2); // appel récursif avec nouveaux paramètres
  }
}
#endregion


Maintenant il nous faut créer une fonction qui fera une copie des fichiers sources. Malheuresement, il n'existe pas encore de méthode permettant la copie automatique d'un dossier et de son contenu, il faut donc travailler sur chaque fichier tout en redessinant la même arborescence et une fois encore, en utilisant une seule fonction récursive.

 
Sélectionnez

#region Fonction Backup
/// <summary>
/// Crée un dossier contenant les sources du projet
/// </summary>
/// <param name="path"></param>
/// <param name="Addpath"></param>
private void Backup(string path, string AddPath)
{
  DirectoryInfo di = new DirectoryInfo(TB_Chemin.Text + "\\" + AddPath);
  di.Create();
  DirectoryInfo diSource =new DirectoryInfo(path);
  foreach(FileInfo fi in diSource.GetFiles())
  {
    try
    {
      fi.CopyTo(di.FullName+"\\"+fi.Name, true);
    }
    catch{};
  }
  foreach(DirectoryInfo dii in diSource.GetDirectories())
  {
    Backup(dii.FullName,AddPath+"\\"+ dii.Name);
  }
}
#endregion


Toutes les fonctions sont maintenantprêtes, il reste à gérer l'utilisation de celle-ci en fonction des choix de l'utilisateur (checkbox), vérifier d'un chemin d'export est bien renseigné, gérer la possibilité de caractères spéciaux.
Cliquez ici pour voir un rapport type tel qu'il sort de l'add-in.

Petit ajout: l'add-in étant présent dès le chargement de Visual Studio .Net et celui-ci ne contenant pas forcément à ce moment, de projet (ou solution) chargé. Il est donc nécessaire de vérifier à un moment donné la présence de ce projet (cette solution) afin de désactiver les fonctions de l'add-in pour éviter des comportements non voulus. Pour simplifier ce traitement, j'ai placé le code de vérification dans le code de chargement de la form principale.

Note: il eut été plus propre de le mettre dans la fonction Exec( ) de la classe connect.cs.

 
Sélectionnez

// on vérifie qu'une solution a été chargée
System.Array theProjects = (System.Array)applicationObject.ActiveSolutionProjects;
if(theProjects.Length <= 0)// Si la solution courante ne contient pas de projet
{
  MessageBox.Show("Aucun projet ou solution n'est actuellement chargé \n L'add-in va alors se fermer");
  this.Close();
}

Vous voilà en possession de votre premier add-in fonctionnel. Vous pouvez en télecharger les sources à la fin de ce cours.

4. Add-in : Code Improver

4.1. Description

Ici encore, nous verrons un autre exemple concret avec un add-in: code Improver, permettant d'agir directement sur le code. Il contiendra tout d'abord, une option permettant d'insérer un entête sur un fichier (comprenant nom de l'auteur et date de création), ainsi qu'une option pour ajouter les balises #region #endregion autour d'une sélection.
En plus, de voir comment travailler sur les fichiers ouverts à même Visual Studio .Net, nous verrons également l'intégration de l'add-in dans un menu contextuel spécifique.

4.2. Intégration de l'add-in

Comme pour notre premier add-in, nous allons rajouter des menus mais cette fois, nous allons le placer de façon à ce qu'il ne soit accessible que dans la fenêtre code. Pour cela, il nous suffit juste de placer notre menu dans une barre d'outil réservée au code: la barre "CodeWindow".

Création des menus
Sélectionnez

#region OnConnection
public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, 
object addInInst, ref System.Array custom)
{
	applicationObject = (_DTE)application;
	addInInstance = (AddIn)addInInst;
	object []contextGUIDS = new object[] { };        // ref vide mais necessaires
	Commands commands = applicationObject.Commands;  // représente les menus de visual studio
	_CommandBars commandBars = applicationObject.CommandBars; // représente les barres de menu de visual studio

	// Création des menus
	try
	{ 
        CommandBar commandBar = (CommandBar)commandBars["Code Window"];  // menu contextuel des pages de code
        Command command = commands.AddNamedCommand(addInInstance, 
                                                   "AjoutRegione", 
                                                   "Ajouter une region", 
                                                   "Ajoute une region autour de la selection", 
                                                   true, 
                                                   1554, 
                                                   ref contextGUIDS, 
                                                   (int)vsCommandStatus.vsCommandStatusSupported+
                                                   (int)vsCommandStatus.vsCommandStatusEnabled);
        CommandBarControl commandBarControl = command.AddControl(commandBar, 1); // j'ajoute le menu au menu contextuel
        command = commands.AddNamedCommand(addInInstance, 
                                           "AjouterHeadere", 
                                           "Ajouter un entête",
                                           "Ajoute un entête en haut du document", 
                                           true, 
                                           1555, 
                                           ref contextGUIDS, 
                                           (int)vsCommandStatus.vsCommandStatusSupported+
                                           (int)vsCommandStatus.vsCommandStatusEnabled);
        commandBarControl = command.AddControl(commandBar, 2); // j'ajoute le menu au menu contextuel
	}
	catch(System.Exception e)
	{  
	}
}


Une fois le menu crée de cette façon, nous avons la garantie qu'il n'apparaîtra que dans la fenêtre de code, pas dans celle de design ni dans l'environnement de Visual Studio. Néanmoins, dans le cas de notre add-in, il nous reste deux vérifications à faire.
La première, est de s'assurer que le code sur lequel se trouve le curseur est du C#. En effet, c'est le seul qui accepte les balises #region #endregion(VB.NET utilisant #Region et #End Region). La vérification peut se faire soit en allant vérifier l'extension du fichier soit plus facilement grâce au variables de Visual Studio:

 
Sélectionnez

if(applicationObject.ActiveDocument.Language.Equals("CSharp"))


La deuxième est de vérifier qu'une selection existe déjà. En effet, notre add-in se contentra de mettre des balises #region avec titre autour du texte sélectionné; chose inutile si aucun texte n'est sélectionné. La vérification se fait de cette manère.

 
Sélectionnez

TextSelection LaSelectionCourrante = applicationObject.ActiveDocument.Selection as TextSelection;
                if(LaSelectionCourrante.Text.Length > 0)


Ce qui donne au final:

préparation des menus
Sélectionnez

#region QueryStatus
public void QueryStatus(string commandName, EnvDTE.vsCommandStatusTextWanted neededText, 
ref EnvDTE.vsCommandStatus status, ref object commandText)
{
   if(neededText == EnvDTE.vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
   {
        if(commandName == "RegionsAddIn.Connect.AjoutRegion")
        {
            // Pour éviter des problemes avec les langages (c++,etc) 
			// nous devons donc désactiver ce menu si le code n'est pas du C#
            if(applicationObject.ActiveDocument.Language.Equals("CSharp"))
            {
                // Nous devons maintenant vérifier si un texte est actuellement sélectionné
                TextSelection LaSelectionCourrante = applicationObject.ActiveDocument.Selection as TextSelection;
                if(LaSelectionCourrante.Text.Length > 0)
                {
                   // Confirmation: on peut utiliser le menu
                   status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported 
				   | vsCommandStatus.vsCommandStatusEnabled;
                   // Note: j'active et rajoute ("supported") le menu au cas  précédement il eut été supprimé
                 }
                 else status=(vsCommandStatus)vsCommandStatus.vsCommandStatusUnsupported;
            }
            else
            {
                 // désactivation du menu 
                 status=(vsCommandStatus)vsCommandStatus.vsCommandStatusUnsupported;
                 //Note: il n'est pas possible de "disabled" le menu. nous ne pouvons 
				 //      que le retirer ou le rendre invisible.
            }			
         }
   }
}
#endregion


Enfin pour finir l'intégration, il nous restera à developper une Form qui permettra à l'utilisateur de spécifier le titre de la balise #region

Image non disponible

4.3. Développement du code fonctionnel

Pour ajouter les balises regions autour de notre selection, la méthode la plus simple est de modifier directement la selection.
Pour cela, il nous suffit d'y insérer au début ou la fin selon les flags, le texte désiré. Ce que fait le code suivant:

 
Sélectionnez

TextDocument doc = applicationObject.ActiveDocument.Object("TextDocument") as TextDocument;
// code amélioré grace aux sources de JW THOMAS (au niveau de detection de debut de ligne)

//Si notre selection de ne commence pas en début de ligne
// il nous faut rajouter au retour à la ligne avant notre #region
if(!doc.Selection.TopPoint.AtStartOfLine)
{
  doc.Selection.Insert("\r\n#region " + TB_RegionName.Text + "\r\n", (int)vsInsertFlags.vsInsertFlagsInsertAtStart);
  int selelectionStartLine = doc.Selection.TopLine + 1;
  int selelectionEndLine = doc.Selection.BottomPoint.Line;
  int selelectionEndColumn = doc.Selection.BottomPoint.LineCharOffset;
  doc.Selection.MoveToLineAndOffset(selelectionStartLine, 1, false);
  doc.Selection.MoveToLineAndOffset(selelectionEndLine, selelectionEndColumn, true); 
  // on place le curseur à la fin de la selection
}
else doc.Selection.Insert("#region " + TB_RegionName.Text + "\r\n", (int)vsInsertFlags.vsInsertFlagsInsertAtStart);
            
// Si la selection ne fini pas par un retour à la ligne
if(!doc.Selection.Text.EndsWith("\r\n"))
  doc.Selection.Insert("\r\n#endregion\r\n", (int)vsInsertFlags.vsInsertFlagsInsertAtEnd);
else
  doc.Selection.Insert("endregion\r\n", (int)vsInsertFlags.vsInsertFlagsInsertAtEnd);



Maintenant, nous allons créer un entête dynamique en haut du fichier code, ce qui va nous permmettre de voir une seconde approche de l'édition de fichier dans Visual Studio
Cela se déroule en 3 étapes:
-1- Récupérer le nom de l'auteur et la date

création de l'entête
Sélectionnez

// RegistryKey reg = Registry.CurrentUser.OpenSubKey(applicationobject.RegistryRoot,true);
// string szAuthor = reg.GetValue("UserName").ToString();
// tout est personnalisable, on pourrait y ajouter le nom du projet
szAuthor="MORAND Louis-Guillaume;
string szHeader= "/**************************  \r\n"
               + "/* auteur: "+ szAuthor +"\r\n"
               + "/* date de création: " +DateTime.Now.ToShortDateString()
               + "/*************************";


-2- Récupérer et créer un point de modification (EditPoint) au début du document

Création de l'EditPoint
Sélectionnez

TextDocument doc = applicationObject.ActiveDocument.Object("TextDocument") as TextDocument;
EditPoint edtPnt=doc.StartPoint.CreateEditPoint();


-3- Insérer notre entête à cet endroit

insertion de l'entête
Sélectionnez

edtPnt.Insert(szHeader);


Vous possédez maintenant un add-in permettant de rapidement écrire les zones regions ainsi qu'un entête. Il est possible de modifier son fonctionnement pour d'autres usages: insérer des TryCatch, ajouter des commentaires ou une license, etc.
Il ne nous reste plus qu'à déployer notre add-in

5. Interactions possibles avec Visual Studio .Net

Cette partie rassemble différentes interactions possibles qu'il est possible de faire avec l'addin et/ou Visual Studio et qu'il est courant de rencontrer

5.1. Ajouter un fichier au projet

Il est possible que vous soyez obligé de créer un fichier externe ou simplement de prendre un fichier déjà existant et que vous deviez l'ajouter au projet en cours. Heuresement pour nous, il est possible de le faire via la méthode ProjectItems.AddFromFile().
Voici donc un bout de code proposé par Eraim qui permet de créer un fichier csharp ayant pour nom le nom du projet courant, puis d'ajouter celui-ci au projet:

 
Sélectionnez

// Récupération du projet
Project proj = applicationObject.Solution.Item(1);

// Chemin et nom du fichier C#
String path = proj.FullName.Substring(0,proj.FullName.Length-7) + ".cs";

// Création du fichier
StreamWriter sw = new StreamWriter(path,false,System.Text.Encoding.UTF8);
sw.WriteLine("ligne de test");
sw.Close();

// Ajout du fichier au projet
proj.ProjectItems.AddFromFile(path);

De la même façon, il est possible d'ajouter au projet, un template déjà existant. Les templates sont les fichiers préremplis par Visual Studio lorsque vous faites Fichier > Ajouter > Nouveau Fichier > type voulu. Vous devez alors utiliser la méthode ProjectItems.AddFromTemplate()

5.2. Travailler avec le dossier racine de l'addin

Il serait erroné de croire que l'on peut travailler à partir du dossier racine de l'application (travailler avec des fichiers images, une base access ou autre) en utilisation l'habituelle Application.StartupApplication.

6. Déploiement de l'add-in

6.1. Préparation du setup

Comme vous avez dû le remarquer, lors de la création de la solution pour votre add-in, Visual Studio .Net a automatiquement généré un projet de déploiement permettant de redistribuer votre add-in sous forme d'un setup. Lorsque vous compiliez votre projet, une nouvelle instance de Visual Studio était lancée intégrant votre add-in. Néanmoins, à la fermeture de celle-ci, l'add-in était retiré. Plus précisément, il n'était que temporairement chargé dans l'IDE. Pour qu'il soit intégré définitivement, il faut qu'il soit enregistrer et notre setup est là pour nous faciliter la vie car il le fait automatiquement. Une fois, votre projet terminé, vous devez donc générer votre setup:

Image non disponible

Puis une fois la génération terminée, vous pouvez l'installer.

Image non disponible

Ouvrez maintenant une nouvelle instance de Visual Studio .Net et selon votre choix d'intégration, vous verrez peut-être dans le menu outils (ou autre) un menu correspondant à votre add-in.
Maintenant, tout est fonctionnel comme dans la plupart des cas, mais pour des déploiements plus professionnels, vous serez amener à modifier le fichier setup et ses actions sur le système.

6.2. Configuration du setup

Il est maintenant temps de configurer le fichier setup afin que l'installation corresponde à vos attentes. Vous noterez que le projet de déploiement ne contient aucun fichier de configuration (à part le fichier tlb que je vous déconseille d'ouvrir car il vous sera inutile (=illisible)). Pour configurer votre setup, il vous faut remarquer les nouveaux boutons apparus au dessus de l'explorateur de ressources

Image non disponible


Détaillez toutes les actions possibles d'un déploiement prendrait un article tout entier donc nous ne verrons ici que les fonctions les plus utilisées.
Tout d'abord, l'editeur de registre, c'est ici que vous pouvez indiquer les clés de registre à créer. Si vous développez un add-in "mémorisant" les préférences utilisateurs il est plus simple dans ce cas de le faire via le registre. Vous pouvez donc grâce à cet onglet créer les clés qui pourront servir à la configuration de base de votre add-in.

L'éditeur de l'interface utilisateur. Cette partie va vous permettre de designer votre fichier d'installation, aussi bien les images que les différents textes (CLUF, etc). Il vous est également possible d'ajouter de nouvelles fenêtres avec des choix multiples permettant des installations différentes.

L'éditeur de condition de lancement. Il peut par exemple servir pour vérifier qu'un ancien add-in est dejà présent ou autre, car il vous permet de vérifier la présence de clé de registre ou de fichiers sur le disque.

L'éditeur de fichiers, permet de voir le répertoire d'installation de votre add-in et d'y placer des fichiers complémentaires (des fichiers ressources) qui pourront être appelés par votre add-in durant son fonctionnement.

Les deux onglets que je ne décris pas ici sont l'éditeur des types de fichiers car avec un add-in, du fait de son intégration dans VS.Net ayant lui même ses propres fichiers, vous risquez d'avoir des comportements non voulus. J'ai également omis l'éditeur d'actions personnalisées car pour la plupart des compléments qui restent simples, il n'y a pas besoin d'interagir sur le système.

Tout ceci reste une description très rapide du déploiement d'un setup, il est possible de créer des setup bien plus poussés.

Conclusion

Vous avez donc vu avec quelle "simplicité" il est possible de développer son propre add-in. Ici, ce tutoriel présentait un add-in complet mais j'y ai délibérément omis de parler des macros qui sont une méthode plus simple d'automatiser certaines tâches.
Pour toute information complémentaire ou remarque constructive sur l'article, n'hésitez pas à me contacter par mp ou posez votre question sur le forum.

Téléchargements

Voici les sources des add-ins développés lors de ce cours. (Visual Studio 2003)
Sources de Project Manager (395ko) Miroir
Source de Code Improver (20ko) Miroir

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2004 MORAND Louis-Guillaume. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.