VS 2010 : Analyseur personnalisé pour les graphes de dépendances 5

VS 2010 : Analyseur personnalisé pour les graphes de dépendances

Après vous avoir montré comment personnaliser l’IntelliTrace, je vais aujourd’hui vous montrer comment créer vos propres analyseurs pour les graphes de dépendance dans les outils d’architecture de VisualStudio 2010.

Avant de commencer encore un petit avertissement (toujours en gras et rouge pour être bien visible :) ) :

Microsoft ne fournit pas et ne fournira ni documentation ni support sur ce que je vais expliquer car même si cela est possible, ce n’est pas une fonctionnalité supportée.

Maintenant que vous êtes prévenu nous allons voir comment créer un analyseur qui affichera l’ensemble des noeuds commençant par « Qetza. ».

On commence par créer une bibliothèque de classe et par référencer les trois assemblies suivantes :

  • Microsoft.VisualStudio.Progression.Common
  • Microsoft.VisualStudio.Progression.GraphModel
  • Microsoft.VisualStudio.Progression.Interfaces

Ces assemblies se trouve dans le sous-répertoire « Common7\IDE\PrivateAssemblies » de votre répertoire d’installation de VisualStudio 2010 (par défaut « C:\Program Files\Microsoft Visual Studio 10.0″).

Il faut ensuite créer une classe qui implémentera l’interface Microsoft.VisualStudio.Progression.IProvider et ajouter l’attribut Microsoft.VisualStudio.Progression.Provider à la classe :

using System;

using Microsoft.VisualStudio.Progression;
using Microsoft.VisualStudio.Progression.GraphModel;

namespace Qetza.VisualStudio.Analyzers
{
    /// <summary>
    /// The Name analyzer searches the graph linearly for nodes with Labels that start with "Qetza".
    /// </summary>
    [Provider(Name = "NameAnalyzer")]
    public class NameAnalyzer : IProvider
    {
        #region IProvider Members

        /// <summary>
        /// Initialize the named analyzer.
        /// </summary>
        /// <param name="serviceProvider">Not Used</param>
        public void Initialize(IServiceProvider serviceProvider)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Describes the types of node and link categories produced by this provider,
        /// where each Node in the graph is the Id of a Node Category and each link between the
        /// nodes describes the type of navigations possible between nodes.
        /// </summary>
        public Graph Schema
        {
            get { throw new NotImplementedException(); }
        }

        #endregion
    }
}

L’attribut est important car il est utilisé par le mécanisme de VisualStudio pour chercher et charger les classes implémentant IProvider.

L’interface IProvider déclare deux méthodes :

  • Initialize : cette méthode est appelée pour initialiser le provider. C’est dans cette méthode que nous allons enregistrer notre action personnalisée.
  • Schema : cette propriété renvoi les types de catégories de noeuds et de lien créés par le provider. Dans notre cas aucun.

Nous allons maintenant implémenter la méthode Initialize :

static GraphProperty IsNamed = GraphProperty.Register(
    "IsNamed",
    typeof(bool),
    new GraphMetadata(
        Resources.IsNamed,
        Resources.IsNamedDescription,
        null,
        GraphMetadataFlags.Default
        ),
    typeof(NameAnalyzer)
    );

public void Initialize(IServiceProvider serviceProvider)
{
    var action = Microsoft.VisualStudio.Progression.Action.Register(
        "AnalyzeNamed",
        Resources.FindNamed,
        ActionFlags.IsAnalyzer | ActionFlags.IsGlobal,
        null
        );
    action[DgmlProperties.AnalyzerAnnotation] = IsNamed;
    action[DgmlProperties.AnalyzerStyle] = string.Format(
        CultureInfo.CurrentCulture,
        @"<Style TargetType='Node' xmlns='http://schemas.microsoft.com/vs/2009/dgml' GroupLabel='{0}' ValueLabel='True'>
                <Condition Expression='{1}' />
                <Setter Property='Background' Value='#FF008080' />
            </Style>",
        Resources.IsNamed.Replace("'", "&apos;"),
        IsNamed.Id
        );
    action.ActionHandlers.Add(this.OnAnalyzeNamed);
    action.EnabledChanged += this.OnEnabledChanged;
    action[DgmlProperties.BrowserGroup] = Microsoft.VisualStudio.Progression.Global.Resources.BrowserGroup_ActionAnalysis_Id_NOLOC_;
}

Afin de pouvoir sauver pour chaque noeud du graphe si celui-ci commence ou non par « Qetza. » nous avons besoins d’enregistrer une GraphProperty, dans l’exemple IsNamed. Un peu comme les DependencyProperty en WPF et Silverlight nous devons spécifier plusieurs informations :

  • Un identifiant unique : « IsNamed ».
  • Le type de valeur de la propriété : booléen.
  • Des métadonnées dont le libellé (Resources.IsNamed), une description (Resources.IsNamedDescription) et des flags.
  • Le type déclarant la propriété : NameAnalyzer.

Lors de l’initialisation nous créons et enregistrons une action à afficher dans le menu contextuel. Comme pour la GraphProperty nous devons spécifier plusieurs informations :

  • Un identifiant : « AnalyzeNamed ».
  • Un libellé : Resources.FindNamed.
  • Des flags : IsAnalyzer et IsGlobal pour indiquer qu’il s’agit d’un analyseur.
  • Une icone : null.

Nous spécifions ensuite plusieurs propriétés (qui sont des GraphProperty) :

  • AnalyzerStyle : le style en XAML à appliquer aux noeuds ayant la valeur true pour la propriété IsNamed. Attention : l’attribut  GroupLabel doit avoir la même valeur que le libellé de la GraphProperty associée (dans l’exemple j’encode aussi les apostrophes pour ne pas « cassé » le XAML) et l’attribut Expression doit avoir la même valeur de l’identifiant de la GraphProperty.
  • ActionHandlers : on peut via cette propriété ajouter (ou supprimer) des méthodes à appeler lorsque l’action est exécutée.
  • EnabledChanged : on peut via cet évènement gérer ce qui ce passe lorsque l’action est activée ou désactivée.

Maintenant que l’action est enregistrée il faut s’occuper du corps de l’analyseur, la méthode OnAnalyzeNamed :

public void OnAnalyzeNamed(ActionContext context)
{
    this.m_graph = context.Graph;
    this.m_context = context;

    var result = context.OutputObjects;

    using (GraphTransactionScope scope = new GraphTransactionScope("NamedAnalyzer"))
    {
        // reset any prior name information.
        this.ClearNamed();
        this.ThrowIfCancelled();

        // analyzse nodes
        foreach (Node node in this.m_graph.VisibleNodes)
        {
            if (node.Label.StartsWith("Qetza."))
            {
                node[IsNamed] = true;
                result.Add(node);
            }

            this.ThrowIfCancelled();
        }

        scope.Complete();
    }
}

public void ThrowIfCancelled()
{
    if (this.m_context.Cancel)
    {
        throw new OperationCanceledException();
    }
}

private void ClearNamed()
{
    foreach (Node node in this.m_graph.Nodes)
    {
        if (node.HasValue(IsNamed))
        {
            node.ClearValue(IsNamed);
        }

    }
}

La méthode OnAnalyzeNamed va, dans une transaction spécifique pour les graphes :

  • Remettre à zéro l’ensemble des informations sur les noeuds vis-à-vis de la propriété IsNamed (méthode ClearNamed).
  • Pour chaque noeud  visible du graphe passé en paramètre « associer » la GraphProperty IsNamed avec la valeur true au noeud si le libellé de celui-ci commence par « Qetza. » et ajouter ce noeud à la liste des noeuds mis à jour.
  • Lors de chaque boucle on test si l’opération a été annulée afin de s’arrêter via la méthode ThrowIfCancelled.

Il ne reste plus qu’a gérer l’activation/désactivation de l’analyseur :

/// <summary>
/// Handle event when analyzer is enabled/disabled.
/// </summary>
private void OnEnabledChanged(object sender, EventArgs e)
{
    if (this.m_graph != null)
    {
        using (GraphTransactionScope scope = new GraphTransactionScope("DisableUndo"))
        {
            // we always clear names whether we are enabled or disabled, since OnAnalyzeNames will always re-do the names anyway.
            this.ClearNamed();

            scope.Complete();
        }
    }
}

Dans une transaction nous remettons à zéro tous les noeuds. En effet dans notre exemple l’analyseur repasse sur l’ensemble des noeuds lors de son exécution.

Et voila notre analyseur personnalisé (et pas forcement très intéressant mais il fallait bien un exemple :p ) est terminé ! Afin de le tester et de pouvoir ensuite le distribuer facilement il suffit de créer un projet de type VSIX. Dans le contenu du package on ajoutera une entrée pointant sur notre bibliothèque de classes en spécifiant le type personnalisé « Microsoft.VisualStudio.DirectedGraph.Provider ». Le résultat en images :

image

image

Pour résumer, afin de créer un analyseur de graphe de dépendances personnaliser il faut :

  1. Implémenter l’interface Microsoft.VisualStudio.Progression.IProvider.
  2. Ajouter l’attribut Microsoft.VisualStudio.Progression.Provider à la classe.
  3. Enregistrer une (ou plusieurs) GraphProperty pour stocker les données de notre analyseur pour chaque noeud.
  4. Enregistrer une action pour déclencher le code de notre analyseur et définir le style d’affichage des noeuds.

Voila il ne vous reste plus qu’a faire marcher votre imagination pour créer des analyseurs plus intéressant que le mien :)

Carpe Diem.

Vous retrouverez la solution que j’ai utilisée pour ce billet à l’adresse http://blog.qetza.net/wp-content/uploads/2010/05/Qetza.Analyzers.zip.

Leave a Reply

  

  

  

CAPTCHA *