I'm Keyvan Nayyeri, a 25 years old Ph.D. student at
the Computer Science department of
the University of Texas at San Antonio.
I'm also
a Software Architect and Developer and previously held a B.Sc.
degree in Applied Mathematics.
This is my blog where I publish content about various topics specifically Programming Languages and Compilers, Software
Engineering and Programming.
One of the early feedbacks about my book was from Brennan Stehling who told me that it's good to write more about Visual Studio Extensibility on my blog while VSX community is activated to improve this topic this year.
I have lots of ideas to write about them on this blog but unfortunately my time is very very limited and I have to be honored for managing my time with this busy schedule!
But Brennan's comment was absolutely right. Now that I've written a book about VSX which is the only book dedicated to this book, it would be a good opportunity to use the little popularity of my blog and attention of my new subscribers to help VSX community to some extent. I know that Visual Studio Ecosystem team is doing a great job for the future of VSX and you'll see more from them in the near future and I want to help them as much as I can.
So here I begin writing a post series about VSX with a primary focus on common concepts and simplicity. While I covered many of the important topics in my book, here I try to use a practical approach to show those concepts by example and also show some tips and tricks that are not covered in the book. Of course, I'm writing on Professional Visual Studio blog as well so don't miss our content if you'd like to discover more details about VS and VSX.
Let's begin with a simple example which is a real world case for myself and probably for others. It's a Visual Studio add-in called Simpler Code.
One of the common tasks that I always do when write my programming code is giving it a good organization and make it simple. I achieve this by applying some tasks as follows:
This is very simple but gives a good look to my code and helps me and others read them easily. Moreover, for some sample code that I insert in my blog posts, this can prevent some strange line breaks that appear in the code.
This is simple when you talk or even write about it but you agree that it will be just a waste of time if you do this for various code files manually. Especially if you decide to apply this to all files in a solution with million lines of code!
So the question is how to automate this? And here is where VSX comes into the play!
Here and in this post I want to write a very simple Visual Studio add-in to automate these tasks and show a few concepts that may help you. Of course, there is a lot of space to improve this add-in but more than its application, I do care about its concepts even though I personally use it for my own projects and it has worked for me very well.
Before following the progress, let me give a short background. You probably have heard about the recent release of PowerCommands for Visual Studio (I wrote about it here) which is available as an open source project on MSDN code gallery. Features introduced in this project (which is actually a VSPackage) are a built-in part of Visual Studio 2008 Team Suite but I don't know about other editions. This project simply adds some features like reference organization (second and third steps of abovementioned process) to your IDE. If your IDE doesn't have such features, you can download and install PowerCommands and enjoy using them.
I ignore the process of describing how to create a new add-in project and passing the wizard and step directly into the implementation.
But the implementation! The implementation should consist of some simple steps as I outline:
This does the job in a simple and effective form but is more theoretical than what I will implement.
Regarding this multi-step process, the code wouldn't be so hard to write. Below is the long source code of my add-in but don't worry about its length because most of the code is auto-generated by Visual Studio. I'll talk about the hand-written code after it.
using System;
using Extensibility;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.CommandBars;
using System.Resources;
using System.Reflection;
using System.Globalization;
using System.Diagnostics;
namespace SimplerCode
{
public class Connect : IDTExtensibility2, IDTCommandTarget
{
public Connect()
{
}
public void OnConnection(object application, ext_ConnectMode connectMode,
object addInInst, ref Array custom)
{
_applicationObject = (DTE2)application;
_addInInstance = (AddIn)addInInst;
if (connectMode == ext_ConnectMode.ext_cm_UISetup)
{
object[] contextGUIDS = new object[] { };
Commands2 commands = (Commands2)_applicationObject.Commands;
string toolsMenuName;
try
{
string resourceName;
ResourceManager resourceManager = new ResourceManager("SimplerCode.CommandBar",
Assembly.GetExecutingAssembly());
CultureInfo cultureInfo = new CultureInfo(_applicationObject.LocaleID);
if (cultureInfo.TwoLetterISOLanguageName == "zh")
{
System.Globalization.CultureInfo parentCultureInfo = cultureInfo.Parent;
resourceName = String.Concat(parentCultureInfo.Name, "Tools");
}
else
{
resourceName = String.Concat(cultureInfo.TwoLetterISOLanguageName, "Tools");
}
toolsMenuName = resourceManager.GetString(resourceName);
}
catch
{
toolsMenuName = "Tools";
}
Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar =
((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars)["MenuBar"];
CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName];
CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;
try
{
Command command = commands.AddNamedCommand2(_addInInstance, "SimplerCode",
"Simpler Code", "Executes the command for SimplerCode", true, 59,
ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported +
(int)vsCommandStatus.vsCommandStatusEnabled,
(int)vsCommandStyle.vsCommandStylePictAndText,
vsCommandControlType.vsCommandControlTypeButton);
if ((command != null) && (toolsPopup != null))
{
command.AddControl(toolsPopup.CommandBar, 1);
}
}
catch (System.ArgumentException)
{
}
}
}
public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
{
}
public void OnAddInsUpdate(ref Array custom)
{
}
public void OnStartupComplete(ref Array custom)
{
}
public void OnBeginShutdown(ref Array custom)
{
}
public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText,
ref vsCommandStatus status, ref object commandText)
{
if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
{
if (commandName == "SimplerCode.Connect.SimplerCode")
{
status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported |
vsCommandStatus.vsCommandStatusEnabled;
return;
}
}
}
public void Exec(string commandName, vsCommandExecOption executeOption,
ref object varIn, ref object varOut, ref bool handled)
{
handled = false;
if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
{
if (commandName == "SimplerCode.Connect.SimplerCode")
{
SimplifyCode();
handled = true;
return;
}
}
}
private void SimplifyCode()
{
foreach (Project project in this._applicationObject.Solution.Projects)
{
foreach (ProjectItem item in project.ProjectItems)
{
ApplyRecursive(item);
}
}
}
private void ApplyRecursive(ProjectItem item)
{
if (item.Name.EndsWith(".cs") || item.Name.EndsWith(".vb"))
{
try
{
Window win = item.Open(EnvDTE.Constants.vsViewKindCode);
win.SetFocus();
this._applicationObject.DTE.ExecuteCommand("Edit.RemoveAndSort", "");
this._applicationObject.DTE.ExecuteCommand("Edit.FormatDocument", "");
item.Save(item.Name);
win.Close(vsSaveChanges.vsSaveChangesNo);
}
catch
{
// There is no doubt that we have exceptions here
// but simply ignore them for simplicity!
}
}
foreach (ProjectItem childItem in item.ProjectItems)
{
ApplyRecursive(childItem);
}
}
private DTE2 _applicationObject;
private AddIn _addInInstance;
}
}
But in the above code, I just added two simple methods and added a call reference to Exec method to let my add-in work when user chooses the add-in item from Tools menu or add-in command is executed.
SimplifyCode is a method that iterates through all projects in the current solution and all project items in every project and calls ApplyRecursive method by passing the project item. Here there is nothing to mention.
private void SimplifyCode()
{
foreach (Project project in this._applicationObject.Solution.Projects)
{
foreach (ProjectItem item in project.ProjectItems)
{
ApplyRecursive(item);
}
}
}
But ApplyRecursive is the the heart of this implementation. It checks the project file name to see if it's a C# or VB source code file then follows a multi-step process to organize the code in that file.
The process consists of these steps:
After this, I called ApplyRecursive method recursively for all child project items as well.
private void ApplyRecursive(ProjectItem item)
{
if (item.Name.EndsWith(".cs") || item.Name.EndsWith(".vb"))
{
try
{
Window win = item.Open(EnvDTE.Constants.vsViewKindCode);
win.SetFocus();
this._applicationObject.DTE.ExecuteCommand("Edit.RemoveAndSort", "");
this._applicationObject.DTE.ExecuteCommand("Edit.FormatDocument", "");
item.Save(item.Name);
win.Close(vsSaveChanges.vsSaveChangesNo);
}
catch
{
// There is no doubt that we have exceptions here
// but simply ignore them for simplicity!
}
}
foreach (ProjectItem childItem in item.ProjectItems)
{
ApplyRecursive(childItem);
}
}
Let me talk about some points that may be questions for you. Actually such points were my main purpose of writing this post.
In Visual Studio, we deal with different project item files such as code files, resources or other types but here you would note that a folder is also a project item in the DTE (Development Tools Extensibility). In Visual Studio a project item can have other project items as its children so we need to use a recursive method to apply our operations to all these items.
The other point is about opening the file in a window. The commands that I used to organize my code only work when an appropriate file type (i.e. code file) is open and has a focus on it otherwise they throw exceptions. There are some tricks around this but I used the simplest method to keep my code clean. Open method is a method that gets a string value to open the file. DTE has some constants for this argument. A normal developer and user of Visual Studio wouldn't notice the difference between different open file types but a VSX developer would know that in Visual Studio we may open our files in a code editor, in a text editor, in a debugger or in a designer (like Windows Forms designer) so we need to pass an appropriate value here.
The other point is about the window, itself. We can't apply our commands unless the window has a focus so we need to move focus to it. Of course, this also opens the window in the IDE if it's not open. The side-effect is having a window open that user doesn't want it to be open so we need to close it later. If you run this add-in for larger projects you notice several open and close tasks in your IDE which isn't very good but I chose this approach for simplicity.
There are two commands in the IDE that remove and sort Using references (Edit.RemoveAndSort) and format the document (Edit.FormatDocument). I run these commands to organize my code. Since they don't need any arguments, I also pass an empty string as their arguments. In Visual Basic you could simply ignore this parameter since it's optional but here I need to pass the empty string (for those who blame Visual Basic!). Note that "null" doesn't work and throws an exception.
To have a really automated add-in, I also need to save my changes into the file and close the window and here job is almost done.
Believe it or not but this is a general code that throws exceptions when you run it for real world projects. But the Visual Studio mechanism to run add-ins doesn't care about exceptions and end user sees nothing. I used a try...catch block just to note this simple fact. If you open the source code and insert a break point inside the catch block then you would be able to see the exceptions.
To test this add-in, I create a class library project and check its default Class1.cs code.

Now I choose to run my add-in from Tools menu.

To make sure about the my add-in, I also close the source code file. After running the add-in, I open it again and get what I expected to get!

The source code of this add-in is available for download here. Please note that this add-in is available as is without guarantee and just as an example so don't forget to get a back up of your code before applying it!
Josh
Apr 11, 2008 12:48 PM
#
Or use Resharper: Ctrl + Alt + F
Keyvan Nayyeri
Apr 11, 2008 12:50 PM
#
@Josh:
Good tip, I didn't know that!
Thanks :-)
Dew Drop - April 12, 2008 | Alvin Ashcraft's Morning Dew
Apr 12, 2008 3:28 PM
#
Pingback from Dew Drop - April 12, 2008 | Alvin Ashcraft's Morning Dew
Reflective Perspective - Chris Alcock » The Morning Brew #72
Apr 13, 2008 10:53 PM
#
Pingback from Reflective Perspective - Chris Alcock » The Morning Brew #72
Thorn
Apr 14, 2008 8:17 AM
#
Damn this resharper - turtle with half functions already implemented in VS.
igorbrejc.net » Friday Goodies - 18. March
Apr 18, 2008 3:01 AM
#
Pingback from igorbrejc.net » Friday Goodies - 18. March
Mehfuz
Apr 15, 2009 2:09 PM
#
Nice post !! Sure helpful
Amir
Mar 16, 2010 12:10 AM
#
tnx for your post
source code link was broken
Leave a Comment