none
UIHierarchyItem from a ProjectItem

    Question

  • Hi,
     
    I need to find a UIHierarchyItem from a ProjectItem. My current solution involves getting the UIHierarchy from the Solution Explorer, and searching depth-first for the hierarchy item with a corresponding project item. For a large project it takes 15-20 seconds to find it, and the Solution Explorer window flickers the whole time.
     
    Is there a better way?
     
    Ivo
    Saturday, January 07, 2006 3:35 AM

Answers

  • This version of FindHierarchyItem works with solution folder and is faster than the recursive version (probably just as fast at the first version above, that doesnt work). For free you get my OnLocateInSolutionExplorer method:-)

          
    public void OnLocateInSolutionExplorer() 
            { 
             
                ProjectItem projectItem = _DTE.ActiveDocument.ProjectItem; 
     
                UIHierarchyItems solutionItems = _DTE.ToolWindows.SolutionExplorer.UIHierarchyItems; 
     
                // 
                // ExpandView dont (always) work it item is inside solution folders. 
                // The hack below worked, but it expanded all solution folders, and since we walk the solution explorer 
                // in FindHierarchyItem we might as well expand at the same time 
                // 
                //try 
                //{ 
                //    proItem.ExpandView(); 
                //} 
                //catch (Exception) 
                //{ 
                //    //bug: expand project dont work if a soultion folder is parent and it hasnt been expanded yet 
                //    LoadSolutionItems(items); 
                //    proItem.ExpandView(); 
                //} 
     
                //check if we have a solution 
                if (solutionItems.Count != 1) 
                    return
     
                //FindHierarchyItem expands nodes as well (it must do so, because children arent loaded until expanded) 
                UIHierarchyItem uiItem = FindHierarchyItem(solutionItems.Item(1).UIHierarchyItems, projectItem); 
     
                if (uiItem != null
                { 
                    //if we called DoDefaultAction in FindHierarchyItem, solution explorer will have focus 
                    //set it back to  the dicument 
                    projectItem.Document.Activate(); 
     
                    //didnt find another way to make the item gray (while not having focus) 
                    _DTE.ExecuteCommand("View.TrackActivityinSolutionExplorer""True"); 
     
                    //if item is already selected, View.TrackActivityinSolutionExplorer wont make the item grey, 
                    //so deselect it first 
                    if (uiItem.IsSelected) 
                        uiItem.Select(vsUISelectionType.vsUISelectionTypeToggle); 
     
                    //selecting while View.TrackActivityinSolutionExplorer selects and makes it grey 
                    uiItem.Select(vsUISelectionType.vsUISelectionTypeSelect); 
                    //done with it now 
                    _DTE.ExecuteCommand("View.TrackActivityinSolutionExplorer""False"); 
                    
                } 
                else 
                { 
                    MessageBox.Show("could not find item in solution explorer"); 
                } 
                    
            } 
     
            UIHierarchyItem FindHierarchyItem(UIHierarchyItems items, object item) 
            { 
                // 
                // Enumerating children recursive would work, but it may be slow on large solution. 
                // This tries to be smarter and faster 
                // 
     
                Stack s = new Stack(); 
                CreateItemsStack(s, item); 
     
                UIHierarchyItem last = null
                while (s.Count != 0) 
                { 
                    if (!items.Expanded) 
                        items.Expanded = true
                    if (!items.Expanded) 
                    { 
                        //bug: expand dont always work... 
                        UIHierarchyItem parent = ((UIHierarchyItem)items.Parent); 
                        parent.Select(vsUISelectionType.vsUISelectionTypeSelect); 
                        _DTE.ToolWindows.SolutionExplorer.DoDefaultAction(); 
                    } 
     
                    object o = s.Pop(); 
     
                    last = null
                    foreach (UIHierarchyItem child in items) 
                        if (child.Object == o) 
                        { 
                            last = child; 
                            items = child.UIHierarchyItems; 
                            break
                        } 
                } 
     
                return last; 
            } 
     
            void CreateItemsStack(Stack s, object item) 
            { 
                if (item is ProjectItem) 
                { 
                    ProjectItem pi = (ProjectItem)item; 
                    s.Push(pi); 
                    CreateItemsStack(s, pi.Collection.Parent); 
                } 
                else if (item is Project) 
                { 
                    Project p = (Project)item; 
                    s.Push(p); 
                    if (p.ParentProjectItem != null
                    { 
                        //top nodes dont have solution as parent, but is null 
                        CreateItemsStack(s, p.ParentProjectItem); 
                    } 
                } 
                else if (item is Solution) 
                { 
                    //doesnt seem to ever happend... 
                    Solution sol = (Solution)item; 
                } 
                else 
                { 
                    throw new Exception("unknown item"); 
                } 
            }  

    Friday, August 01, 2008 9:41 AM
  • You will want to do a QI.

    Craig

    Tuesday, January 10, 2006 5:15 AM

All replies

  • While you can go from a UIHierarchyItem to a ProjectItem, there is no way to go in reverse. There are a few reasons for this, such as a project can be hidden from the solution explorer window, and so going from the ProjectItem to the UIHierarchyItem is impossible, but mostly it was because the project objects were in place long before the UIHierarchyItem object was added to the object model, and adding new methods to the ProjectItem object would break compatibility with existing code, so we never added it.

    Craig

    Tuesday, January 10, 2006 2:27 AM
  • Thanks. I was expecting that...

    Now another related question - if I have a ProjectItem how do I get the parent ProjectItem? And can I assume that the hierarchy of the UIHierarchyItems is the same as the hierarchy of the ProjectItems?

    If I have both of these I can do a much smarter search.

    Ivo

     

    Tuesday, January 10, 2006 2:41 AM
  • You can call ProjectItem.Collection to get to the collection containin the item (a ProjectItems object), then call Parent to get to the item representing the collection. Be careful with what you do with this object! It can be either a ProjectItem or a Project object, depending on where you are in the hierarchy.

    Craig

    Tuesday, January 10, 2006 2:55 AM
  • Do I do QueryInterface to find which one it is, or just compare the pointer to get_ContainingProject?

    Thanks

    Ivo

    Tuesday, January 10, 2006 2:58 AM
  • You will want to do a QI.

    Craig

    Tuesday, January 10, 2006 5:15 AM
  • Takes milliseconds on a solution with 15 projects/6000+ files. Pass a ProjectItem as the second parameter. (See below for a sample call.

    Code Block

    public static EnvDTE.UIHierarchyItem FindHierarchyItem( EnvDTE.UIHierarchyItems items, object item )
    {
        if ( items == null || item == null )
            return null;

        EnvDTE.UIHierarchyItem hitem = ( from i in items.Cast()
                                         where i.Object == item
                                         select i )
                                         .FirstOrDefault();

        if ( hitem != null )
            return hitem;

        EnvDTE.UIHierarchyItem parent = null;

        if ( item is EnvDTE.ProjectItem )
        {
            parent = FindHierarchyItem( items, ( item as EnvDTE.ProjectItem ).Collection.Parent );
        }
        else if ( item is EnvDTE.Project )
        {
            EnvDTE.UIHierarchyItem result = ( from child in items.Cast()
                                              where !( child.Object is EnvDTE.Project )
                                              where !( child.Object is EnvDTE.ProjectItems )
                                              select FindHierarchyItem( child.UIHierarchyItems, item ) )
                                              .FirstOrDefault();

            return result;
        }
        else if ( item is EnvDTE.Solution )
        {
            // solutions have no parent
            return null;
        }
        else
        {
            parent = ( from child in items.Cast()
                       where child != null
                       select FindHierarchyItem( child.UIHierarchyItems, item ) )
                       .FirstOrDefault();
        }

        if ( parent != null )
        {
            bool expanded = parent.UIHierarchyItems.Expanded;

            if ( !expanded && parent.UIHierarchyItems.Count == 0 )
            {
                try
                {
                    parent.UIHierarchyItems.Expanded = true;
                }
                catch
                {
                }
            }

            EnvDTE.UIHierarchyItem found = FindHierarchyItem( parent.UIHierarchyItems, item );

            if ( !expanded && parent.UIHierarchyItems.Expanded )
            {
                try
                {
                    parent.UIHierarchyItems.Expanded = false;
                }
                catch
                {
                }
            }

            return found;
        }

        return null;
    }

     

     

    Call the function like this:

     

    Code Block

    // Find the project item for the document

    EnvDTE.ProjectItem item = applicationObject.Solution.FindProjectItem( filename );

    // Get the UIHierarchyItems

    EnvDTE.UIHierarchy uiHierarchy = applicationObject.ToolWindows.SolutionExplorer;

    EnvDTE.UIHierarchyItems items = uiHierarchy.UIHierarchyItems;

    // Convert the ProjectItem into UIHierarchyItem

    EnvDTE.UIHierarchyItem hitem = SolutionFileListControl.FindHierarchyItem( items, item );

     

     

    Thursday, December 13, 2007 3:17 PM
  • I was able to do the same using much simpler solution (VS 2008):

     

    Code Block

    item.ExpandView();

    var hitem = _findUIHierarchyItem(items, item);

    ...

     

    UIHierarchyItem _findUIHierarchyItem(UIHierarchyItems items, ProjectItem item) {

    foreach (UIHierarchyItem child in items) {

    if (child.Object == item) return child;

    var result = _findUIHierarchyItem(child.UIHierarchyItems, item);

    if (result != null) return result;

    }

    return null;

    }

     

    My only problem with this is when there are "Solution Folders" (i.e. virtual folders, VS lets you create) and they are collapsed - ExpandView() throws exception. Note that the solution above (by 280Z28) returns null in such case (the clause handling Project, returns no result because "items" collection is empty).

     

    The only workaround I could find is to perform fake expand/collapse of "Solution Folders" using the following method. It's not as slow as expanding the whole tree, so it will perform OK in most cases

     

    Code Block

    try {

       item.ExpandView();

    } catch {

       _loadSolutionItems(items);

       item.ExpandView();

    }

    var hitem = _findUIHierarchyItem(items, item);

    ...

     

    void _loadSolutionItems(UIHierarchyItems items) {

      var xitems = new List<UIHierarchyItem>();

      foreach (UIHierarchyItem child in items) {

         var pi = child.Object as ProjectItem;

         var pis = child.Object as ProjectItems;

         var project = child.Object as Project;

     

         var expand = false;

         if ((pi != null) && (pi.Kind == Constants.vsProjectItemKindSolutionItems)) expand = true;

         if ((pis != null) && (pis.Kind == Constants.vsProjectItemsKindSolutionItems)) expand = true;

         if ((project != null) && (project.Kind == Constants.vsProjectKindSolutionItems)) expand = true;

     

         if (expand && (child.UIHierarchyItems != null) && (child.UIHierarchyItems.Expanded == false)) {

            child.UIHierarchyItems.Expanded = true;

            xitems.Add(child);

         }

     

         _loadSolutionItems(child.UIHierarchyItems);

      }

     

      foreach (var item in xitems) {

        item.UIHierarchyItems.Expanded = false;

      }

    }

     

     

     

     

    Looks ugly, but seems to work OK.

    Thursday, December 13, 2007 11:03 PM
  • I don't think you realize quite how much speed difference the DFS is compared to the way I did it...

     

    It's fine on small projects but it's death on big ones. It almost made me want to find the item myself...

     

    I haven't needed to find solution items yet, but I was aware mine didn't find them. I'll take a look at that sometime.

    Friday, December 14, 2007 12:52 AM
  • This version of FindHierarchyItem works with solution folder and is faster than the recursive version (probably just as fast at the first version above, that doesnt work). For free you get my OnLocateInSolutionExplorer method:-)

          
    public void OnLocateInSolutionExplorer() 
            { 
             
                ProjectItem projectItem = _DTE.ActiveDocument.ProjectItem; 
     
                UIHierarchyItems solutionItems = _DTE.ToolWindows.SolutionExplorer.UIHierarchyItems; 
     
                // 
                // ExpandView dont (always) work it item is inside solution folders. 
                // The hack below worked, but it expanded all solution folders, and since we walk the solution explorer 
                // in FindHierarchyItem we might as well expand at the same time 
                // 
                //try 
                //{ 
                //    proItem.ExpandView(); 
                //} 
                //catch (Exception) 
                //{ 
                //    //bug: expand project dont work if a soultion folder is parent and it hasnt been expanded yet 
                //    LoadSolutionItems(items); 
                //    proItem.ExpandView(); 
                //} 
     
                //check if we have a solution 
                if (solutionItems.Count != 1) 
                    return
     
                //FindHierarchyItem expands nodes as well (it must do so, because children arent loaded until expanded) 
                UIHierarchyItem uiItem = FindHierarchyItem(solutionItems.Item(1).UIHierarchyItems, projectItem); 
     
                if (uiItem != null
                { 
                    //if we called DoDefaultAction in FindHierarchyItem, solution explorer will have focus 
                    //set it back to  the dicument 
                    projectItem.Document.Activate(); 
     
                    //didnt find another way to make the item gray (while not having focus) 
                    _DTE.ExecuteCommand("View.TrackActivityinSolutionExplorer""True"); 
     
                    //if item is already selected, View.TrackActivityinSolutionExplorer wont make the item grey, 
                    //so deselect it first 
                    if (uiItem.IsSelected) 
                        uiItem.Select(vsUISelectionType.vsUISelectionTypeToggle); 
     
                    //selecting while View.TrackActivityinSolutionExplorer selects and makes it grey 
                    uiItem.Select(vsUISelectionType.vsUISelectionTypeSelect); 
                    //done with it now 
                    _DTE.ExecuteCommand("View.TrackActivityinSolutionExplorer""False"); 
                    
                } 
                else 
                { 
                    MessageBox.Show("could not find item in solution explorer"); 
                } 
                    
            } 
     
            UIHierarchyItem FindHierarchyItem(UIHierarchyItems items, object item) 
            { 
                // 
                // Enumerating children recursive would work, but it may be slow on large solution. 
                // This tries to be smarter and faster 
                // 
     
                Stack s = new Stack(); 
                CreateItemsStack(s, item); 
     
                UIHierarchyItem last = null
                while (s.Count != 0) 
                { 
                    if (!items.Expanded) 
                        items.Expanded = true
                    if (!items.Expanded) 
                    { 
                        //bug: expand dont always work... 
                        UIHierarchyItem parent = ((UIHierarchyItem)items.Parent); 
                        parent.Select(vsUISelectionType.vsUISelectionTypeSelect); 
                        _DTE.ToolWindows.SolutionExplorer.DoDefaultAction(); 
                    } 
     
                    object o = s.Pop(); 
     
                    last = null
                    foreach (UIHierarchyItem child in items) 
                        if (child.Object == o) 
                        { 
                            last = child; 
                            items = child.UIHierarchyItems; 
                            break
                        } 
                } 
     
                return last; 
            } 
     
            void CreateItemsStack(Stack s, object item) 
            { 
                if (item is ProjectItem) 
                { 
                    ProjectItem pi = (ProjectItem)item; 
                    s.Push(pi); 
                    CreateItemsStack(s, pi.Collection.Parent); 
                } 
                else if (item is Project) 
                { 
                    Project p = (Project)item; 
                    s.Push(p); 
                    if (p.ParentProjectItem != null
                    { 
                        //top nodes dont have solution as parent, but is null 
                        CreateItemsStack(s, p.ParentProjectItem); 
                    } 
                } 
                else if (item is Solution) 
                { 
                    //doesnt seem to ever happend... 
                    Solution sol = (Solution)item; 
                } 
                else 
                { 
                    throw new Exception("unknown item"); 
                } 
            }  

    Friday, August 01, 2008 9:41 AM
  • Update: discovered a problems with FindHierarchyItem when trying to locate a Project. The Object member of a UIHierarchyItem for a project point to a Project or ProjectItem, depending on the nesting level of projects\solution folders (huh?!?), but locating a ProjectItem always seems to work. To workaround this problem, I added a wrapper:

            UIHierarchyItem FindHierarchyItem2(UIHierarchyItems items, object item)
            {
                UIHierarchyItem uiItem = FindHierarchyItem(items, item);
                if (uiItem == null && item is Project && ((Project)item).ParentProjectItem != null)
                        uiItem = FindHierarchyItem(items, ((Project)item).ParentProjectItem);

                return uiItem;
            }

    this also seems to work:

            UIHierarchyItem FindHierarchyItem2(UIHierarchyItems items, object item)
            {
                if (item is Project && ((Project)item).ParentProjectItem != null)
                    return FindHierarchyItem(items, ((Project)item).ParentProjectItem);
                else
                    return FindHierarchyItem(items, item);
            }

    Pick your poison.

    Really confusing how this really works, mostly trial and error from my side.
    • Edited by arghhhhhhhhhhh Saturday, August 02, 2008 10:42 PM typo
    • Proposed as answer by CodeValue Ltd Friday, February 17, 2012 5:50 PM
    Saturday, August 02, 2008 10:25 PM
  • That helped me a lot, arghhhhhhhhhhh, thanks!
    Friday, February 17, 2012 5:50 PM
  • I cleaned up and refactored the code a bit, perhaps will be useful to someone. It includes the latest fix for Projects as well:

    https://gist.github.com/1862526


    Co-Founder of BugAid Software Enhance your C# debugging! http://www.bugaidsoftware.com

    Sunday, February 19, 2012 7:59 AM