# Sunday, May 20, 2007

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.

Collapsing the Solution Explorer

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.

Opening a Particular File

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

Fixing Things in the Solution

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".


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.

Sunday, May 20, 2007 1:12:32 AM (Alaskan Daylight Time, UTC-08:00)
# Sunday, May 13, 2007

I purchased the Pioneer CD-IB100II iPod adapter today from Image Audio for my Pioneer DEH-4800MP car stereo.  I had only used the thing for a total of 10 seconds when I decided I absolutely *hated* how it operates.  I very much disliked the fact that I had to use my stereo to control which song/podcast was being played.  It wouldn't have been so bad that I was forced to use the stereo to control which song was being played, except that when scrolling through the songs, it plays them.

  This is really bad for podcasts.  Playing a podcast marks it as not-new and makes it hard to figure out which ones need to be listened to.

  I was talking to a friend of mine while installing the unit, and he looked on the interweb and turns out that I'm not the only one with this complaint.  Well, some enterprising fellow out there figured out that if you take apart the iPod adapter and snip the blue wire that goes out to the iPod (or in my case I disconnected the from the connector and e-taped it), you get all the benefits of the iPod adapter (charging and line-level outputs) with none of the draw-backs (crappy interface).  So now I can control what song is being played on iPod and not the adapter!

  There is one drawback to disconnecting the blue wire...  The stereo now displays "ERROR-11" a minute or two after power on.  The error message is no big deal, the audio from the iPod still plays and can be heard through the car stereo, iPod still charges, and I get to use the nice iPod interface!

geek | nerd
Sunday, May 13, 2007 10:30:21 PM (Alaskan Daylight Time, UTC-08:00)