Critical Development

Language design, framework development, UI design, robotics and more.

Visual Studio Macro: Locate Item in Solution Explorer on Demand

Posted by Dan Vanderboom on March 21, 2008

[ For an updated macro that works best with VS2008 and VS2010, see the followup article. ]

By default, Visual Studio’s Solution Explorer will update its selected item based on the currently active document.  This is occassionally useful when exploring unfamiliar code, but it’s annoying when it’s always on, bouncing around as you navigate through larger solutions.

Do you want to disable project item tracking?

 

Go to Tools—>Options, expand Projects and Solutions, select General, and then uncheck the box next to Track Active Item in Solution Explorer.

But what if you’re looking at a document, and you want to know where in Solution Explorer it’s located?  Unfortunately, you would have to go back into Tools—>Options, …, turn that option back on, locate the project item, and then turn it back off again.

This is very inconvenient.  What would be ideal is a button or hot key that would jump to that item in Solution Explorer just once, on demand.  “Where’s the corresponding project item for this document?  Find it and then don’t track after that.”

After about two hours of messing around with the VS SDK, trying to read the documentation, finding out that this particular option isn’t available for manipulation through automation, and resolving to figure out another way to do it, I finally accomplished this goal and I’m happy to share the macro that I created.

Don’t know how to create a macro?

 

Adding a macro is easy.  First open the Macro Explorer.  Press Alt-F8, or if you’ve remapped your keys, go to Tools—>Macros—>Macro Explorer.  You’ll probably have a project in there called MyMacros.  If not, right-click on Macros and select New Macro Project…  A window pops up, and you can enter a project name.  Once you have selected or created a macro project, right click on the project name and select New module…  I named mine Utilities.  Right click on the module name and select Edit.  You should now see a VB code editor window.

Copy the following code into the editor and press Ctrl-S to save it.

Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90

Public Module Utilities
    Public Sub TrackProjectItem()
        Dim solution As Solution2 = DTE.Solution
        If Not solution.IsOpen OrElse DTE.ActiveDocument Is Nothing Then Return

        solution.FindProjectItem(DTE.ActiveDocument.FullName).ExpandView()

        Dim FileName As String = DTE.ActiveDocument.FullName

        Dim SolutionExplorerPath As String
        Dim items As EnvDTE.UIHierarchyItems = DTE.ToolWindows.SolutionExplorer.UIHierarchyItems
        Dim item As Object = FindItem(items, FileName, SolutionExplorerPath)

        If item Is Nothing Then
            MsgBox("Couldn't find the item in Solution Explorer.")
            Return
        End If

        DTE.Windows.Item(Constants.vsWindowKindSolutionExplorer).Activate()
        DTE.ActiveWindow.Object.GetItem(SolutionExplorerPath).Select(vsUISelectionType.vsUISelectionTypeSelect)
    End Sub

    Public Function FindItem(ByVal Children As UIHierarchyItems, ByVal FileName As String, ByRef SolutionExplorerPath As String) As Object
        For Each CurrentItem As UIHierarchyItem In Children
            Dim TypeName As String = Microsoft.VisualBasic.Information.TypeName(CurrentItem.Object)
            If TypeName = "ProjectItem" Then
                Dim projectitem As EnvDTE.ProjectItem = CType(CurrentItem.Object, EnvDTE.ProjectItem)
                Dim i As Integer = 1
                While i <= projectitem.FileCount
                    If projectitem.FileNames(i) = FileName Then
                        SolutionExplorerPath = CurrentItem.Name
                        Return CurrentItem
                    End If
                    i = i + 1
                End While
            End If

            Dim ChildItem As UIHierarchyItem = FindItem(CurrentItem.UIHierarchyItems, FileName, SolutionExplorerPath)
            If Not ChildItem Is Nothing Then
                SolutionExplorerPath = CurrentItem.Name + "\" + SolutionExplorerPath
                Return ChildItem
            End If
        Next
    End Function
End Module

 

Now let’s hook it up to a keyboard hot key to make it super fast to access.  If you open Tools—>Options, expand Environment, select Keyboard, and in the text box labeled Show commands containing, type part of the macro name, such as TrackProjectItem.  You’ll see your new macro in the list highlighted.  In the dropdown control labeled Use new shortcut in, select Text Editor.  Click on the box that says Press shortcut keys, and press a key combination.  I chose Alt-T (for Track), which was available in that context to my surprise.

Try it out.  Open up a few code or XML windows.  Make one of those windows active and press your hot key.  The item in Solution Explorer that corresponds to the current document window will activate.

 

Tags: , , ,

47 Responses to “Visual Studio Macro: Locate Item in Solution Explorer on Demand”

  1. RachelP said

    Thanks for the tip on turning off the annoying default ‘find item in solution’ behaviour. The macro, however, needs a bit of polishing; in the current form it expands every node in the solution tree above the point you’re trying to find just to find one file that would be fairly near the top if the search algorithm left unrelated nodes alone – this is more annoying than the original default behaviour!

  2. Dan Vanderboom said

    When you say “it expands every node in the solution tree above the point you’re trying to find”, what do you mean by “above”? If you mean all ancestor nodes (parent, parent’s parent, etc.), then I’d say it has to expand those nodes, otherwise the child you’re trying to find couldn’t be displayed.

    If, however, you mean all nodes in the tree vertically above it in the control, I’m not getting that behavior unless I have previously expanded those nodes. The macro doesn’t auto-collapse all nodes except for the path of nodes leading to the one you’re looking for. You could easily do so with a minor change to the code, but I didn’t want to mess with the current expansion of nodes, in case the developer was working with certain areas of code and wanted to keep those areas of the tree expanded.

    If you collapse all levels of nodes, and then use the macro, it should only expand the nodes necessary to reveal the current document’s file. If you had expanded other subtrees of nodes, and then collapsed a parent node, those subtrees will remember that they’re expanded when the parent is expanded, whether you use the macro or expand the parent node yourself.

    The only problem I see with my macro is that dependent files (.resx and .designer.cs for forms, for example) “below” the primary file in the hierarchy also get expanded, but this is a single click to fix.

  3. Thomas G. Mayfield said

    Just what I was looking for (well, kinda. I was writing the same thing and you showed up as a search result for a specific question, but it’s your code driving it now, not mine). To fix the expanding child items (.Designer.cs, .resx) issue for the selected item:


    solution.FindProjectItem(DTE.ActiveDocument.FullName).ExpandView()

    with:


    self = solution.FindProjectItem(DTE.ActiveDocument.FullName)
    parent = self.Collection.Parent
    If parent Is Nothing Then
    ' Unlikely, but works as a guard clause
    self.ExpandView()
    Else
    parent.ExpandView()
    End If

  4. Thomas G. Mayfield said

    Missed declarations. D’oh.

    Entire block:

    Dim self As ProjectItem
    Dim parent As ProjectItem

    self = solution.FindProjectItem(DTE.ActiveDocument.FullName)
    parent = self.Collection.Parent
    If parent Is Nothing Then
    self.ExpandView()
    Else
    parent.ExpandView()
    End If

  5. Thomas G. Mayfield said

    Bah. Ignore all of that. It is perfectly possible to keep items inside directories from expanding, but the above code will bug out if the item’s parent is the Project node. I’ll play with it some more.

  6. Thomas G. Mayfield said

    To not show the child items of the currently selected item almost all the time:

    Public Sub ExpandView(ByRef document As EnvDTE.Document)
    ExpandView(document.ProjectItem)
    End Sub

    Public Sub ExpandView(ByVal item As ProjectItem)
    If (item.ProjectItems.Count = 0) Then
    ' We don't care about it accidentally expanding child items
    item.ExpandView()
    Return
    End If

    If TryExpandParent(item) Then
    Exit Sub
    ElseIf TryExpandSibling(item) Then
    Exit Sub
    End If

    ' Side effects be damned
    item.ExpandView()
    End Sub

    Private Function TryExpandSibling(ByVal item As ProjectItem) As Boolean
    ' Try and find a file next to the current one that doesn't have any child items

    For Each sib As ProjectItem In item.Collection
    If sib.ProjectItems.Count = 0 Then
    sib.ExpandView()
    Return True
    End If
    Next

    Return False
    End Function

    Private Function TryExpandParent(ByVal item As ProjectItem) As Boolean
    Dim parent As Object
    parent = item.Collection.Parent

    If TypeOf parent Is ProjectItem Then
    parent.ExpandView()
    Return True
    End If

    Return False
    End Function

    Use “ExpandView(solution.FindPRojectItem(DTE.ActiveDocument.FullName)” (or “ExpandView(DTE.ActiveDocument.ProjectItem)” instead of “solution.FindProjectItem(DTE.ActiveDocument.FullName).ExpandView()”. If the item has no child items, it’ll expand normally. If it does, it’ll first try and expand the parent (which doesn’t work for items whose parent is the Project file). Failing that, it’ll try and find an item at the same level of the hierarchy without any child items and expand that, which has the same effect. If all of that fails, it just expands the selected item, child items included.

  7. Maggie said

    Yay! I agree, it would be great ot have a button (like the Eclipse IDE button) to toggle the tracking on and off so you can use it when you want.

    Thanks for the tip!

  8. Alex Naydenov said

    Very useful macro, thanks for that!

    Just one amendment, based on the first comment: ‘it expands every node in the solution tree above the point you’re trying to find ‘. I added a module level variable to hold the current project like this:


    'keep a reference to the current project
    Dim project As EnvDTE.UIHierarchyItem

    Also amended the FindItem method like this:


    If TypeName = "Project" Then
    If Not (project Is Nothing) Then
    'when the project changes collapse the previous project if not null
    project.UIHierarchyItems.Expanded = False
    End If
    project = CurrentItem
    Debug.Print(project.Name)
    ElseIf TypeName = "ProjectItem" Then
    Dim projectitem As EnvDTE.ProjectItem = CType(CurrentItem.Object, EnvDTE.ProjectItem)
    Dim i As Integer = 1
    While i <= projectitem.FileCount
    If projectitem.FileNames(i) = FileName Then
    SolutionExplorerPath = CurrentItem.Name
    Return CurrentItem
    End If
    i = i + 1
    End While
    End If

    It helps me to collapse all projects prior to the item I am looking for.

    Regards,
    Alex

  9. dan said

    This is precisely what I was looking for, and after popping in Alex’s suggestion, it works exactly as I hoped. Thanks for sharing!

  10. John Rusk said

    Thanks!

  11. clem said

    Excellent

  12. Andrew said

    Another work-around without a macro, but more of a pain, is to just bind Alt+T to the existing action:

    View.TrackActivityInSolutionExplorer

    This toggles the aforementioned option to turn tracking on or off.

  13. tsohr said

    it was very helpful.
    i was surprised about the project, projectitems, editpoint and many object i don’t know it is.
    i couldn’t leave a trackback so, here is my article
    http://tsohr.tistory.com/243
    sorry for my short of english 😉
    thanks!

  14. Kevin said

    How about just toggling the View.TrackActivityInSolutionExplorer on then back off w/ a macro? (Make sure it’s set to off initially.)

    This works much faster for large solutions and projects too.


    Imports EnvDTE
    Imports EnvDTE80
    Imports EnvDTE90

    Public Module Utilities
    Public Sub TrackProjectItem()

    DTE.ExecuteCommand("View.TrackActivityInSolutionExplorer")
    DTE.ExecuteCommand("View.TrackActivityInSolutionExplorer")

    End Sub
    End Module

    • Pratik Chandra said

      You know what…you just made me feel like an absolute idiot…what you said is just so coool.How come, I never thought of this.
      I just assigned some easy shortcut to the default vs feature : “View.TrackActivityInSolutionExplorer“, and simply use it twice to visual studio do EXACTLY what I wanted. No macros or anything required!!!

      “ctrl+shift+D” is my key-combination.

      million thanks kevin…Cheers!!!!!!

  15. Bri said

    I have a question on a related topic of context menus in Visual Studios..for add-ins
    I want to add a popup menu to the solution explorer as an add-in. I can do this fine -without problem in the OnConnection method
    However I want to add the menu at runtime -the contents of the menu depend on the selected Project CommandBar.
    Does anyone know the method or event method i can put my code in so that the popup menu is created when you right-click
    on one of the Project Commandbars in your solution explorer…? Foe example – a method that is invoked if i right-click on one of the
    CommandBars?

  16. aditya said

    Thanx for the tip on turning off the active item tracking feature.

    That was one annoying thing.

  17. Hello said

    Thanks a lot !

  18. JackieLee said

    Thanks, Dan, this page really helped me out.

    However, instead of the macro (which for me was taking a very long time in a large solution, and then crapped out before finding the item), I prefer Andrew’s solution (#12) of mapping a shortcut key to the View.TrackActivityInSolutionExplorer command. I’m not sure why it’s “more of a pain”, as Andrew put it, because I find it quite handy. It does require you to remember the shortcut key and also remember to turn it off.

    It’s also possible to create a toolbar button for the command (as Maggie #7 wished for) by going to Tools -> Customize -> Commands tab -> View category, and drag-and-drop the “Track Activity in Solution Explorer” command to a toolbar. This Customize dialog also allows editing of the icon through the Modify Selection button, although you have to put the button on the toolbar before you can setup an icon. Having the toolbar button eliminates having to remember the shortcut key, and the icon also indicates when the command is active or not.

    • Dan Vanderboom said

      @JackieLee,

      When I wrote this, I’m not sure the TrackActivityInSolutionExplorer was available. Not sure exactly! 🙂

  19. heimlifeiss said

    Thanks Dan, your macro helped me a lot.

    I had some performance issues though…getting the SolutionExplorerPath took quite long sometimes, so I made those changes:
    Instead of

    Dim item As Object = FindItem(items, FileName, SolutionExplorerPath)

    I use

    Dim SolutionExplorerPath As String = GetSolutionExplorerPath(solution, DTE.ActiveDocument.FullName)

    and

    Public Function GetSolutionExplorerPath(ByVal Solution As Solution2, ByVal FileName As String) As String
    Dim sln As String = Mid(Solution.FullName, InStrRev(Solution.FullName, "\") + 1)
    Dim slnPath = Left(Solution.FullName, InStrRev(Solution.FullName, "\"))
    'Remove .sln
    Return Left(sln, Len(sln) - 4) & "\" & Right(FileName, Len(FileName) - Len(slnPath))
    End Function

    I don’t know if this is a general approach, but it seems to work for me…and it is much faster than iterating through all ProjectItems…

  20. Nabeel said

    Thanks very much for the tip. It really helped !

  21. Thank you a lot!

  22. Thank you! Very useful tip.

  23. […] Find Current Item in Solution Explorer […]

  24. guewst said

    this doesn’t work on me, Visual 2005, nothing happend

  25. V. Ogız TOKMAK said

    Thank you, exactly what I was looking for 🙂

  26. Dale said

    There is an easier way to do this. Create a macro as follows:


    Public Sub SelectCurrentItemInSolutionExplorer()
    DTE.ExecuteCommand("View.TrackActivityinSolutionExplorer")
    DTE.ExecuteCommand("View.TrackActivityinSolutionExplorer")
    End Sub

    and assign it to a toolbar button. It essentially toggles the track item option which will select the item in the solution explorer.

  27. Andres said

    Great, Dan!!

    It worked!

    Thanks so much

  28. […] Visual Studio Macro: Locate Item in Solution Explorer on Demand « Critical Development (tags: visualstudio c# macro productivity tricks vs.net 2008) […]

  29. jutsi said

    Amended one line in FindItem so that document for code behind is found in solution browser (vs 2008):

    If projectitem.FileNames(i) = FileName Or projectitem.FileNames(i) = System.IO.Path.GetDirectoryName(FileName) + System.IO.Path.DirectorySeparatorChar + System.IO.Path.GetFileNameWithoutExtension(FileName) Then

  30. Sandy said

    Between your original post and Kevin’s comment (14), got the job done perfectly. Thanks.

  31. Sidney said

    I just got an error from line “solution.FindProjectItem(DTE.ActiveDocument.FullName).ExpandView()”.

    HRESULT E_FAIL

    Is there anyone like me?

    • Rob said

      @sidney: yes, same problem here. Also the solution with the two DTE.ExecuteCommand is not working.

      • Rob said

        Well… no. The macro with the two DTE.ExecuteCommand IS working but with small changes more solid:

            Public Sub TrackProjectItem2()
                DTE.ExecuteCommand("View.TrackActivityinSolutionExplorer", True)
                DTE.ExecuteCommand("View.TrackActivityinSolutionExplorer", False)
                DTE.ExecuteCommand("View.SolutionExplorer")
            End Sub
        
  32. evan said

    i found andrew’s and jackielee’s suggestion of mapping the keyboard shortcut very helpful. thanks very much all above for the helpful comments!

  33. mila said

    Great macro.
    I recently found out that vscommands can do the same thing too http://visualstudiogallery.msdn.microsoft.com/en-us/d491911d-97f3-4cf6-87b0-6a2882120acf

  34. Ido Y said

    Why not simply binding a shortcut key to FindInSolutionExplorer?

  35. BhaaL said

    Ido Y: I’d really like to do that. At work I have the entry, but I don’t at home; nor do I have it on another machine.

    Any idea why/when it shows up, and why/when it does not?

  36. FrancoisV said

    you could also bind ‘View.TrackActivityInSolutionExplorer’ to a key and press it 2 times to auto-expand the current source file in the solution explorer. This flag is a toggle so calling it 2 times will put it back to disable while(it cause it to execute the first time).

    Take 2 sec do do 🙂

    My 2 cent

  37. Luke said

    Awesome! Thank you!

  38. This is great!

  39. MaiT said

    If you are using resharper then you can use shift+alt+L to locate an item in the solution explorer.

  40. buyitfromme…

    […]Visual Studio Macro: Locate Item in Solution Explorer on Demand « Critical Development[…]…

  41. Ray said

    Perfect, the macro by Dan Vanderboom works fine. I found other solutions to do extra settings etc. perfectly works for me

  42. Excellent!!!

  43. DarrenMB said

    Thanks for this excellent macro.
    Was looking for exactly this so I could turn off the annoying “Track Active Item in Solution Explorer” option in visual studio.

    The previously mentioned “View.TrackActivityInSolutionExplorer” also works well when mapped to a keyboard shortcut.

Leave a reply to buyitfromme Cancel reply