The Visual Studio solution file for our software contains 36 projects (and growing). If you've ever tried to find a particular file or project in a 36 project solution when many projects and folders are expanded, then you know how frustrating it can be.
The Solution
After putting up with it for over a year, I finally asked a co-worker of mine if he knew of a way to quickly jump to a particular project in Visual Studio. He reminded me that Visual Studio has excellent macro support. A few minutes later using Visual Studios Macro Recorder feature I had something to jump to a project I'm in a lot. All told, I jump between around 4 projects pretty regularly - our business logic layer, domain model, smart client and UI, and having these macros have been a huge time saver!
The Visual Studio Macro
Sub TemporaryMacro()
DTE.ActiveWindow.Object.GetItem("MySolution\MyProject").UIHierarchyItems.Expanded = True
DTE.ActiveWindow.Object.GetItem("MySolution\MyProject").Select(vsUISelectionType.vsUISelectionTypeSelect)
End Sub
Using this macro, you can straight to the "MyProject" project in your solution. This works, but its not very generic. The solution name is hard coded, which is fine if the particular project only lives in one solution. But in our case, we have 3 or 4 different solutions created. The monster with all 36 solutions, a client only solution with 10 of the projects, and a core only solution.
I don't feel like creating different macros to jump between the same 5 projects depending upon which solution is open. So lets refactor this a little bit.
1 Private Function GetSolution() As UIHierarchyItem
2 Dim win As Window = DTE.Windows.Item(Constants.vsWindowKindSolutionExplorer)
3 Dim uih As UIHierarchy = win.Object
4 GetSolution = uih.UIHierarchyItems.Item(1)
5 End Function
6
7 Private Function GetProject(ByVal ProjectName As String) As UIHierarchyItem
8 GetProject = GetSolution().UIHierarchyItems.Item(ProjectName)
9 End Function
10
11 Private Sub GotoProject(ByVal ProjectName As String)
12 DTE.Windows.Item(Constants.vsWindowKindSolutionExplorer).Activate()
13
14 Try
15 GetProject(ProjectName).Select(vsUISelectionType.vsUISelectionTypeSelect)
16 GetProject(ProjectName).UIHierarchyItems.Expanded = True
17 Catch ex As Exception
18 End Try
19 End Sub
20
21 Public Sub MyProject()
22 GotoProject("MyProject")
23 End Sub
Lets break this down. The GetSolution method returns a UIHierarchyItem (an item in the solution explorer tree you can click on). The first thing we do is tell the DTE (What does DTE stand for btw?) we want the solution explorer window (line 2), once we have that we can get the hierarchy (line 3), then we can get the items and get the first item (line 4) which is the solution item in the hierarchy.
NOTE: The macro environment is based on COM, and in COM collections and arrays start at 1, and not 0.
In the GetProject method, you should see code which looks familiar. The interesting things come in the GotoProject method. First we active the the solution explorer window in the IDE (line 12), then get the project hierarchy item and select it so it has focus (line 15), and finally we expand the item in the hierarchy so you can see all the files under the project (line 16).
And finally on line 21 we have the actual callable macro which will warp us to the project we want in the solution.
A Good First Step
So thats a good first step, but what else can we do? We can collapse all the items in the solution explorer, open up a particular file, or even "fix" items in your solution.
One thing I don't like about Visual Studio is over time, with a large number of projects, a lot of projects, folders and compound items (winforms items with a designer and resource file) get expanded and it makes it especially hard to pick things out of the visual clutter. I always find it to be very tedious to close all the items in the solution to clean up the clutter. Lets write a macro to fix this mess for us.
1 Public Sub CollapseTopLevel()
2 DTE.Windows.Item(Constants.vsWindowKindSolutionExplorer).Activate()
3
4 Dim solutionWindow As EnvDTE.Window = DTE.ActiveWindow
5 solutionWindow.Visible = False
6 Dim solution As UIHierarchyItem = GetSolution()
7
8 CollapseHierarchy(solution.UIHierarchyItems, True, True)
9 solutionWindow.Visible = True
10 DTE.StatusBar.Clear()
11 DTE.StatusBar.Progress(False)
12 End Sub
13 Public Sub CollapseAll()
14 DTE.Windows.Item(Constants.vsWindowKindSolutionExplorer).Activate()
15
16 Dim solutionWindow As EnvDTE.Window = DTE.ActiveWindow
17 solutionWindow.Visible = False
18 Dim solution As UIHierarchyItem = GetSolution()
19
20 CollapseHierarchy(solution.UIHierarchyItems, True, False)
21 solutionWindow.Visible = True
22 DTE.StatusBar.Clear()
23 DTE.StatusBar.Progress(False)
24 End Sub
25 Private Sub CollapseHierarchy(ByRef items As UIHierarchyItems, ByVal IsRoot As Boolean, ByVal OnlyCollapseRootLevel As Boolean)
26 For i As Int32 = 1 To items.Count
27 If IsRoot Then DTE.StatusBar.Progress(True, "Collapsing", i, items.Count)
28 If (items.Item(i).UIHierarchyItems.Count > 0 And Not OnlyCollapseRootLevel) Then
29 DTE.StatusBar.Text = "Collapsing " & items.Item(i).Name
30 CollapseHierarchy(items.Item(i).UIHierarchyItems, False, False)
31 End If
32 items.Item(i).UIHierarchyItems.Expanded = False
33 Next
34 End Sub
There are two different methods here to collapse the solution, CollapseTopLevel and CollapseAll. CollapseTopLevel only collapses project items in the UI, while CollapseAll will drill down and collapse every item. The first is fast, takes less than a second to collapse 36 items, while the later takes about 15 seconds. If you notice lines 4, 5 we grab a reference to the solution window, and then hide it. If the solution window is visible while the projects are collapsed, the whole process takes much longer while the UI repaints.
When working on our data access layer (DAL), I do a lot of editing of our SQL upgrade script. I don't really like to try and find this file in the solution explorer. More than that, when I'm editing this file, I'm always adding to the bottom of this file. Can we do all that with a macro? Sure enough!
1 Public Sub DbScripts()
2 DTE.Windows.Item(Constants.vsWindowKindSolutionExplorer).Activate()
3 Dim project As UIHierarchyItem = GetSolution().UIHierarchyItems.Item("SchemaUpgradeManagerProject")
4 Dim scripts As ProjectItem = project.UIHierarchyItems.Item("Scripts").UIHierarchyItems.Item("DbScripts.sql").Object
5 scripts.Open().Activate()
6 DTE.ActiveDocument.Selection.EndOfDocument()
7 End Sub
One of the frustrating things about creating NHibernate mapping files is remembering to set the build action to Embedded Resource. Or remembering to set the build action of images, sounds and movie clips and other files to Embedded Resource. Why not let a macro fix this mess for you too?
Public Sub FixThings()
Try
Dim project As Project
Dim projectItem As UIHierarchyItem
Dim folder As UIHierarchyItem
projectItem = GetProject("DataAccessProject")
folder = projectItem.UIHierarchyItems.Item("Mappings")
EmbeddResources(folder, "hbm.xml")
project = projectItem.Object
project.Save()
projectItem = GetProject("UserInterfaceProject")
folder = projectItem.UIHierarchyItems.Item("Icons")
EmbeddResources(folder, "png")
project = projectItem.Object
project.Save()
DTE.StatusBar.Clear()
DTE.StatusBar.Progress(False)
Catch ex As Exception
End Try
End Sub
Private Sub EmbeddResources(ByRef folder As UIHierarchyItem, ByVal extension As String)
Dim file As ProjectItem
For i As Int32 = 1 To folder.UIHierarchyItems.Count
file = folder.UIHierarchyItems.Item(i).Object
DTE.StatusBar.Progress(True, "Setting BuildAction in " & folder.Name, i, folder.UIHierarchyItems.Count)
If file.Name.EndsWith(extension) Then
file.Properties.Item("BuildAction").Value = 3 ' Embedded Resource
End If
Next
End Sub
There you have it, some simple Visual Studio macros to make your development much easier! Questions, comments, concerns, leave me a comment.
Your Macros, Now Only a Click Away!
Now that we have all of these macros, we need a way to quickly and easily access them. Custom Visual Studio toolbars to the rescue! To create a custom toolbar, click on Tools -> Customize -> Toolbars. From there click on the "New" button and give it a name. I choose the very original name of "My Macros" :). Dock your toolbar if you'd like.
With that done, click over to "Commands" tab and click on "Macros" in the left-side. All of your macros, as well as the sample macros will show up in the right-side. Drag the macros you want to the toolbar you just created. You'll notice that your macro has a really long name like "Macros.MyMacros.SomeName.CollapseTopLevel." Long names like that will quickly eat up your valuable screen real estate.
Thankfully we can fix that. While still in Customize mode, you can right-click on your macro in the toolbar (and indeed any item in any toolbar) and the third option down is "Name." Set this to what ever you want. Since screen real-estate is precious to me, I named my "Macros.MyMacros.ProjectName.CollapseTopLevel" macro to "ClpseTopLvl".
Update
In my haste to publish this article at around 1 AM I failed to mention how to create a custom tool bar to give you quick access to all of your macros.
11-Feb-2009 - Johnny Idol has referenced this blog post, and gone into detail about how to actually create the macros and bind keyboard shortcuts to them.