Click here to Skip to main content
15,888,069 members
Articles / Programming Languages / C#

Conventional Commands

Rate me:
Please Sign up or sign in to vote.
4.00/5 (1 vote)
4 Apr 2013CPOL3 min read 12.6K   67   4   1
Avoiding the need to declare Command properties in View Model.

Introduction

The good programmer is a lazy programmer - that is a reason for DRY principle. At least it's how I understand it.

While coding one rather complex View Model I've noticed many declarations of ICommand public properties backed with the private field. Moreover for most of the commands two methods are declared; 

C#
private void SaveCmd(object param){...};
private bool CanSaveCmd(object param){...}; 

That gives the pattern for convention:

C#
private void <CommandName><Cmd|CmdManual>(object param){...}; 
private bool Can<CommandName><Cmd|CmdManual>(object param){...};

It will become clear a bit later why there is a choice between two options Cmd, and CmdManual.

Implementation

The idea is simple. Now we should look for the ways of its implementation. Two variants come to mind.

  • Support of dynamic interface (IDynamicMetaObjectProvider)
  • Creating commands and exposing them through the dictionary when View Model is instantiated.

I'm not experienced in DLR and have concerns about performance of the former approach that's why I chose the latter one. Fortunately WPF XAML supports indexers and we can bind commands like this:

XML
<Button Command="{Binding Commands[SaveAs]}"/>

Where Commands is a readonly dictionary property in ViewModelBase class: 

C#
private readonly Dictionary<string, IExtendedCommand> _commands = 
    new Dictionary<string,IExtendedCommand>(StringComparer.InvariantCultureIgnoreCase);
private readonly IReadOnlyDictionary<string, IExtendedCommand> _readOnlyCommands;
public IReadOnlyDictionary<string, IExtendedCommand> Commands
{
    get { return _readOnlyCommands; }
}

Here I declared IExtendedCommand interface for two purposes: 

C#
public interface IExtendedCommand : ICommand 
{ 
        string Name { get; }
        void RaiseCanExecuteChanged();
}  

Name property is useful for debugging|tracing and RaiseCanExecuteChanged() allows to notify about command's state change. By default in our CommandBase class CanExecuteChanged event is delegated to static CommandManager.RequerySuggested event what eliminates the necessity of caring about well-judged notifications. But this has a downside: RequerySuggested fires two often and can be the cause of performance degradation if CanExecute requires a lot time to compute. Therefore we need to select mode between automatic to manual raise of CanExecuteChanged event. CmdManual postfix serves exactly for this purpose.

Here is the code of CommandBase class:

C#
public sealed class CommandBase : IExtendedCommand
{
    private readonly bool _doNotUseCommandManager;
    private readonly Predicate<object> _canExecute;
    private readonly Action<object> _execute;
    private readonly string _name;
    public string Name
    {
        get { return _name; }
    }
    public CommandBase(Action<object> execute) : this(execute, null)
    {
    }
    public CommandBase(Action<object> execute, Predicate<object> canExecute, 
           string name = null, bool doNotUseCommandManager = false)
    {
        _execute = execute;
        _canExecute = canExecute;
        _name = name;
        _doNotUseCommandManager = doNotUseCommandManager;
    }
    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        Debug.WriteLine("CanExecute value requested in Command {0}", new object[]{ _name });
#if DEBUG
        var sw = Stopwatch.StartNew();
#endif
        var result = _canExecute == null || _canExecute(parameter);
#if DEBUG
        sw.Stop();
        Debug.WriteLine("CanExecute took {0} ms to complete.", sw.ElapsedMilliseconds);
#endif
        return result;
    }
    private EventHandler _canExecuteChanged;
    public event EventHandler CanExecuteChanged
    {
        add
        {
            _canExecuteChanged += value;
            if (!_doNotUseCommandManager)
            {
                CommandManager.RequerySuggested += value;
            }
            Debug.WriteLine("CanExecuteChanged listener attached to Comamnd {0}", new object[] { _name });
        }
        remove
        {
            _canExecuteChanged -= value;
            if (!_doNotUseCommandManager)
            {
                CommandManager.RequerySuggested -= value;
            }
            Debug.WriteLine("CanExecuteChanged listener detached from Comamnd {0}", new object[] { _name });
        }
    }
    public void RaiseCanExecuteChanged()
    {
        _canExecuteChanged.Raise(this);
    }
    public void Execute(object parameter)
    {
        _execute(parameter);
    } 
} 

and Raise is the extension method for thread-safety: 

C#
public static class EventHelper
{
    public static void Raise(this EventHandler handler,object sender)
    {
        if (handler != null)
            handler(sender, EventArgs.Empty);
    }
    public static void Raise<TArgs>(this EventHandler<TArgs> handler, object sender, TArgs args) 
        where TArgs : EventArgs
    {
        if (handler != null)
            handler(sender, args);
    }
    public static void Raise(this PropertyChangedEventHandler handler, 
                             object sender, PropertyChangedEventArgs args)
    {
        if (handler != null)
            handler(sender, args);
    }
    public static void Raise(this PropertyChangingEventHandler handler, object sender, PropertyChangingEventArgs args)
    {
        if (handler != null)
            handler(sender, args);
    }
    public static void Raise(this NotifyCollectionChangedEventHandler handler, 
           object sender, NotifyCollectionChangedEventArgs args) 
    {
        if (handler != null)
            handler(sender, args);
    }
}

Note, please, that add and remove of CanExecuteChanged implemented in a not thread-safe manner!  

Next step is to implement ad hoc DI for creating instances of commands: 

C#
public interface ICommandBuilder
{
    IExtendedCommand BuildCommand(Action<object> execute, 
      Predicate<object> canExecute, string name, bool doNotUseCommandManager);
} 
public interface ICommandsProvider
{
    void FillCommands(ViewModelBase viewModel, Dictionary<string, IExtendedCommand> commands);
}   

and in ViewModelBase class:

C#
private static ICommandBuilder _commandBuilder;
public static ICommandBuilder CommandBuilder
{
    get { return _commandBuilder; }
    
    set
    {
        if (value == null)
        {
             throw new ArgumentNullException("value");
        } 
        _commandBuilder = value;
    }
}
public static readonly List<ICommandsProvider> 
      CommandProviders = new List<ICommandsProvider>();
static ViewModelBase()
{
    CommandBuilder = new DefaultCommandBuilder();
    CommandProviders.Add(new DefaultCommandsProvider());
}

DefaultCommandBuilder is nothing more than a wrapper of CommandBase constructor:

C#
public class DefaultCommandBuilder : ICommandBuilder
{
    public IExtendedCommand BuildCommand(Action<object> execute, 
      Predicate<object> canExecute, string name, bool doNotUseCommandManager)
    {
        return new CommandBase(execute, canExecute, name, doNotUseCommandManager);
    }
}

Default Commands Provider 

Eventually we got to the essence of the article DefaultCommandsProviderClass. Indeed it is rather simple class. It scans the type hierarchy (up to ViewModelBase) for non public (protected or private) nongeneric methods matching the pattern discussed in Introduction. Then for each execute candidate it tries to find can execute candidate throwing ViewModelCommandException if these candidates have different postfixes or if number of can execute candidates is greater than number of execute candidates.

As candidates are searched in the first step it is possible for execute to be a method of  ancestor and can execute - method of descendant. It's up to you to treat it either as a bug or a feature.

C#
private static CommandPart GetCommandPart(MethodInfo mi)
{
    // Method name must conform to the pattern: [CanExecutePrefix]<CommandName><CommandPostfix>
    var matchedPostfix = CommandPostfixes.First(commandPosfix => 
               mi.Name.EndsWith(commandPosfix, true, CultureInfo.InvariantCulture));
    Debug.WriteLine("Matched postfix {0}", new object[]{matchedPostfix});
    var nameWithoutPostfix = mi.Name.Remove(mi.Name.Length - matchedPostfix.Length);
    if (nameWithoutPostfix.Length > 0)
    {
        var doNotUseCommandManager = string.Compare(matchedPostfix, 
            ManualCanExecuteChangedRaisePostfix, true, CultureInfo.InvariantCulture) == 0;
        var cmdInfo = new CommandPart
            {
                Method = mi,
                DoNotUseCommandManager = doNotUseCommandManager
            };
        
        string commandName;
        // CanExecute method
        if (mi.ReturnType == typeof(bool) &&
            nameWithoutPostfix.StartsWith(CanExecutePrefix, true, CultureInfo.InvariantCulture)
            && nameWithoutPostfix.Length > CanExecutePrefix.Length)
        {
            commandName = nameWithoutPostfix.Substring(CanExecutePrefix.Length);
            cmdInfo.Type = CommandPart.MethodType.CanExecute;
        }
        // Execute method
        else if (mi.ReturnType == typeof(void) &&
                 !nameWithoutPostfix.StartsWith(CanExecutePrefix, true, CultureInfo.InvariantCulture))
        {
            commandName = nameWithoutPostfix;
            cmdInfo.Type = CommandPart.MethodType.Execute;
        }
        else
        {
            Debug.WriteLine("Resulting command name is invalid. Method name is {0}.", new object[] { mi.Name });
            return null;
        }
        cmdInfo.Name = commandName;
        return cmdInfo;
    }
    Debug.WriteLine("Resulting command name is invalid. Method name is {0}", new object[] { mi.Name });
    return null;
}

private class CommandPart
{
    public enum MethodType { Unknown, Execute, CanExecute }
    public MethodType Type { get; set; }
    public string Name { get; set; }
    public MethodInfo Method { get; set; }
    /// <summary>
    /// CanExecuteChanged is raised only manually.
    /// </summary>
    public bool DoNotUseCommandManager { get; set; }
    public CommandPart()
    {
         Type = MethodType.Unknown;
    }
}

The whole algorithm of  FillCommands() is trivial: 

  • Get candidates in form of CommandPart 
  • Split collection by Type: Execute or CanExecute
  • Foreach Execute part try to find CanExecute part
  • Create corresponding delegates 
  • Ask builder for new command
  • Add command to the dictionary 

That's it. From this point methods conventionally become commands.

But... I'd like to point out this approach has a couple of disadvantages: Everything comes at a cost and such loose coupling makes impossible IntelliSense support in XAML designer. Moreover if you use ReSharper and declare methods as private they will be grayed as never used in the code.  

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Positive Technologies
Russian Federation Russian Federation
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 4 Pin
jackmos8-Apr-13 5:49
professionaljackmos8-Apr-13 5:49 
Great article. Would have given it a 5 if you had some realistic examples of use.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.