I Command Silverlight

Posted by Rishi on 09-Mar-09 10:33 AM - Comments (7)

Tags: , , , , | Categories: .NET, Code
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.

ParameterCommand

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.

Comments

trackback
DotNetKicks.com
on 24-Jan-09 7:08 PM
Trackback from DotNetKicks.com

I Command Silverlight

Rishi
Rishi
on 26-Jan-09 12:38 AM
Hi I just wanted to mention that in my new post about about MVC-type routing framework for silverlight, I've put a demo that uses the ICommand implementation shown above. The navigation buttons, the url text box use the command pattern to hook up with the ViewModel. Have a look at www.orkpad.com/.../...outing-a-la-MVC-Routing.aspx

Also, I updated the code above with a small bug I introduced whilst refactoring. It should work fine now.

pingback
alvinashcraft.com
on 27-Jan-09 5:27 AM
Pingback from alvinashcraft.com

Dew Drop - March 11, 2009 | Alvin Ashcraft's Morning Dew

Kris
Kris Poland
on 27-Jan-09 2:36 PM
Hello,

please add you blog at www.sweebs.com among other best sites available on the net, where other people can find you!

Regards
Kris

nuno
nuno United States
on 02-Mar-09 4:58 PM
Hi

I tried to use your code in a silverlight composite application (using CAL) and when my search button is always disabled.
I tried to set it to true by default:
_searchCommand = new ParameterCommand<string>(v => Search(v), v => false, "Default string", true);

but this did not change anything.

If I use ActionCommand then it is enabled and I can click on it.

Any ideas why my button never gets to the enabled state?

Thanks
Nuno

Rishi
Rishi
on 06-Mar-09 3:21 PM
Hi Nuno, the is answer is very simple it is because you are doing this "v => false" for the canExecuteHandler. Now, even though you have set the IsEnabled property to true by default, the canExecute returns false always - and in the delegate command and parameter command we check with both the canExecuteHandler and the IsEnabled property to check if we can execute the command (note, in ActionCommand we only check IsEnabled). So I suggest when using the parameter command or delegate command, either put some criteria (like say the search textbox's text length should be greater than zero) or always set it to true ("v => true") and then use the IsEnabled to either enable/disable the command. Hope this helps..

trackback
orktane
on 11-Sep-09 12:33 PM
Introducing nRoute.Toolkit for Silverlight (Part II)

Introducing nRoute.Toolkit for Silverlight (Part II)

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading