[LiveFX] Le Resource Model et l’annonce du Live Framework Explorer 1.0 lors du MIX09 à Las Vegas

Mardi 19 mai 2009

Depuis le temps que je devais publier cet article resté près de deux mois dans mes brouillons, voila qui est fait !

Vous vous en souvenez peut être en début d’année de la sortie de MOB : Mesh Object Browser, une application WinForm fort bien utile pour les développeurs et ceux voulant comprendre le modèle de ressource du Live Framework.

image Le Live Framework que je vous ai présenté dans mon post consacré à la nouvelle CTP d’Avril 2009 est, je me cite, "une API permettant d’unifier l’accès aux services Live et en particulier au “Live Operating Environment“".

Le Live Operating Environment (LOE) est en quelque sorte votre ordinateur virtuel hébergé sur le Cloud Azure dans ce que l’on appelle le Live Desktop où vous retrouverez vos applications et données partageable avec n’importe qui et cela accessible depuis n’importe quel de vos devices (ordinateurs, laptop, smartphone, web,….) par des mécanismes de synchronisation assurant notamment le fonctionnement en mode déconnecté.

Votre Live Operating Environment est exposé au travers d’un service REST sur le protocole standard du Web qu’est le HTTP. Le format de retour se précise avec le champ "Accept" de l’entête HTTP de votre requête. Au choix :

  • ATOM
  • JSON
  • POX
  • RSS

Vous l’aurez compris, votre Live Operating Environment n’est pas fermé aux technologies Microsoft : n’importe quel langage ou plateforme sachant créer une requête HTTP et parser du XML (ou JSON) peut accéder et interagir avec notre LOE. Au travers du "Live Framework SDK", Microsoft propose trois API simplifiant l’accès au LOE : une pour .NET, une autre pour Silverlight et une dernière pour Javascript ! Vous trouverez des exemples d utilisation du LOE, notamment avec d’autre langage comme PHP ou IronPython, sur le blog de l’equipe LiveFx.

Le Resource Model

Une fois connecté à votre LOE vous pourrez accéder à votre "Resource Model" (modèle de ressource) que forme vos données et applications.

Le Resource Model est le modèle de donnée de votre LOE. Il contient les ressources concrètes (type) et ses relations. Tout dans le Live Framework est exposé comme une ressource (application, donnée, notification, contact, profile, membres, etc…). Chacune de ces ressources disposes d’une URI fixe, d’un nom, d’un auteur, date de modification, etc…

L’avantage de ce découpage LOE <-> Resource Model est qu’il n’y a pas de lien fort entre le service responsable d’exposer en REST des collections de LiveItem et ce qu’on va réellement y stocker dans ces LiveItem que ce soit des contacts, des applications, des fichiers ou quoi que ce soit !

Aujourd’hui le Resource Model proposé dans le Live Framework est celui ci :

 

image

On y retrouve l’accès à notre Mesh, nos contacts et notre profile. Dans notre Mesh nous pouvons retrouver nos Devices, Applications, News et aussi nos MeshObject.

Chaque MeshObject contient une collection de Feed, des members pour la notion de partage, des news, des mappings pour la notion de synchronisation entre les Devices. Et c’est dans les DataFeed que nous retrouverons nos DataEntries qui matérialises nos entités dans notre LOE (par exemple un fichier dans Mesh est un DataEntries de type File d’un DataFeed de type LiveMeshFiles d’un MeshObject de type LiveMeshFolder).

Comment parcourir votre Resource Model ?

La première question que je me suis posé peu après avoir suivi les webcasts des sessions de la PDC en Octobre 2008 qui présentait le Live Framework était : comment parcourir notre Resource Model ?

Pour comprendre son fonctionnement comme pour développer on a besoin de savoir ce qu’il y a dedans, un peu comme on ouvre son SQL Studio Management ou phpMyAdmin pour explorer les tables, relations entre ces tables et données qu’elles contiennent.

Le Live Framework SDK offre un et un seul outil nommé le "Live Framework Resource Browser". Cet outil est ni plus ni moins qu’un browser web adapté pour la navigation du service REST "on the cloud" (Live Desktop) ou "local" (Local Desktop) du LiveFX. En effet ce browser à la particularité de  :

  • Activer les liens dans les flux pour une navigation facile
  • Formater les flux JSON
  • Permettre de sélectionner son format de sortie (JSON/POX/RSS/ATOM)
  • Éditer les requêtes HTTP

image image image

Cet outil bien que fort utile est limité en terme de recherche dans le Resource Model et de manipulation des LiveItem (création, édition et suppression). De plus en terme de visualisation, bien qu’un ordinateur n’aura pas de mal à lire des centaines de milliers de lignes de code (RSS, ATOM ou JSON), pour un humain c’est bien moins rapide, vite noyé dans la verbosité du langage et informations peu pertinentes !!!

C’est de là qu’est né, chez Wygwam, le besoin de disposer d’un outil permettant une représentation graphique du Resource Model de notre Live Operating Environment.

1er projet MOB : Mesh Object Browser

image Développé en Novembre 2008, Mesh Object Browser est une petite application Winform permettant de visualiser au travers d’une Treeview notre Resourcel Model avec la possibilité de pouvoir éditer nos LiveItem dans une grille de propriétés.

MOB n’est sortie qu’un mois après, en Janvier 2009, sur la plateforme CodePlex en code ouvert à l’adresse : http://mob.codeplex.com/

Pour citer le magazine Programmez! :

Il s’agit pour le développeur de naviguer rapidement et simplement dans les objets Mesh et leur contenu. Il aidera donc grandement le développeur Mesh. L’outil est écrit en C#. Créé à l’origine par Sébastien Warin (qui a victorieusement participé à ImagineCup 2008 France), il est aujourd’hui rendu disponible par l’équipe Wygwam. Il permet outre la navigation de gérer les contenus des objets. Parmi les objets supportés en Live Desktop ou en desktop local : contacts, profils, Mesh Objects, Devices et les News.

image

Retrouvez Mesh Object Browser :

2ème projet : Live Framework Explorer 1.0

MOB offrait une représentation graphique facile à comprendre avec la possibilité de pouvoir éditer facilement nos LiveItem. Mais certaines informations nécessitent encore le Live Framework Resource Explorer quand la représentation graphique ne suffisait plus.

Du point de vue du développeur, nous nous retrouvons avec trois programmes lancés systématiquement :

  • Visual Studio pour notre développement
  • Mesh Object Browser pour pouvoir avoir une représentation graphique rapide
  • Live Framework Resource Model pour pouvoir fouiller les propriétés cachées dans nos LiveItem

C’est donc suite à ce constat renforcé avec les feedbacks des utilisateurs de MOB que j’ai développé fin Janvier ce qui allait devenir le Live Framework Explorer.

image

Le Live Framework Explorer (ou LFE) est un addin à Visual Studio qui va permettre accéder à votre Resource Model directement depuis votre environnement de développement. En quelque sorte l’intégration de MOB dans Visual Studio.

 image

L’autre particularité de LFE comparé à MOB est son architecture extensible permettant d’enrichir les fonctionnalités de LFE par le développement de plugin.

L’un des premiers plugins développés est le "View in browser" qui permet, toujours depuis Visual Studio, de pouvoir ouvrir le flux du LiveItem sélectionné avec la possibilité de sélectionner le format de sortie. En clair, l’intégration du Live Framework Resource Browser comme plugin de LFE.

image

Cela permet donc d’unifier le LFRB et MOB dans un seul outil intégré lui-même à votre environnement de développement Visual Studio.

Depuis la beta 1 fin Janvier, d’autre plugin ont vu le jour comme la possibilité d’uploader/downloader des fichiers directement depuis LFE, d’ajouter, modifier et supprimer des LiveItem, d’inviter des membres sur un MeshObject, etc…

Annonce au MIX09 de Las Vegas

D’abord présenté aux équipes de Microsoft Corp. en la personne de James Senior, LFE a tout de suite suscité un grand intérêt auprès de Microsoft. Si bien que Microsoft m’a gentiment invité à passer 3 jours à Las Vegas à l’occasion du MIX, et a proposé à Gregory Renard (Redo) une session sur le LiveFX afin de lancer officiellement la sortie de LFE !

image image

Ce fut aussi pour moi, l’occasion pour moi de rencontrer quelques personnes culte chez Microsoft comme Scott Guthrie (Corporate Vice President, Microsoft .NET Developer Platform), Nikhil Kothari (software architect in the .NET Developer Platform group), James Senior (Live Services Evangelist), Bill Buxton (Principal Researcher Microsoft Research) et enfin Nigel Ellis (Principal Architect of Microsoft SQL Services)

Scott Guthrie (Corporate Vice President, Microsoft .NET Developer Platform) Nikhil Kothari (software architect in the .NET Developer Platform group)

James Senior (Live Services Evangelist) Bill Buxton (Principal Researcher Microsoft Research)

Nigel Ellis (Principal Architect of Microsoft SQL Services)

LFE en Beta 5 et code ouvert sur CodePlex

Depuis notre retour du MIX, LFE a encore évolué et est actuellement disponible en version Beta 5 sur CodePlex à l’adresse http://lfe.codeplex.com/

Téléchargement direct : http://lfe.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=26624

Liste des plugins à ce jour :

  • FileDownloadingPlugin : to download a LiveMeshFile
  • FileUploadingPlugin : to upload a LiveMeshFile in a LiveMeshFolder
  • LiveFxBrowserPlugin : a LiveFxBrowserResource integration to browse in HTTP your Mesh and custom easily the output format (JSON, RSS, ATOM or POX)
  • LiveItemEditorPlugin : to edit a resource in a PropertyGrid and update this content on your Mesh
  • CreateLiveItemPlugin : to create an LiveItem in a LiveItemCollection (the LiveItem is type of TMeshItem of the collection)
  • DeleteLiveItemPlugin : to remove a LiveItem from a LiveItemCollection
  • VSSettingsImportExportPlugin : to import/export Visual Studio Settings file in Mesh
  • AddInvitationPlugin : to share your MeshObject by sending invitation

Alors n’hésitez plus, au même titre que le Reflector est pour le développement .NET, Live Framework Explorer (LFE) est l’outil indispensable pour vos développements sur LiveFX :)

A voir aussi :

http://lfe.codeplex.com/

[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.

[VS Extensibility] Part 1 : Comment étendre Microsoft Visual Studio

Lundi 2 février 2009

image La plupart des développeurs sur plateforme Microsoft/.NET ont bien l’habitude d’ouvrir leur Visual Studio pour le développement de leurs applications. Lancé en 1997, la 1ère version de Visual Studio (ou VS) nommé tout naturellement Visual Studio 97, a voulu réunir dans un seul et même IDE plusieurs langages qui étaient à l’époque le VB 5.0, VC++, InterDev, VJ++ et FoxPro. Depuis 2002 et l’arrivée de la plateforme .NET, Visual Studio est devenu l’IDE de référence permettant le développement d’application WinForm, ASP.NET (WebForm), Mobile, WPF, Services Web/WCF, workflow, etc… supportant plusieurs langages comme VB.NET, C#, VC++.net et bien d’ autre encore sous forme d’extension.

image La grande qualité de Visual Studio est sa capacité à être étendue pour l’enrichir de nouvelles fonctionnalités (Add-in), de nouveaux langages, designers (DSL), etc.. Dans la version 2008 il est aussi possible de se servir du Visual Studio Shell permettant l’utilisation du socle Visual Studio pour développer un IDE spécifique basé sur VS.

Au fil du temps, les API d’extensibilité de VS se sont de plus en plus ouvertes rendant plus accessible et plus facile le développement d’extension. Ouvrons donc notre Visual Studio 2008 pour y créer un nouveau projet ! Vous retrouverez quelques templates de projets d’extensibilité dont les principaux sont :image

  • VS Addin : pour créer un module/extension dans Visual Studio
  • VS Shell Isolated : pour créer un nouvel IDE basé sur Visual Studio
  • VS Language Package : pour ajouter un nouveau langage dans Visual Studio en spécifiant les analyseurs lexical/syntaxique, le compilateur, la coloration syntaxique pour le langage, etc…
  • VS Package : pour créer un package (visible dans le Splash screen de VS) contenant un ensemble d’éléments graphique, de services, de templates de projets, d’ éditeurs, ou encore de designers.
  • DSL : pour la création de designers dans Visual Studio

Dans cet article nous verrons comment créer un Add-in dans votre Visual Studio 2005 ou 2008 en parcourant les principales fonctionnalités que l’on voudra doter à notre Add-in (création de fenêtre, écriture dans les Window Pane, interaction avec la StatusBar ou Properties Window, etc…). Je mettrai aussi en avant les principaux problèmes et leur solutions que l’on rencontre couramment dans le développement de tel add-in.

Création du projet de type « VS Add-in » :

Commençons par créer un projet de type « Visual Studio Add-in » que nous nommerons « MonAddinDemo » ! Afin simplifier la création d’ un tel projet, Visual Studio vous lancera un assistant (wizzard) permettant de préparer votre projet de type Add-in :

image  image  image  image  image  image

  1. Choix du langage : ici en C#
  2. Choix de l’hôte qui chargera notre Add-in : ici nous créons un add-in compatible VS2005 et 2008 en mode normal et Macro (pour l’ édition de Macro)
  3. Choix du nom et description de notre Add-in
  4. Choix des options : ici nous sélectionnons le fait que nous voulons ajouter automatique notre add-in dans le menu Tools de VS.
  5. Choix des informations sur le « About »
  6. Résumé !

Une fois validée toutes les informations recueillies vont permettre à Visual Studio de créer votre projet d’ Add-in en personnalisant votre fichier .addin et Connect.cs en fonction des choix effectués dans l’ assistant.

Voici à quoi ressemble votre projet après création :

image

On y retrouve :

  • AssemblyInfo.cs : contenant les attributs sur notre assembly
  • CommandBar.resx : contenant les traductions des menus de niveau 1 (Fichier, Édition,  …) de Visual Studio dans toutes les cultures
  • Connect.cs : classe de démarrage de notre add-in (qui implémente IDTExtensibility2)
  • MonAddinDemo – For Testing.AddIn : contenant la description de l’ Add-in au format XML (pour le test !)
  • MonAddinDemo.Addin : idem mais pour le packaging !

Sans plus attendre, voyons ce que tout cela rend. Tapez F5 pour compiler et exécuter notre projet ! Une nouvelle instance de Visual Studio se lancera et en cliquant sur le menu Tools (ou Outils en français) nous retrouverons notre add-in avec une jolie icône :)

image

Fichiers .AddIn et la classe Connect – comment ca marche ?

Fichiers .AddIn

Les fichiers de type .AddIn sont en fait des fichiers XML de description d’un addin. Chaque addin a donc son fichier .AddIn permettant de le décrire en deux sections XML différentes : Les HostApplication(s) et la section Addin.

Les HostApplication(s) vont décrire quelles sont les applications qui hébergeront notre addin. Dans notre « MyAddinDemo » nous avons souhaité héberger notre addin au sein de VS 2005 (8.0) et 2008 (9.0) en mode normal et macro.

Le XML correspondant est donc :

image

La section Addin contiendra les propriétés de l’addin en question comme par exemple :

  • FriendlyName : Le nom de notre addin tel qu’il sera affiché dans le Gestionnaire d’Add-in de Visual Studio
  • Description : la description de notre addin.
  • AboutBoxDetail et AboutBoxIcon : les informations (détail et icône) pour la boite A Propos comme nous l’ avons spécifié dans l’ étape 5 de l’ assistant
  • Assembly : le fichier DLL de l’assembly .NET contenant notre addin
  • FullClassName : le nom complet de la classe Connect contenu dans notre Assembly (ici : MonAddinDemo.Connect)

Extrait de notre section Addin :

image

Au démarrage, Visual Studio parcourra les répertoire ci-dessous (plus de détails sur MZ-Tools) pour charger tous fichiers .AddIn qu’il trouvera.

  • %ALLUSERSPROFILE%\Application Data\Microsoft\MSEnvShared\AddIns
  • %VSCOMMONAPPDATA%\AddIns
  • %ALLUSERSDOCUMENTS%\Microsoft\MSEnvShared\AddIns (seulement pour VS 2008)
  • %APPDATA%\Microsoft\MSEnvShared\AddIns
  • %VSAPPDATA%\AddIns
  • %VSMYDOCUMENTS%\AddIns

Comme nous l’avons vu un peu plus haut, avec ces fichiers XML, Visual Studio sera capable de charger l’assembly et d’ instancier la classe de connexion (notre MonAddinDemo.Connect).

Vous avez remarqué la présence de deux fichiers .AddIn dans notre solution : un normal et un pour « testing ». En fait, un fichier <mon projet>.AddIn est créé pour décrire l’add-in tel que l’on utilisera lors du packaging. Le 2ème « For Testing » est en fait un raccourci vers un fichier .AddIn présent dans le répertoire « %VSMYDOCUMENTS%\AddIns« . Il permet en fait d’avoir notre d’addin lancé au démarrage de Visual Studio pour nos tests ! Mais attention, si un 2ème VS est ouvert, vous risquerez de ne plus pouvoir compiler car il sera impossible de modifier votre assembly .DLL si elle est chargée par une autre instance de VS.

La classe Connect

Notre classe Connect est une implémentation de l’interface IDTExtensibility2 et IDTCommandTarget pour l’ajout d’une commande dans le menu. C’est elle qui est référencée dans notre fichier .AddIn et c’est donc elle qui sera instanciée par Visual Studio pour le chargement de l’ addin.

Comme nous le montre notre Object Browser :

image 

… cette classe implémentera les méthodes suivantes :

  • IDTExtensibility2.OnConnection : appelée au début du chargement de l’ addin par VS.
  • IDTExtensibility2.OnDisconnection : appelée au début du déchargement de l’ addin par VS.
  • IDTExtensibility2.OnAddInsUpdate : appelée quand la liste des addins est modifiée .
  • IDTExtensibility2.OnStartupComplete : appelée en fin de chargement de l’ addin.
  • IDTExtensibility2.OnBeginShutdown : appelée lorsque Visual Studio se ferme.
  • IDTCommandTarget.QueryStatus : appelée pour retourner l’état actuel (activée, désactivée, masquée, etc.) de la commande nommée spécifiée.  
  • IDTCommandTarget.Exec : appelée pour exécuter la commande nommée spécifiée. 

Comme vous le remarquait l’interface IDTExtensibility2 définie les méthodes de chargement/déchargement de notre addin. L’interface IDTCommandTarget  permet quant à elle de créer des commandes nommées qui est ici notre élément dans le menu Tools avec le petit smiley :)

Analysons maintenant le code de notre classe Connect générée par Visual Studio ! Vous remarquerez que seules les méthodes OnConnection, QueryStatus et Exec contiennent du code. Libre à vous d’implémenter une logique pour la fermeture de votre add-in si cela est nécessaire.

Lors de l’appel de la méthode OnConnection, l’argument connectMode va permettre de savoir le mode de connexion de l’addin. Dans le code générée par VS, le code de la méthode connexion est executé si le connectMode == ext_cm_UISetup. Comme nous l’indique la MSDN :

La valeur ext_cm_UISetup indique au complément qu’il s’agit de sa première exécution. Dans ce cas, le complément peut ajouter ses commandes personnalisées au menu et à la barre d’outils. Sinon, il peut ignorer cette étape.

Dans d’autre cas, on préféra utiliser les modes AfterStartup ou Startup pour permettre la création de fenêtres au démarrage ou initialisation de la logique de notre application au démarrage de VS.

La suite du code montre un « try..catch » (qu’il conviendrai d’externaliser dans une méthode privée) permettant la récupération du nom du menu « Tools ». En effet comme l’indique le commentaire, ce code permet de rechercher dans le fichier CommandBar.resx quel est le nom du menu Tools en fonction de la culture de VS. En version anglaise (EN), le nom est bien sûr « Tools » mais en version française (FR) cela devient « Outils », et cela change pour les versions JP, ES, IT, RU,  etc….

Une fois le nom récupéré on pourra récupérer la CommandBarPopup par le code :

1
2
3
4
5
6
7
//Place the command on the tools menu.
//Find the MenuBar command bar, which is the top-level command bar holding all the main menu items:
Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar = ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars)["MenuBar"];
 
//Find the Tools command bar on the MenuBar command bar:
CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName];
CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;

Et ainsi ajouter notre bouton :

1
2
3
4
5
6
//Add a command to the Commands collection:
Command command = commands.AddNamedCommand2(_addInInstance, "MonAddinDemo", "MonAddinDemo", "Executes the command for MonAddinDemo", true, 59, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported+(int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);
 
//Add a control for the command to the tools menu:
if((command != null) && (toolsPopup != null))
	command.AddControl(toolsPopup.CommandBar, 1);

Vous remarquerez dans l’appel de la méthode AddNamedCommand2 les différents paramètres pour notre bouton comme par exemple son nom, son titre et son tooltip. Mais où est donc spécifié notre image (smiley) ? C’est en fait le « 59″ qui détermine l’icône de notre bouton !

Il s’agit en effet du n° de FaceID dont vous retrouverez la liste complète sur la page http://www.kebabshopblues.co.uk/2007/01/04/visual-studio-2005-tools-for-office-commandbarbutton-faceid-property/. On y retrouve notre n° 0059 représentant notre smiley jaune.

Nous verrons dans un autre post comment définir sa propre icône !

Une fois notre addin « connecté » et le bouton ajouté dans le menu Tools, voyons l’implémentation de l’interface IDTCommandTarget et de ses deux méthodes : QueryStatus et Exec.

La méthode QueryStatus permet de définir le status de notre bouton dans le menu. Nous pourrions par exemple implémenter une logique où le bouton serait grisé si l’addin est déjà lancé par exemple.

Dans le code actuel il n’en n’est rien. Le bouton est toujours activé et visible comme l’indique le code :

1
2
3
4
5
6
7
8
if(neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
{
	if(commandName == "MonAddinDemo.Connect.MonAddinDemo")
	{
		status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported|vsCommandStatus.vsCommandStatusEnabled;
		return;
	}
}

La méthode Exec quant à elle implémente la logique lors du click du bouton. Il convient d’abord de vérifier quel bouton est cliqué en testant le paramètre commandeName.

Dans le code généré par Visual Studio, aucune action n’est effectuée. Pour notre démo nous allons ajouter la référence vers System.Windows.Forms pour afficher une MessageBox. Voici le code complet de notre méthode Exec :

1
2
3
4
5
6
7
8
9
10
11
12
13
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")
		{
			handled = true;
			System.Windows.Forms.MessageBox.Show("Mon 1er Add-in Visual Studio :)");
			return;
		}
	}
}

Relançons maintenant notre projet (F5) et observez le résultat lors du click sur notre bouton :

image

Conclusion

Vous voilà maintenant introduit dans le développement d’add-in pour Visual Studio 2005/2008. Un addin est donc une assembly .NET contenant une classe nommée généralement Connect qui implémente l’interface IDTExtensibility2 et est responsable de la « Connexion » et « déconnexion » de l’addin à Visual Studio. Chaque add-in étant accompagné d’un fichier de description au format XML (.AddIn) placé dans un des répertoires de Visual Studio pour préciser les informations sur l’add-in (nom, fichier de l’assembly, nom de la classe Connect, etc…) et ainsi être chargé au démarrage de l’IDE Visual Studio.

Dans la prochaine partie nous verrons comment créer des fenêtres dans Visual Studio.

Historique .NET : dates et versions (Fx, CLR, IDE et langages)

Jeudi 29 janvier 2009

En préparant du contenu pour des sessions sur le futur de .NET (VS2010, .NET 4.0, C# 4.0 ou encore VB 10), j’ ai été amené à faire un petit tableau récapitulatif des dates et versions des principaux éléments de .NET : le framework, la CLR, l’ IDE Visual Studio et les deux principaux langages .net (C# et VB.net).

Un petit copier/coller pour vous en faire profiter :

image

A bientôt :)