There's a problem in Rubberduck that we haven't neatly solved yet, and as we're exploring alternative approaches I figured I'd ask the CR community and see what our C# experts think.
Here's the slightly simplified (irrelevant constructor parameters were removed) constructor for the CodeExplorerViewModel
(actual file on GitHub), which is used by the WPF/XAML control for the Code Explorer feature.
The ViewModel needs to expose quite a large number of commands, and since each command has its dependencies, and these dependencies have their own dependencies, we use DI/IoC to constructor-inject the commands into the ViewModel - the DI registration essentially injects every single command instance that exists in Rubberduck all Code Explorer commands1, and then since the XAML command bindings require ICommand
properties, we assign these properties by fetching the command of the appropriate type from the List<CommandBase>
we're given:
public CodeExplorerViewModel(List<CommandBase> commands)
{
var reparseCommand = commands.OfType<ReparseCommand>().SingleOrDefault();
RefreshCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(),
reparseCommand == null ? (Action<object>)(o => { }) :
o => reparseCommand.Execute(o),
o => !IsBusy && reparseCommand != null && reparseCommand.CanExecute(o));
OpenCommand = commands.OfType<UI.CodeExplorer.Commands.OpenCommand>().SingleOrDefault();
OpenDesignerCommand = commands.OfType<OpenDesignerCommand>().SingleOrDefault();
AddTestModuleCommand = commands.OfType<UI.CodeExplorer.Commands.AddTestModuleCommand>().SingleOrDefault();
AddStdModuleCommand = commands.OfType<AddStdModuleCommand>().SingleOrDefault();
AddClassModuleCommand = commands.OfType<AddClassModuleCommand>().SingleOrDefault();
AddUserFormCommand = commands.OfType<AddUserFormCommand>().SingleOrDefault();
OpenProjectPropertiesCommand = commands.OfType<OpenProjectPropertiesCommand>().SingleOrDefault();
RenameCommand = commands.OfType<RenameCommand>().SingleOrDefault();
IndenterCommand = commands.OfType<IndentCommand>().SingleOrDefault();
FindAllReferencesCommand = commands.OfType<UI.CodeExplorer.Commands.FindAllReferencesCommand>().SingleOrDefault();
FindAllImplementationsCommand = commands.OfType<UI.CodeExplorer.Commands.FindAllImplementationsCommand>().SingleOrDefault();
CollapseAllSubnodesCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteCollapseNodes);
ExpandAllSubnodesCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteExpandNodes);
ImportCommand = commands.OfType<ImportCommand>().SingleOrDefault();
ExportCommand = commands.OfType<ExportCommand>().SingleOrDefault();
ExportAllCommand = commands.OfType<Rubberduck.UI.Command.ExportAllCommand>().SingleOrDefault();
_externalRemoveCommand = commands.OfType<RemoveCommand>().SingleOrDefault();
if (_externalRemoveCommand != null)
{
RemoveCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteRemoveComand, _externalRemoveCommand.CanExecute);
}
PrintCommand = commands.OfType<PrintCommand>().SingleOrDefault();
CommitCommand = commands.OfType<CommitCommand>().SingleOrDefault();
UndoCommand = commands.OfType<UndoCommand>().SingleOrDefault();
CopyResultsCommand = commands.OfType<CopyResultsCommand>().SingleOrDefault();
SetNameSortCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), param =>
{
if ((bool)param == true)
{
SortByName = (bool)param;
SortByCodeOrder = !(bool)param;
}
}, param =>
{
return SortByName ? false : true;
});
SetCodeOrderSortCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), param =>
{
if ((bool)param == true)
{
SortByCodeOrder = (bool)param;
SortByName = !(bool)param;
};
}, param =>
{
return SortByCodeOrder ? false : true;
});
}
Some of these commands are used in command bindings for the toolbar, some are used in the context menu, others in link buttons in the bottom panel, and some are reused/redundant, for UX/convenience:
Having as many constructor parameters as we need to expose commands would be unacceptable.
Surely there's a better way to do this?
Note: We're currently using ninject, but the IoC is being ported to castle-windsor.
1 [CodeExplorerCommand]
attributes decorate the Code Explorer command classes; the IoC configuration is setup so that only command classes decorated with that attribute get injected into the ViewModel.
Dictionary
of the maps and iterate the piss out of that baby. :) \$\endgroup\$