patterncsharpMinor
Code Explorer 2.0: A folder hierarchy to organize VBA projects
Viewed 0 times
projectshierarchyexplorerorganizefoldervbacode
Problem
Next release of Rubberduck will introduce a very cool
...which is tremendously awesome, because VBA doesn't have any concept of namespace, and the VBE doesn't offer anything better than a "Classes" folder to organize your class modules - this feature will make writing OOP VBA code much more appealing.
The Code Explorer WPF user control binds to a
I'm interested in feedback specifically about the way the node hierarchy is done and rendered, so here's the root-level
```
public class CodeExplorerProjectViewModel : CodeExplorerItemViewModel
{
private readonly Declaration _declaration;
private static readonly DeclarationType[] ComponentTypes =
{
DeclarationType.Class,
DeclarationType.Document,
DeclarationType.Module,
DeclarationType.UserForm,
};
public CodeExplorerProjectViewModel(Declaration declaration, IEnumerable declarations)
{
_declaration = declaration;
Items = FindFolders(declarations.ToList(), '.');
_icon = _declaration.Project.Protection == vbext_ProjectProtection.vbext_pp_locked
? GetImageSource(resx.lock__exclamation)
: GetImageSource(resx.VSObject_Library);
}
private static IEnumerable FindFolders(IEnumerable declarations, char delimiter)
{
var root = new CodeExplorerCustomFolderViewModel(string.Empty, new List());
var items = declarations.ToList();
var folders = items.Where(item => ComponentTypes.Contains(item.DeclarationType))
.GroupBy(item => item.CustomFolder)
.OrderBy(item => item.Key);
foreach (var grouping in folders)
{
CodeExplorerItemViewMo
@Folder annotation system that the Code Explorer will use to organize modules into - you guessed it - folders:...which is tremendously awesome, because VBA doesn't have any concept of namespace, and the VBE doesn't offer anything better than a "Classes" folder to organize your class modules - this feature will make writing OOP VBA code much more appealing.
The Code Explorer WPF user control binds to a
CodeExplorerViewModel class, which isn't completed yet - I have a bunch of commands to wire up still, so I'm not including it here.I'm interested in feedback specifically about the way the node hierarchy is done and rendered, so here's the root-level
CodeExplorerProjectViewModel class:```
public class CodeExplorerProjectViewModel : CodeExplorerItemViewModel
{
private readonly Declaration _declaration;
private static readonly DeclarationType[] ComponentTypes =
{
DeclarationType.Class,
DeclarationType.Document,
DeclarationType.Module,
DeclarationType.UserForm,
};
public CodeExplorerProjectViewModel(Declaration declaration, IEnumerable declarations)
{
_declaration = declaration;
Items = FindFolders(declarations.ToList(), '.');
_icon = _declaration.Project.Protection == vbext_ProjectProtection.vbext_pp_locked
? GetImageSource(resx.lock__exclamation)
: GetImageSource(resx.VSObject_Library);
}
private static IEnumerable FindFolders(IEnumerable declarations, char delimiter)
{
var root = new CodeExplorerCustomFolderViewModel(string.Empty, new List());
var items = declarations.ToList();
var folders = items.Where(item => ComponentTypes.Contains(item.DeclarationType))
.GroupBy(item => item.CustomFolder)
.OrderBy(item => item.Key);
foreach (var grouping in folders)
{
CodeExplorerItemViewMo
Solution
Looks pretty good. Some readability improvements:
-
Magic character
-
Since you need to classify the
which allows you to write:
This encapsulates the classification logic and makes the code easier to read. If you have control over the
-
I would consider extracting this condition into its own method:
because it's fairly complex and it's kind of cluttered. Extracting it into a method (which should probably live in
-
I'm not a fan of squeezing property
-
Magic character
'.' should be moved into a named constant like DefaultDelimiter.-
Since you need to classify the
DeclarationType another way to encapsulate this logic is to create a set of extension methods like this:public static class DeclarationTypeExtensions
{
public static bool IsComponentType(this DeclarationType declType)
{
switch (declType)
{
DeclarationType.Class:
DeclarationType.Document:
DeclarationType.Module:
DeclarationType.UserForm:
return true;
}
return false;
}
public static bool IsMemberType(this DeclarationType declType)
{
...
}
}which allows you to write:
item.DeclarationType.IsComponentType()This encapsulates the classification logic and makes the code easier to read. If you have control over the
DeclarationType you could also use attributes to decorate the values instead (plus adding some generic code to obtain classification based on the enum value attributes). The advantage is then it's obvious that you need to add something when a new enum member gets added.-
I would consider extracting this condition into its own method:
parents.Contains(item) || parents.Any(parent =>
(item.ParentDeclaration != null && item.ParentDeclaration.Equals(parent)) || item.ComponentName == parent.ComponentName)))because it's fairly complex and it's kind of cluttered. Extracting it into a method (which should probably live in
Declaration if I followed the types correctly) means you can give this an expressive name which will make the whole thing easier to read.-
I'm not a fan of squeezing property
get and set into one line. I find this easier on the eyes:public bool IsErrorState
{
get { return _isErrorState; }
set { _isErrorState = value; OnPropertyChanged(); }
}Code Snippets
public static class DeclarationTypeExtensions
{
public static bool IsComponentType(this DeclarationType declType)
{
switch (declType)
{
DeclarationType.Class:
DeclarationType.Document:
DeclarationType.Module:
DeclarationType.UserForm:
return true;
}
return false;
}
public static bool IsMemberType(this DeclarationType declType)
{
...
}
}item.DeclarationType.IsComponentType()parents.Contains(item) || parents.Any(parent =>
(item.ParentDeclaration != null && item.ParentDeclaration.Equals(parent)) || item.ComponentName == parent.ComponentName)))public bool IsErrorState
{
get { return _isErrorState; }
set { _isErrorState = value; OnPropertyChanged(); }
}Context
StackExchange Code Review Q#117116, answer score: 5
Revisions (0)
No revisions yet.