Création d'un Addin pour Visual Studio .Net
Date de publication : 25/10/2004 , Date de mise à jour : 20/03/2005
Par
MORAND Louis-Guillaume (autres articles)
Tutoriel pas à pas expliquant le principe, le développement et l'intégration d'un addin dans Visual Studio .Net
Avant-Propos
1. Création du projet
1.1. Introduction
1.2. Le projet de complément
2. Fonctionnement d'un add-in
2.1. Structure d'un add-in
2.2. Intégration dans Visual Studio .Net
2.3. Interaction avec Visual Studio .Net
3. Add-in : Project Manager
3.1. Description
3.2. Développement de l'interface de l'add-in
3.3. Développement du code fonctionnel
4. Add-in : Code Improver
4.1. Description
4.2. Intégration de l'add-in
4.3. Développement du code fonctionnel
5. Interactions possibles avec Visual Studio .Net
5.1. Ajouter un fichier au projet
5.2. Travailler avec le dossier racine de l'addin
6. Déploiement de l'add-in
6.1. Préparation du setup
6.2. Configuration du setup
Conclusion
Téléchargements
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
Donnez alors un nom à votre projet. Puis cliquez sur OK. Un nouveau panneau s'affiche alors.
Cliquez sur suivant.
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)
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
Choississez le nom de votre add-in (le nom sous lequel il apparaitra dans la liste des add-ins) ainsi qu'une rapide description.
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é.
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)
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.
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:
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.
Puis l'utilisation d'autres namespaces permettant d'appeler des fonctions.
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.
#region Read me for Add-in installation and setup information.
#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.
[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)
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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);
}
catch
{
}
}
|
Reprenons la structure de l'ajout d'un menu via la méthode AddNamedCommand( ):
| paramètres de création d'un menu |
commands.AddNamedCommand(
addInInstance,
"NomMenu",
"Texte du menu",
"Tooltip",
true,
1,
ref contextGUIDS,
(int)blabla....
)
|
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 |
public void Exec(string commandName, EnvDTE.vsCommandExecOption executeOption, ref object varIn,
ref object varOut, ref bool handled)
{
if(commandName == "MonAddin.Connect.Menu1")
{
}
if(commandName == "MonAddin.Connect.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:
status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
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
private _DTE applicationObject;
|
et se définit de la sorte:
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
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:
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.
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 |
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.
EnvDTE.Project theProject = null;
if (theProjects.Length > 0)
{
theProject = (EnvDTE.Project)(theProjects.GetValue(0));
}
|
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.
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 :
|
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.
foreach (ProjectItem p in theProject.ProjectItems)
{
try
{
Compteur=Compteur+1;
FileInfo Fichier=new FileInfo(f.DirectoryName+"\\"+p.Name);
string taille=(CKB_Det.Checked)?" ("+ FormatSize(Fichier.Length)+")":"";
strW.WriteLine("<FONT class=content>- "+p.Name+ taille+"</FONT><br>");
if (Fichier.Extension == sFiltre)
{
SpecCompteur = SpecCompteur+1;
TotLiCompteur=TotLiCompteur+CompteLigne(f.DirectoryName+"\\"+p.Name);
}
}
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.
#region Fonction CompteLigne
<summary>
</summary>
<param name="path"></param>
<returns></returns>
private int CompteLigne(string path)
{
StreamReader srFile = new StreamReader(path);
string strLine=string.Empty;
int LiCompteur=0;
while ((strLine = srFile.ReadLine()) != null)
{
LiCompteur++;
}
srFile.Close();
return LiCompteur;
}
#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.
#region Fonction Arbo
<summary>
</summary>
<param name="path"></param>
<param name="strW"></param>
<param name="count"></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);
}
}
#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.
#region Fonction Backup
<summary>
</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.
|
System.Array theProjects = (System.Array)applicationObject.ActiveSolutionProjects;
if(theProjects.Length <= 0)
{
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 |
#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[] { };
Commands commands = applicationObject.Commands;
_CommandBars commandBars = applicationObject.CommandBars;
try
{
CommandBar commandBar = (CommandBar)commandBars["Code Window"];
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);
command = commands.AddNamedCommand(addInInstance,
"AjouterHeadere",
"Ajouter un entête",
"Ajoute
|