4
\$\begingroup\$

Continuing the series of Code Explorer posts, here is the collection of view models for the tree nodes:

Code Explorer

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; } }
}
asked May 27, 2016 at 22:30
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

GetSelectedDeclaration is ugly and a little bit uncomprehensive. CodeExplorerItemViewModel does not implement ICodeExplorerDeclarationViewModel, so this method can only return something on a subclass of 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-

answered May 27, 2016 at 23:17
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.