Monday, December 28, 2009

Creating a simple Commanding Framework for Silverlight 3

Whenever you are working with commanding, you are dependent on using either PRISM or perhaps the Interactivity framework. ( see my previous post )

Even in the future when Silverlight 4 is released there will only be support for commanding on Buttons and Hyperlinks.  So you might consider writing your own framework to handle all these things for you and allowing you to add some functionality on the way.

Getting Started

By default, Silverlight provides an ICommand interface that already provides you with a basic contract to get started with Commanding.  What we are going to do is extend this interface in an ITriggeredCommand interface. This will allow us to hook up some event handler when we want to execute the command.

public interface ITriggeredCommand : ICommand

     string Trigger { get; set; }
}

Setting up the Framework

Next we will create an abstract implementation to hide some of the contract from the ICommand and create our own contract.  We will however expose the possibility to invoke the CanExecuteChanged event, to be able to adapt our interface in the way we desire.

public abstract class BaseCommand<T> : ITriggeredCommand

    public string Trigger { get; set; } 

    private Action<T> execute;
    private Func<T, Boolean> canExecute; 

    public BaseCommand(Action<T> execute, Func<T, Boolean> canExecute)
    {
        if(execute == null)
            throw new ArgumentNullException("The execution action cannot be null.");
        this.execute = execute;
        this.canExecute = canExecute;
    } 

    /// <summary>
    /// When we have a canExecute function, execute it and pass the result
    /// </summary>
    public bool CanExecute(object parameter)
    {
        if (canExecute != null)
            return canExecute((T)parameter);
        return true;
    } 

    /// <summary>
    /// Execute the execution action, passing the parameter
    /// </summary>
    public void Execute(object parameter)
    {
        execute((T)parameter);
    }
    /// <summary>
    /// Allow for an implementation of this abstract class to invoke the CanExecuteChanged event
    /// </summary>
    protected void InvokeCanExecuteChanged()
    {
        if (CanExecuteChanged != null)
            CanExecuteChanged(this, EventArgs.Empty);
    } 

    public event EventHandler CanExecuteChanged;
}

At this point you should write a custom implementation of the BaseCommand<T>, however I will now continue with describing how we can hook this functionality up with our UI and ultimately allow you to use it in an MVVM application.

Making it work

So how do we hook up the commands to our UI,  well for this we can use attached properties and databind our commands to them accordingly.

The following code listing will describe how we can make sure the correct event is triggered ( the Trigger property ).

public static class Commands
{
    /// <summary>
    /// Register the new attached property for a command
    /// </summary>
    public static DependencyProperty CommandProperty = DependencyProperty.RegisterAttached
       (
           "Command",
           typeof(ITriggeredCommand),
           typeof(Commands),
           new PropertyMetadata(commandChanged)
       );  

   public static void SetCommand(DependencyObject obj, ITriggeredCommand propertyValue)
   {
       obj.SetValue(CommandProperty, propertyValue);
   } 

   public static ITriggeredCommand GetCommand(DependencyObject obj)
   {
       return (ITriggeredCommand)obj.GetValue(CommandProperty);
   } 

   //When the command changed make sure it is handled correctly 
   private static void commandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs arguments)
   {
       if (arguments.NewValue is ITriggeredCommand)
       {
           ITriggeredCommand command = (ITriggeredCommand)arguments.NewValue;
           //Get the event that is related to the trigger
           EventInfo evInfo = obj.GetType().GetEvent(command.Trigger);
           //Create an eventhandler delegate for it
           Delegate executeEventDelegate = Delegate.CreateDelegate
               (
                  evInfo.EventHandlerType,
                   null,
                   typeof(Commands).GetMethod("handler",BindingFlags.Static | BindingFlags.NonPublic)
                );
            //Assign the Event handler to the object
            evInfo.AddEventHandler(obj,executeEventDelegate); 
        }
    } 

    //Execute the command
    private static void handler(object sender, EventArgs args)
    {
        ITriggeredCommand command = GetCommand((DependencyObject)sender);
        //We could implement an additional attached property to allow for a parameter to be set
        if (command.CanExecute(null))
            command.Execute(null);
    }
}

The important thing here is that we able to attach our command to an event that is related to the object we are binding our command to. 

In the commandChanged method we subscribe our handler to our event using reflection based on the EventHandlerType that was provided by the event defined in the Trigger property.

No comments:

Post a Comment