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 fustrating it can be.
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!
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.
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 allways 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 fustrating 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 develoment much easier! Questions, comments, concerns, leave me a comment.
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".
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.