Continuing the series of Code Explorer posts, here is the collection of view models for the tree nodes:This is the interface for nodes with a declaration.public interface ICodeExplorerDeclarationViewModel{    Declaration Declaration { get; }}And here is the abstract view model they are all based on:public abstract class CodeExplorerItemViewModel : ViewModelBase{    private List<CodeExplorerItemViewModel> _items = new List<CodeExplorerItemViewModel>();    public List<CodeExplorerItemViewModel> Items    {        get { return _items; }        protected set        {            _items = value;            OnPropertyChanged();        }    }    public bool IsExpanded { get; set; }    public abstract string Name { get; }    public abstract string NameWithSignature { get; }    public abstract BitmapImage CollapsedIcon { get; }    public abstract BitmapImage ExpandedIcon { get; }    public abstract CodeExplorerItemViewModel Parent { get; }    public abstract QualifiedSelection? QualifiedSelection { get; }    public CodeExplorerItemViewModel GetChild(string name)    {        foreach (var item in _items)        {            if (item.Name == name)            {                return item;            }            var result = item.GetChild(name);            if (result != null)            {                return result;            }        }        return null;    }    public Declaration GetSelectedDeclaration()    {        return this is ICodeExplorerDeclarationViewModel            ? ((ICodeExplorerDeclarationViewModel)this).Declaration            : null;    }    public void AddChild(CodeExplorerItemViewModel item)    {        _items.Add(item);    }    public void ReorderItems(bool sortByName, bool sortByType)    {        if (sortByType)        {            Items = sortByName                ? Items.OrderBy(o => o, new CompareByType()).ThenBy(t => t, new CompareByName()).ToList()                : Items.OrderBy(o => o, new CompareByType()).ThenBy(t => t, new CompareBySelection()).ToList();            return;        }        Items = sortByName            ? Items.OrderBy(t => t, new CompareByName()).ToList()            : Items.OrderBy(t => t, new CompareBySelection()).ToList();    }}This VM is for project nodes:public class CodeExplorerProjectViewModel : CodeExplorerItemViewModel, ICodeExplorerDeclarationViewModel{    private readonly Declaration _declaration;    public Declaration Declaration { get { return _declaration; } }    private readonly CodeExplorerCustomFolderViewModel _folderTree;    private static readonly DeclarationType[] ComponentTypes =    {        DeclarationType.ClassModule,         DeclarationType.Document,         DeclarationType.ProceduralModule,         DeclarationType.UserForm,     };    public CodeExplorerProjectViewModel(FolderHelper folderHelper, Declaration declaration, IEnumerable<Declaration> declarations)    {        _declaration = declaration;        _name = _declaration.IdentifierName;        IsExpanded = true;        _folderTree = folderHelper.GetFolderTree(declaration);        try        {            FillFolders(declarations.ToList());            Items = _folderTree.Items.ToList();            _icon = _declaration.Project.Protection == vbext_ProjectProtection.vbext_pp_locked                ? GetImageSource(resx.lock__exclamation)                : GetImageSource(resx.VSObject_Library);        }        catch (NullReferenceException e)        {            Console.WriteLine(e);        }    }    private void FillFolders(IEnumerable<Declaration> declarations)    {        var items = declarations.ToList();        var groupedItems = items.Where(item => ComponentTypes.Contains(item.DeclarationType))                           .GroupBy(item => item.CustomFolder)                           .OrderBy(item => item.Key);        foreach (var grouping in groupedItems)        {            AddNodesToTree(_folderTree, items, grouping);        }    }    private bool AddNodesToTree(CodeExplorerCustomFolderViewModel tree, List<Declaration> items, IGrouping<string, Declaration> grouping)    {        foreach (var folder in tree.Items.OfType<CodeExplorerCustomFolderViewModel>())        {            if (grouping.Key.Replace(\, string.Empty) != folder.FullPath)            {                continue;            }            if (folder.Parent.Name == string.Empty)            {                folder.SetParent(this);            }            var parents = grouping.Where(                    item => ComponentTypes.Contains(item.DeclarationType) &&                        item.CustomFolder.Replace(\, string.Empty) == folder.FullPath)                    .ToList();            folder.AddNodes(items.Where(item => parents.Contains(item) || parents.Any(parent =>                (item.ParentDeclaration != null && item.ParentDeclaration.Equals(parent)) ||                item.ComponentName == parent.ComponentName)).ToList());            return true;        }        return tree.Items.OfType<CodeExplorerCustomFolderViewModel>().Any(node => AddNodesToTree(node, items, grouping));    }    private readonly BitmapImage _icon;    public override BitmapImage CollapsedIcon { get { return _icon; } }    public override BitmapImage ExpandedIcon { get { return _icon; } }    // projects are always at the top of the tree    public override CodeExplorerItemViewModel Parent { get { return null; } }    private string _name;    public override string Name { get { return _name; } }    public override string NameWithSignature { get { return _name; } }    public override QualifiedSelection? QualifiedSelection { get { return _declaration.QualifiedSelection; } }    public void SetParenthesizedName(string parenthesizedName)    {        _name +=  ( + parenthesizedName + );    }}The folders' VM:public class CodeExplorerCustomFolderViewModel : CodeExplorerItemViewModel{    private readonly string _fullPath;    private readonly string _name;    private readonly string _folderAttribute;    private static readonly DeclarationType[] ComponentTypes =    {        DeclarationType.ClassModule,         DeclarationType.Document,         DeclarationType.ProceduralModule,         DeclarationType.UserForm,     };    public CodeExplorerCustomFolderViewModel(CodeExplorerItemViewModel parent, string name, string fullPath)    {        _parent = parent;        _fullPath = fullPath;        _name = name.Replace(\, string.Empty);        _folderAttribute = string.Format(@Folder(\{0}\), fullPath.Replace(\, string.Empty));        _collapsedIcon = GetImageSource(resx.folder_horizontal);        _expandedIcon = GetImageSource(resx.folder_horizontal_open);    }    public void AddNodes(List<Declaration> declarations)    {        var parents = declarations.GroupBy(item => item.ComponentName).OrderBy(item => item.Key).ToList();        foreach (var component in parents)        {            try            {                var moduleName = component.Key;                var parent = declarations.Single(item =>                    ComponentTypes.Contains(item.DeclarationType) && item.ComponentName == moduleName);                var members = declarations.Where(item =>                    !ComponentTypes.Contains(item.DeclarationType) && item.ComponentName == moduleName);                AddChild(new CodeExplorerComponentViewModel(this, parent, members));            }            catch (InvalidOperationException exception)            {                Console.WriteLine(exception);            }        }    }    public string FolderAttribute { get { return _folderAttribute; } }    public string FullPath { get { return _fullPath; } }    public override string Name { get { return _name; } }    public override string NameWithSignature { get { return Name; } }    public override QualifiedSelection? QualifiedSelection { get { return null; } }    private readonly BitmapImage _collapsedIcon;    public override BitmapImage CollapsedIcon { get { return _collapsedIcon; } }    private readonly BitmapImage _expandedIcon;    public override BitmapImage ExpandedIcon { get { return _expandedIcon; } }    // I have to set the parent from a different location than    // the node is created because of the folder helper    internal void SetParent(CodeExplorerItemViewModel parent)    {        _parent = parent;    }    private CodeExplorerItemViewModel _parent;    public override CodeExplorerItemViewModel Parent { get { return _parent; } }}The VM for components:public class CodeExplorerComponentViewModel : CodeExplorerItemViewModel, ICodeExplorerDeclarationViewModel{    private readonly Declaration _declaration;    public Declaration Declaration { get { return _declaration; } }    private readonly CodeExplorerItemViewModel _parent;    public override CodeExplorerItemViewModel Parent { get { return _parent; } }    private static readonly DeclarationType[] MemberTypes =    {        DeclarationType.Constant,         DeclarationType.Enumeration,         DeclarationType.Event,         DeclarationType.Function,         DeclarationType.LibraryFunction,         DeclarationType.LibraryProcedure,         DeclarationType.Procedure,        DeclarationType.PropertyGet,         DeclarationType.PropertyLet,         DeclarationType.PropertySet,         DeclarationType.UserDefinedType,         DeclarationType.Variable,     };    public CodeExplorerComponentViewModel(CodeExplorerItemViewModel parent, Declaration declaration, IEnumerable<Declaration> declarations)    {        _parent = parent;        _declaration = declaration;        _icon = Icons[DeclarationType];        Items = declarations.GroupBy(item => item.Scope).SelectMany(grouping =>                        grouping.Where(item => item.ParentDeclaration != null                                            && item.ParentScope == declaration.Scope                                            && MemberTypes.Contains(item.DeclarationType))                            .OrderBy(item => item.QualifiedSelection.Selection.StartLine)                            .Select(item => new CodeExplorerMemberViewModel(this, item, grouping)))                            .ToList<CodeExplorerItemViewModel>();        _name = _declaration.IdentifierName;        var component = declaration.QualifiedName.QualifiedModuleName.Component;        if (component.Type == vbext_ComponentType.vbext_ct_Document)        {            try            {                var parenthesizedName = component.Properties.Item(Name).Value.ToString();                if (ContainsBuiltinDocumentPropertiesProperty())                {                    CodeExplorerItemViewModel node = this;                    while (node.Parent != null)                    {                        node = node.Parent;                    }                    ((CodeExplorerProjectViewModel) node).SetParenthesizedName(parenthesizedName);                }                else                {                    _name +=  ( + parenthesizedName + );                }            }            catch            {                // gotcha! (this means that the property either doesn't exist or we weren't able to get it for some reason)            }        }    }    private bool ContainsBuiltinDocumentPropertiesProperty()    {        var component = _declaration.QualifiedName.QualifiedModuleName.Component;        try        {            component.Properties.Item(BuiltinDocumentProperties);        }        catch        {            // gotcha! (this means that the property either doesn't exist or we weren't able to get it for some reason)            return false;        }        return true;    }    private bool _isErrorState;    public bool IsErrorState    {        get { return _isErrorState; }        set        {            _isErrorState = value;            _icon = GetImageSource(resx.Error);            foreach (var item in Items)            {                ((CodeExplorerMemberViewModel) item).ParentComponentHasError();            }            OnPropertyChanged();            OnPropertyChanged(CollapsedIcon);            OnPropertyChanged(ExpandedIcon);        }    }    public bool IsTestModule    {        get        {            return _declaration.DeclarationType == DeclarationType.ProceduralModule                   && _declaration.Annotations.Any(annotation => annotation.AnnotationType == AnnotationType.TestModule);        }    }    private readonly string _name;    public override string Name { get { return _name; } }    public override string NameWithSignature { get { return _name; } }    public override QualifiedSelection? QualifiedSelection { get { return _declaration.QualifiedSelection; } }    private vbext_ComponentType ComponentType { get { return _declaration.QualifiedName.QualifiedModuleName.Component.Type; } }    private static readonly IDictionary<vbext_ComponentType, DeclarationType> DeclarationTypes = new Dictionary<vbext_ComponentType, DeclarationType>    {        { vbext_ComponentType.vbext_ct_ClassModule, DeclarationType.ClassModule },        { vbext_ComponentType.vbext_ct_StdModule, DeclarationType.ProceduralModule },        { vbext_ComponentType.vbext_ct_Document, DeclarationType.Document },        { vbext_ComponentType.vbext_ct_MSForm, DeclarationType.UserForm }    };    private DeclarationType DeclarationType    {        get        {            var result = DeclarationType.ClassModule;            try            {                DeclarationTypes.TryGetValue(ComponentType, out result);            }            catch (COMException exception)            {                Console.WriteLine(exception);            }            return result;        }    }    private static readonly IDictionary<DeclarationType,BitmapImage> Icons = new Dictionary<DeclarationType, BitmapImage>    {        { DeclarationType.ClassModule, GetImageSource(resx.VSObject_Class) },        { DeclarationType.ProceduralModule, GetImageSource(resx.VSObject_Module) },        { DeclarationType.UserForm, GetImageSource(resx.VSProject_form) },        { DeclarationType.Document, GetImageSource(resx.document_office) }    };    private BitmapImage _icon;    public override BitmapImage CollapsedIcon { get { return _icon; } }    public override BitmapImage ExpandedIcon { get { return _icon; } }}And the VM for members:public class CodeExplorerMemberViewModel : CodeExplorerItemViewModel, ICodeExplorerDeclarationViewModel{    private readonly Declaration _declaration;    public Declaration Declaration { get { return _declaration; } }    private static readonly DeclarationType[] SubMemberTypes =    {        DeclarationType.EnumerationMember,         DeclarationType.UserDefinedTypeMember                };    private static readonly IDictionary<Tuple<DeclarationType,Accessibility>,BitmapImage> Mappings =        new Dictionary<Tuple<DeclarationType, Accessibility>, BitmapImage>        {            { Tuple.Create(DeclarationType.Constant, Accessibility.Private), GetImageSource(resx.VSObject_Constant_Private)},            { Tuple.Create(DeclarationType.Constant, Accessibility.Public), GetImageSource(resx.VSObject_Constant)},            { Tuple.Create(DeclarationType.Enumeration, Accessibility.Public), GetImageSource(resx.VSObject_Enum)},            { Tuple.Create(DeclarationType.Enumeration, Accessibility.Private ), GetImageSource(resx.VSObject_EnumPrivate)},            { Tuple.Create(DeclarationType.EnumerationMember, Accessibility.Public), GetImageSource(resx.VSObject_EnumItem)},            { Tuple.Create(DeclarationType.Event, Accessibility.Public), GetImageSource(resx.VSObject_Event)},            { Tuple.Create(DeclarationType.Event, Accessibility.Private ), GetImageSource(resx.VSObject_Event_Private)},            { Tuple.Create(DeclarationType.Function, Accessibility.Public), GetImageSource(resx.VSObject_Method)},            { Tuple.Create(DeclarationType.Function, Accessibility.Friend ), GetImageSource(resx.VSObject_Method_Friend)},            { Tuple.Create(DeclarationType.Function, Accessibility.Private ), GetImageSource(resx.VSObject_Method_Private)},            { Tuple.Create(DeclarationType.LibraryFunction, Accessibility.Public), GetImageSource(resx.VSObject_Method_Shortcut)},            { Tuple.Create(DeclarationType.LibraryProcedure, Accessibility.Public), GetImageSource(resx.VSObject_Method_Shortcut)},            { Tuple.Create(DeclarationType.LibraryFunction, Accessibility.Private), GetImageSource(resx.VSObject_Method_Shortcut)},            { Tuple.Create(DeclarationType.LibraryProcedure, Accessibility.Private), GetImageSource(resx.VSObject_Method_Shortcut)},            { Tuple.Create(DeclarationType.LibraryFunction, Accessibility.Friend), GetImageSource(resx.VSObject_Method_Shortcut)},            { Tuple.Create(DeclarationType.LibraryProcedure, Accessibility.Friend), GetImageSource(resx.VSObject_Method_Shortcut)},            { Tuple.Create(DeclarationType.Procedure, Accessibility.Public), GetImageSource(resx.VSObject_Method)},            { Tuple.Create(DeclarationType.Procedure, Accessibility.Friend ), GetImageSource(resx.VSObject_Method_Friend)},            { Tuple.Create(DeclarationType.Procedure, Accessibility.Private ), GetImageSource(resx.VSObject_Method_Private)},            { Tuple.Create(DeclarationType.PropertyGet, Accessibility.Public), GetImageSource(resx.VSObject_Properties)},            { Tuple.Create(DeclarationType.PropertyGet, Accessibility.Friend ), GetImageSource(resx.VSObject_Properties_Friend)},            { Tuple.Create(DeclarationType.PropertyGet, Accessibility.Private ), GetImageSource(resx.VSObject_Properties_Private)},            { Tuple.Create(DeclarationType.PropertyLet, Accessibility.Public), GetImageSource(resx.VSObject_Properties)},            { Tuple.Create(DeclarationType.PropertyLet, Accessibility.Friend ), GetImageSource(resx.VSObject_Properties_Friend)},            { Tuple.Create(DeclarationType.PropertyLet, Accessibility.Private ), GetImageSource(resx.VSObject_Properties_Private)},            { Tuple.Create(DeclarationType.PropertySet, Accessibility.Public), GetImageSource(resx.VSObject_Properties)},            { Tuple.Create(DeclarationType.PropertySet, Accessibility.Friend ), GetImageSource(resx.VSObject_Properties_Friend)},            { Tuple.Create(DeclarationType.PropertySet, Accessibility.Private ), GetImageSource(resx.VSObject_Properties_Private)},            { Tuple.Create(DeclarationType.UserDefinedType, Accessibility.Public), GetImageSource(resx.VSObject_ValueType)},            { Tuple.Create(DeclarationType.UserDefinedType, Accessibility.Private ), GetImageSource(resx.VSObject_ValueTypePrivate)},            { Tuple.Create(DeclarationType.UserDefinedTypeMember, Accessibility.Public), GetImageSource(resx.VSObject_Field)},            { Tuple.Create(DeclarationType.Variable, Accessibility.Private), GetImageSource(resx.VSObject_Field_Private)},            { Tuple.Create(DeclarationType.Variable, Accessibility.Public ), GetImageSource(resx.VSObject_Field)},        };    public CodeExplorerMemberViewModel(CodeExplorerItemViewModel parent, Declaration declaration, IEnumerable<Declaration> declarations)    {        _parent = parent;        _declaration = declaration;        if (declarations != null)        {            Items = declarations.Where(item => SubMemberTypes.Contains(item.DeclarationType) && item.ParentDeclaration.Equals(declaration))                                .OrderBy(item => item.Selection.StartLine)                                .Select(item => new CodeExplorerMemberViewModel(this, item, null))                                .ToList<CodeExplorerItemViewModel>();        }        var modifier = declaration.Accessibility == Accessibility.Global || declaration.Accessibility == Accessibility.Implicit            ? Accessibility.Public            : declaration.Accessibility;        var key = Tuple.Create(declaration.DeclarationType, modifier);        _name = DetermineMemberName(declaration);        _icon = Mappings[key];    }    private readonly string _name;    public override string Name { get { return _name; } }    private readonly CodeExplorerItemViewModel _parent;    public override CodeExplorerItemViewModel Parent { get { return _parent; } }    private string _signature = null;    public override string NameWithSignature    {        get        {            if (_signature != null)            {                return _signature;            }            var context =                _declaration.Context.children.FirstOrDefault(d => d is VBAParser.ArgListContext) as VBAParser.ArgListContext;            if (context == null)            {                _signature = Name;            }            else if (_declaration.DeclarationType == DeclarationType.PropertyGet                   || _declaration.DeclarationType == DeclarationType.PropertyLet                   || _declaration.DeclarationType == DeclarationType.PropertySet)            {                // 6 being the three-letter get/let/set + parens + space                _signature = Name.Insert(Name.Length - 6, context.GetText());             }            else            {                _signature = Name + context.GetText();            }            return _signature;        }    }    public override QualifiedSelection? QualifiedSelection { get { return _declaration.QualifiedSelection; } }    private static string DetermineMemberName(Declaration declaration)    {        var type = declaration.DeclarationType;        switch (type)        {            case DeclarationType.PropertyGet:                return declaration.IdentifierName +  (Get);            case DeclarationType.PropertyLet:                return declaration.IdentifierName +  (Let);            case DeclarationType.PropertySet:                return declaration.IdentifierName +  (Set);            case DeclarationType.Variable:                if (declaration.IsArray)                {                    return declaration.IdentifierName + ();                }                return declaration.IdentifierName;            case DeclarationType.Constant:                var valuedDeclaration = (ConstantDeclaration)declaration;                return valuedDeclaration.IdentifierName +  =  + valuedDeclaration.Expression;            default:                return declaration.IdentifierName;        }    }    public void ParentComponentHasError()    {        _icon = GetImageSource(resx.Warning);        OnPropertyChanged(CollapsedIcon);        OnPropertyChanged(ExpandedIcon);    }    private BitmapImage _icon;    public override BitmapImage CollapsedIcon { get { return _icon; } }    public override BitmapImage ExpandedIcon { get { return _icon; } }}
Code Explorer View Models
c#;meta programming;rubberduck
GetSelectedDeclaration is ugly and a little bit uncomprehensive. CodeExplorerItemViewModel does not implement ICodeExplorerDeclarationViewModel, so this method can only return something on a subclassof it like CodeExplorerProjectViewModel. I would prefer to have a virtual method and override it.    //on `CodeExplorerItemViewModel`public virtual Declaration GetSelectedDeclaration(){    return null;}//on `CodeExplorerProjectViewModel `public override Declaration GetSelectedDeclaration(){    return Declaration;}ReorderItems is ok but there's room for improvement.What you know is that the OrderBy may be performed by any comparer and it may be needed a ThenBy if sortByType is selected.You also always need one instance of CompareByName or CompareBySelection. I sugest an implementation where you start by instatiating the right comparer:public void ReorderItems(bool sortByName, bool sortByType){    var comparer = sortByName ? new CompareByName() : new CompareBySelection();    if(sortByType){        Items = Items            .OrderBy(o => o, new CompareByType())            .ThenBy(comparer)            .ToList();    }else{        Items = Items            .OrderBy(o => o, comparer))            .ToList();    }}Your AddNodesToTree could use a variable to store the nodes before adding them to folder.var nodes = items.Where(item => parents.Contains(item) ||                 parents.Any(parent =>                    (item.ParentDeclaration != null && item.ParentDeclaration.Equals(parent)) ||                    item.ComponentName == parent.ComponentName)                ).ToList()folder.AddNodes(nodes);CodeExplorerItemViewModel could have a Root property that would return the first node, sou you wouldn't have to write the while on CodeExplorerComponentViewModel-