VS 2010 : Custom graph dependency analyzer 5

VS 2010 : Custom graph dependency analyzer

After showing you how to customize IntelliTrace, i’ll show you today how you can create your own graph dependency analyzers for VisualStudio 2010 architecture tools.

Before starting here is another warning (still in bold and red so everyone sees it :) ):

Microsoft does not give any documentation or support on all that i will explain because even it’s possible, the functionality is not supported.

Now that you are warned lets start implementing a small analyzer which will show us all nodes starting with “Qetza.”.

First we create a class library and add the following assembly references:

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

These assemblies are in the sub directory “Common7\IDE\PrivateAssemblies” of your VisualStudio 2010 install directory (by default “C:\Program Files\Microsoft Visual Studio 10.0″).

Now lets create a class which will implement the Microsoft.VisualStudio.Progression.IProvider interface and has the Microsoft.VisualStudio.Progression.Provider attribute:

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
    }
}

The attribute is important because it’s used by VisualStudio when searching and loadin classes implementing IProvider.

The IProivder interface declares two methods:

  • Initialize : this methods is called to initialize the provider. It’s in this method that we will register our customized action.
  • Schema : this property returns the types of node and like categories produced by the provider. None in our example.

Lets look at the initialize method implementation:

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_;
}

So that we save for each not if it starts with “Qetza.” we need to register a GraphProperty, IsNamed in our example. As for DependencyProperty in WPF and Silverlight we must specify some informations:

  • A unique identifier: “IsNamed”.
  • The property value type: booléen.
  • Metadata such as the label (Resources.IsNamed), the description (Resources.IsNamedDescription) and some flags.
  • The declaring type: NameAnalyzer.

When initializing our analyzer we register an new action to display in the context menu. As for GraphProperty we must specify some information:

  • An identifier: “AnalyzeNamed”.
  • A label: Resources.FindNamed.
  • Some flags: IsAnalyzer and IsGlobal to indicate it’s an analyzer.
  • An icon: null.

We then specify some properties (which are GraphProperty):

  • AnalyzerStyle : the XAML style for nodes wwill have a value of true for our GraphProperty IsNamed. Warning : the GroupLabel attribute must have the same value that the associated GraphProperty label (in the example i also encode single quotes since attribute values are already enclosed in single quotes) and the attribute Expression must have the same value as the associated GraphProperty identifier.
  • ActionHandlers : using this property we can add (or remove) action handlers to be executed with the action.
  • EnabledChanged : we can handle the action enabling and disabling using the event.

Now that our action is register lets look at the analyzer code in the OnAnalyzeNamed method:

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);
        }

    }
}

The OnAnalyzeNamed method will, in a transaction specific to graphs:

  • Clean all data on nodes for the IsNamed GraphProperty using the CleanNamed method.
  • For each visible node in the received graph assigned the GraphProperty IsNamed with a value of true to each node with a label starting with “Qetza.” and add this node to the updated node list.
  • In each loop iteration we test if the operation was cancelled using the ThrowIfCancelled method.

The only code left is for managing the action enabling and disabling:

/// <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();
        }
    }
}

In a transaction we clean all nodes. This simple algorithm can be used because we always parse every node in OnAnalyzeNamed.

Our custom (and not really interesting but i needed an example :)) analyzer is finished! So that we can test it and deploy it all we have to do is create a VSIX project. In this content of the VSIX we will and an entry to our class library using the custom type “Microsoft.VisualStudio.DirectedGraph.Provider”. The results in image:

image

image

To summarize, for creating a custom graph dependency analyzer you must:

  1. Implement the Microsoft.VisualStudio.Progression.IProvider interface.
  2. Add the Microsoft.VisualStudio.Progression.Provider attribute to your class.
  3. Register one (or more) GraphProperty to store data for your analyzer on each node.
  4. Register an action to execute your code and define the display style for the nodes.

That’s all folks, now is time to let your imagination work and create more interesting custom analyzer than mine :)

Carpe Diem.

You can find the solution i used for this post at the following url http://blog.qetza.net/wp-content/uploads/2010/05/Qetza.Analyzers.zip.

5 thoughts on “VS 2010 : Custom graph dependency analyzer

  1. Reply كازينو Aug 9,2010 21:50

    Hi guys, tried loading this blog through Google RSS reader and got a strange error message, any ideas what could be the issue?

  2. Reply كازينو Aug 13,2010 14:04

    Nevermind, works now!

  3. Reply Guillaume Rouchon Aug 19,2010 15:54

    Hi,
    Glad it works now. Sorry for not replying i was in vacation.

    Guillaume

  4. Reply Pascal Couture Sep 21,2010 22:06

    Hi,

    Is there a way to have it so that if I click on a method, class or assembly in a graph dependency that it tells me all the methods that could be affected (up or down in a tree) if I change code in it?

    Thanks

  5. Reply Guillaume Rouchon Sep 23,2010 10:26

    Hi Pascal,
    If what you have with VS 2010 graph dependency screen is not enough, you can save the graph and develop a small application to parse it for your specific need. The file is XML and contains all nodes and links between them, the schema is not complicated.

    Guillaume

Leave a Reply to Pascal Couture Cancel Reply

  

  

  

CAPTCHA *