Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Strongly Typed Collection Builder Addin for VS.NET 2003

0.00/5 (No votes)
4 Jul 2003 5  
VS.NET addin that creates a strongly typed collection from a class.

Sample Image - CollectionBuilderAddin1.jpg

Introduction

This article does not aim to introduce any complexity in writing addins. It merely uses simple approaches to create an addin in VS.NET IDE that can be useful for code writing. I believe that most addins for VS.NET are very useful but they are even better if they can speed up the code writing process.

Getting Started

I was recently working in a project that involved a series of independent classes that needed to be stored in strongly typed collections in different parts of the program. From the wise words of Phil Wright (the creator of dotnetmagic library) Although creating a collection class is not difficult it can be time consuming. Indeed! I decided to create a VS.NET Addin to speed up that part of development.

The Addin consisted in adding a Menu Item when the user right clicks a class in the "Class View Window" in VS.NET, exposing an option to create a Strongly Typed Collection based on the selected class.

How to insert a Menu Item in the VS.NET Context Menu?

Firstly we need to work out how to add a Menu Item in the Context Menu but only in the "Class View Window". In order to get this done, we need to understand how the context Menus in VS.NET are correlated with the addins themselves. All context menus and Menus in VS.NET are office CommandBars. So we can find out the indexes by looping throughout the collection (this can be done on the OnConnnection method from a dummy addin). Here is part of the dump I wrote to the disk (the full listing is included with the source files :-p).

...
Text Editor
XML Data
XML Schema
Device
Debug Location
Debug
Layout
Dialog Editor
Image Editor
Source Control
Analyzer
Analyzer Windows
TestContextMenu
ResultContextMenu
Project
...

Taking in consideration that we can add the menu item anywhere from the above list as a test I tried to add one on the "Code Window" which describes the context menu that pops up when we right click on the Text Editor. I am sure someone can come up with good ideas for addins using this, such as adding quick code snippets or else :-)

Sample Image - maximum width is 600 pixels

After trying out (and having fun) placing different Menu Items around VS.NET, I came to realise that in the class view window there are a few different Context menus, but the one I wanted is called "Class View Item" which is the one that pops up when we select the Item in the Class view tree. This is how we add a menu item in that specific Context menu:

 // Create a new command
 Command command = commands.AddNamedCommand(addInInstance, 
   "CollectionCommand", "Create Collection", 
   "Adds a collection based on a type", true, 137, 
   ref contextGUIDS, 
   (int)vsCommandStatus.vsCommandStatusSupported + 
    (int)vsCommandStatus.vsCommandStatusEnabled);

 // Create a new commandBar from the "Class View Item".
 CommandBar commandBar = (CommandBar)commandBars["Class View Item"];

 // Just add to the command control.
 _commandBarControl = command.AddControl(commandBar, 1);

Notice that I have (for simplicity) kept the same code produced by the Wizard. Yes, what you need to do is to replace the menubar name given by the wizard with any of the indexes from the above list, automatically you will have that very same menu item in the belonging command bar (whatever it may be either a menu bar or context menu).

How to check if we are selecting a Class and not other elements?

Worst case scenario I did not want to create a strongly typed collection based on a namespace name or class member. The addin works on the premises that the user right clicks on the top of the class that he/she wishes to create a collection from. *The answer for this is to use the QueryStatus which is called every time the context menu pop up, notice that in VS.NET 2002 was a bug and this behaviour was not consistent however it's been fixed for VS.NET 2003 and it works perfectly!

So, when we the Class View Item pops up the QueryStatus fires and we can verify if the selected item is a class by trying to cast it to a CodeClass type, if yes we enable the adding making it visible (showing the menu item) otherwise we hide it.

...            
    // Get current selected item
    object _object = this.VSNET.SelectedItems.SelectionContainer.Item(1);
    // try to safely cast it to CodeClass
    if(_object is CodeClass)
    {
        // Show MenuItem
        status = (vsCommandStatus) vsCommandStatus.vsCommandStatusSupported |
                                   vsCommandStatus.vsCommandStatusEnabled; 

    }
    else
    {
        // Hide MenuItem.
        status = (vsCommandStatus)vsCommandStatus.vsCommandStatusInvisible; 
    }
 ...

Well, this is the presentation of the addin now we need to work on the guts of it!

Generating a Strongly Typed Collection - Design decisions

Once we have the reference from the ProjectItem instance where we want to insert the code into, we need to decide how we are going to generate the code. There are a few option available:

  1. Create a template and replace tagged strings
  2. Create the Class using the FileCodeModel exposed by the DTE object
  3. Use Microsoft's CodeDom

For simplicity, I've decided to use a template based solution. The other options are have your advantages (as well as disadvantages) however they also would introduce more complexity to the code. Thus, from the Magic articles written by Phil Wright I have introduced his Strongly Typed Collections with events as a template. Here is the template:

using System.Collections;
 
namespace #namespace#
{
         public class CollectionWithEvents : CollectionBase
         {
                 ...
                 
            // Declare the event signatures
            public delegate void CollectionClear();
            public delegate void CollectionChange(int index, object value);
 
            // Collection change events
            public event CollectionClear Clearing;
            public event CollectionClear Cleared;
            public event CollectionChange Inserting;
            public event CollectionChange Inserted;
            public event CollectionChange Removing;
            public event CollectionChange Removed;
                 ...
         }
}
        

Then we have the template that will inherit from the CollectionWithEvents (as per dotnetmagic tips and tricks documents):

using System.Collections;
namespace #namespace#
{
         public class #myTypeCollection# : CollectionWithEvents
         {
                 public int Add(#myType# value)
                 {
                          return base.List.Add(value as object);
                 }
 
                 public void Remove(#myType# value)
                 {
                          base.List.Remove(value as object);
                 }
 
                 public void Insert(int index, #myType# value)
                 {
                          base.List.Insert(index, value as object);
                 }
 
                 public bool Contains(#myType# value)
                 {
                          return base.List.Contains(value as object);
                 }
 
                 public #myType# this[int index]
                 {
                          get { return (base.List[index] as #myType#); }
                 }
         }
}

Note that we have already introduced some stubs such as #namespace# and #myType# that will be replaced by whatever string value that the selected class uses as Name identifier. Furthermore we store these templates in the C:\Program Files\Microsoft Visual Studio .NET 2003\VC#\CSharpProjectItems; the setup already deploys these templates there :-)

In a nutshell we created a method called AddCollection() which is called when clicking on the addin menu item. The method firstly gets the reference of the current loaded project:

...              
  // Get the current selected class in the "Class View"
  CodeClass codeClass = 
               (CodeClass)this.VSNET.SelectedItems.SelectionContainer.Item(1);
  // Get the current project (topmost)
  Project prj = 
              (Project)((Array)this.VSNET.ActiveSolutionProjects).GetValue(0);
...

Then, it stores the template(s) full filename path into a string:

...
// Templates path
// Custom collection
string typeCollectionTemplate
            = applicationObject.Solution.ProjectItemsTemplatePath(
                                     VSLangProj.PrjKind.prjKindCSharpProject )
              + "\\MyTypeCollection.cs";
...

Finally, we do some work. Note that we use the ReplaceText three times for the custom type so we can change the namespace, class name and item type that is stored in the collection:

...
try
{
    // Add new ProjectItem from template
    itemCollection = prj.ProjectItems.AddFromFileCopy(typeCollectionTemplate);
    // Assign new file name, if fileExists here it would fail.
    itemCollection.Properties.Item("Filename").Value = codeClass.Name +
                                                              "Collection.cs";
    // Must open as ViewKindCode otherwise it will fail on saving
    string s = itemCollection.Properties.Item("Filename").Value.ToString();

    itemCollection.Open(Constants.vsViewKindCode);
    // Save File.
    itemCollection.Save("");
    // Replace tags. 
    itemCollection.Document.ReplaceText("#namespace#",
                                 codeClass.Namespace.Name + ".Collections",0);
    itemCollection.Document.ReplaceText("#myTypeCollection#",codeClass.Name + 
                                                              "Collection",0);
    itemCollection.Document.ReplaceText("#myType#",codeClass.FullName,0);
}
catch(Exception e)
{
    // Delete File
    itemCollection.Delete();
    // Show Message...Most likely it will be file already included..
    System.Windows.Forms.MessageBox.Show(e.Message);
}
...

Important to notice that this addin will create the collection in a Collections relative namespace i.e.: in a project such as TestProject, the collection will be created in the TestProject.Collections, this can be further customized for your own need. The most important thing is that there is no need to code a custom collection from scratch :-)

Using the code

After installing the addin and writting a class that needs be represented by a collection of its on, you can merely right click on the class and press "Create Collection". Here is an example:

public class AccountRecord
 {
  private decimal _credit = decimal.Zero;
  private decimal _debit = decimal.Zero;
 private string _description = string.Empty;
  public AccountRecord(string description, decimal credit, decimal debit)
  {
   _credit = credit;
   _debit = debit;
   _description = description;
  }

  public decimal Credit
  {
   get{return _credit;}
  }
  public decimal Debit
  {
   get{return _debit;}
  }
  public string Description
  {
   get{return _description;}
  }
 }

After creating the collection we are going to be able to instantiate a AccountRecordCollection (Generated by the addin) and procede:

//Instantiate Collection
AccountRecordCollection recordCollection = new AccountRecordCollection();

//Inicialize it
for(int iCount = 0;iCount < 100 ; iCount++)
recordCollection.Add(new AccountRecord("test" + iCount.ToString(),
                     new Decimal(iCount),new Decimal(iCount)));
//Do Work!
foreach(AccountRecord record in recordCollection)
{
   Console.WriteLine(record.Description);
   Console.WriteLine(record.Credit.ToString("C"));
   Console.WriteLine(record.Debit.ToString("C"));
}
...

Looking forward

As we can see it isnt very hard to create a addin to speed up code writting and no new custom class were introduced here. As I understand this is a simple addin that has helped me quite a bit, however we could make some improvements such as:

  1. Add a VB.NET templates support (just detect the language and add templates in the VB.NET project path)
  2. Customize namespace target (instead of Root.Collections)
  3. Add other templates or Smart code building using dynamic code generation (CodeDom or FileCodeModel). This can be very interesting!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here