Update: The ICommand implementation in this post was designed for Silverlight 2, for Silverlight 3 I've put out some new ideas in library called nRoute.Toolkit (
download at Codeplex). Related to ICommand the toolkit contains:
- Two ICommand implementations including both generic and parameter-less versions
- ICommand extensions that allow you explicitly define execution-state dependencies (i.e. for CanExecute)
- A Blend based behaviour to use ICommands, that is both bindable to your ViewModel and can manage the state of the control it is applied to
Read full details about the above in my
Introducing nRoute.Toolkit (Part 2) post.
The M-V-VM pattern (click here for M-V-VM intro) is perhaps the flavour of the season, it seems to be getting a lot of attention in the SL/WPF community. And rightfully so, because it goes very well with WPF/SL's databinding and data template feature sets. However, in Silverlight ICommand is there in name only, it's not inherently supported by the core-controls (which is a royal pain), but there are a number of reasonable workarounds till Silverlight 3 I suppose.
Anyway, just as a lot of developers I too created a non-event-routing based ICommand implementation for M-V-VM (specifically for SL but can equally be used for WPF). My implementation adds couple of things, let me enumerate:
- It adds a CommandParameter Property; what this allows is to implicitly bind any UI based property to the parameter that is passed into ICommand's execution. So basically if you pass in a null in ICommand's Execute method it would substitute it with the CommandParameter's value.
- Also, I've added an IsEnabled property, which is used to logically disable the command, as it "ands" the CanExecute result. I find this really useful, like say we are already executing a search and we don't want to get any more commands in, just say IsEnabled = false. This ties into the UI too, because changing it's value raises the CanExecuteChanged event.
- Thirdly, the command class implements the INotifyPropertyChanged property, which goes with the two properties I've mentioned above. And like I said, when you disable the command or when the CommadParameter Property's value changes, it triggers the CanExecuteChanged event, that in turn ties into the UI.
- I've also added a read-only bool property called IsExecutable, this again is mainly to bind to the UI as SL doesn't listen to the changes in CanExecuteChanged. So whenever CanExecuteChanged is raised we also raise IsExecutable's PropertyChanged event. This way you can say bind a button's IsEnabled Property to the IsExecutable Property, and it will automatically enable/disable the button. Notably, you can bind this to multiple UI element's, you are not limited to one UI element.
- And because we get the value of the parameter for ICommand using the binding on CommandParameter Property we can just call Execute() or CanExecute() without passing in a parameter.
I am note sure if I got the benefits across, but essentially what this design helps is to better control the state of the command and it's consumption by the UI - particularly in Silverlight. Rather than having to expose two/three more properties, we can just use the command's properties notably with INotifyPropertyChanged Interface.
<TextBox Text="{Binding SearchCommand.CommandParameter, Mode=TwoWay}" />
<Button Content="Search" cal:Click.Command="{Binding SearchCommand}"
IsEnabled="{Binding SearchCommand.IsExecutable, Mode=OneWay}" />
In the xaml above we bind the textbox's text property to the command's parameter value, and button is tied to the command. Also, I am using the Prism 2's attached properties for the click event consumption, you don't need one in WPF. And below is how you set up the command in the ViewModel.
_searchCommand = new ParameterCommand<string>(
v => Search(v), v => !string.IsNullOrEmpty(v), string.Empty, true
);
public ParameterCommand<string> SearchCommand
{
get { return _searchCommand; }
}
void Search(string value)
{
_searchCommand.IsEnabled = false;
// do search..
// show results
_searchCommand.IsEnabled = true;
}
For initializing the ICommand, I am passing in an execute handler, a canExecute lambda handler that checks the string is not empty, a empty string for the default value of the CommadParameter and true as in by default the command is enabled. When doing the search you disable the command and enable it back when done, and the UI part is handled with hook-ups shown earlier.
As for the structure, I have two base classes that are helpful when you don't wanna use the CommadParameter Property. The ActionCommand is mainly useful when you don't use a parameter, just an action like save or close. The delegate command is pretty much like Prism's but with IsEnabled rather than IsActive, however IsEnabled is tied in with INotifyPropertyChanged so it helps with UI consumption. And the read-only IsExecutable Property checks both with the CanExecute method and IsEnabled property, and is also tied in with INotifyPropertyChanged.
Hope this helps, I'll have a running sample up soon, the complete code is below.
using System;
using System.Windows.Input;
using System.ComponentModel;
namespace Orkpad.Samples
{
public class ActionCommand<T> : ICommand, INotifyPropertyChanged
{
protected const string ERROR_CANNOT_EXEC = "Cannot execute command {0}, invalid execution state or parameter.";
protected const string ERROR_EXPECTED_TYPE = "Expected parameter for command ({0}) must be of {1} type.";
Action<T> _executeHandler;
bool _isEnabled;
public ActionCommand(Action<T> executeHandler) : this(executeHandler, true) { }
public ActionCommand(Action<T> executeHandler, bool isEnabled)
{
// basic check
if (executeHandler == null) throw new ArgumentNullException("executeHandler");
// we save
_executeHandler = executeHandler;
_isEnabled = isEnabled;
// we also add an event handler which allows, anything binding to IsExecutable
//this.CanExecuteChanged += (s, e) => RaisePropertyChanged("IsExecutable");
}
#region Additions
//public virtual bool IsExecutable
//{
// get
// {
// return IsEnabled;
// }
//}
public bool IsEnabled
{
get
{
return _isEnabled;
}
set
{
if (_isEnabled != value)
{
_isEnabled = value;
RaiseCanExecuteChanged();
RaisePropertyChanged("IsEnabled");
}
}
}
public bool CanExecute(T parameter)
{
return OnCanExecute(parameter);
}
public void Execute(T parameter)
{
OnExecuteCommand(parameter);
}
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null) CanExecuteChanged(this, new EventArgs());
}
#endregion
#region ICommand Members
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
// basic checks
CheckParameterType(parameter);
return OnCanExecute((T)parameter);
}
public virtual void Execute(object parameter)
{
// basic checks
CheckParameterType(parameter);
OnExecuteCommand((T)parameter);
}
#endregion
#region INotifyPropertyChanged Related
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
// basic check
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
#region Internal
protected virtual bool OnCanExecute(T value)
{
return IsEnabled;
}
protected virtual void OnExecuteCommand(T parameter)
{
// I've changed this to not throw an exception, as failure to execute should fail gracefully
if (!OnCanExecute(parameter)) return;
// throw new InvalidOperationException(string.Format(ERROR_CANNOT_EXEC, this.GetType().FullName));
// we execute
_executeHandler(parameter);
}
protected void CheckParameterType(Object parameter)
{
if (parameter == null) return;
if (! typeof(T).IsAssignableFrom(parameter.GetType()))
throw new ArgumentException(string.Format(ERROR_EXPECTED_TYPE, this.GetType().FullName, typeof(T).FullName));
}
#endregion
}
public class DelegateCommand<T> : ActionCommand<T>
{
Func<T, bool> _canExecuteHandler;
public DelegateCommand(Action<T> executeHandler) : this(executeHandler, null, true) { }
public DelegateCommand(Action<T> executeHandler, Func<T, bool> canExecuteHandler)
: this(executeHandler, canExecuteHandler, true) { }
public DelegateCommand(Action<T> executeHandler, Func<T, bool> canExecuteHandler, bool isEnabled)
: base(executeHandler, isEnabled)
{
_canExecuteHandler = canExecuteHandler;
}
#region Helpers
protected override bool OnCanExecute(T value)
{
// basic check
CheckParameterType(value);
// we execute, note the base checks
return (_canExecuteHandler != null) ? (base.OnCanExecute(value) && _canExecuteHandler(value)) : base.OnCanExecute(value);
}
#endregion
}
public class ParameterCommand<T> : DelegateCommand<T>
{
#region Cosntants & Variables
T _commandParameter;
#endregion
#region Constructors
public ParameterCommand(Action<T> executeHandler) : this(executeHandler, null, default(T), true) { }
public ParameterCommand(Action<T> executeHandler, T commandParameterDefaultValue)
: this(executeHandler, null, commandParameterDefaultValue, true) { }
public ParameterCommand(Action<T> executeHandler, bool isEnabled)
: this(executeHandler, null, default(T), isEnabled) { }
public ParameterCommand(Action<T> executeHandler, Func<T, bool> canExecuteHandler)
: this(executeHandler, canExecuteHandler, default(T), true) { }
public ParameterCommand(Action<T> executeHandler, Func<T, bool> canExecuteHandler, bool isEnabled)
: this(executeHandler, canExecuteHandler, default(T), isEnabled) { }
public ParameterCommand(Action<T> executeHandler, Func<T, bool> canExecuteHandler,
T commandParameterDefaultValue, bool isEnabled) : base(executeHandler, canExecuteHandler, isEnabled)
{
// we save
_commandParameter = commandParameterDefaultValue;
// we also add an event handler which allows, anything binding to IsExecutable
this.CanExecuteChanged += (s, e) => RaisePropertyChanged("IsExecutable");
}
#endregion
#region Additions
public bool IsExecutable
{
get
{
// we check this here, because we can validate against the command parameter
return this.CanExecute();
}
}
public T CommandParameter
{
get
{
return _commandParameter;
}
set
{
if (!Object.Equals(_commandParameter, value))
{
_commandParameter = value;
RaisePropertyChanged("CommandParameter");
RaiseCanExecuteChanged();
}
}
}
public bool CanExecute()
{
return OnCanExecute(this.CommandParameter);
}
public void Execute()
{
this.Execute(this.CommandParameter);
}
#endregion
#region Helpers
protected override void OnExecuteCommand(T parameter)
{
// we subsitute the
T _value = parameter != null ? parameter : this.CommandParameter;
// we execute
base.OnExecuteCommand(_value);
}
#endregion
}
}
UPDATE: I've got revised versions of all the three classes in nRoute, please visit http://nRoute.codeplex.com for the latest release and check out the demo app for examples on how to use these ICommand implementations.