Click here to Skip to main content
15,881,625 members
Articles / Programming Languages / C#

MVVM (Part 2) - Commanding

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
1 Oct 2010CPOL5 min read 32.3K   278   16   3
How to create Commands infrastructure in Silverlight

Introduction

In my previous article, I covered the MVVM infrastructure that was lacking commanding. This one completes the previous article by adding commands to the Home Grown MVVM.

How Commanding Works Here

I am pretty sure that Silverlight 5 will have much better support for commands, but right now I had to come up with some scheme to support commanding. The way I ‘wired’ commands is by ‘kidnapping’ the events and rewiring them so the command action will get invoked.

IMHO, some events should remain in the code behind because they are more of a UI functionality than business or data dependant functionality. But if I want to be able to do everything via commanding, it requires some manipulation and more than just manipulation - certain event information I do not know how to get via XAML binding. Certain actions may require a reference to the control. Other actions may need an unrelated data. Based on the above, I want my command to deliver 3 objects – the sender, the original event data, and a user (XAML bound) parameter. I also wanted to be able to bind and specify the Command with a very simple XAML line. No resources etc.

The following is not rocket science, but is not simple if you haven't done it before. If I lose you, go through the code section it may clarify it. In order to not be drawn in details, the following description skips and hides few points – I'll cover them later.

So here is how it works: via the XAML I bind a property (attached dependency property) to an instance of CommandBase class. CommandBase is a class that keeps references to the command’s action and other related data. When the view is rendered, the binding to this property causes an execution of a callback that is specified in the dependency property 'registration'. The attached property is static but the callback delivers instances. It delivers an instance of the control and an instance of the command I bind to in the XAML. Next is the kidnapping/rewiring – I take the original event and register to it a local generic handler. At that point, the binding activity is done. As a result of the above, what I have is a generic handler that will run when the event is fired.

This handler receives the original events parameters, and the event sender. It also has the ability to pull the command object (I’ll cover it later) and to pull the parameter the user might have specified (I’ll cover it later). It combines the 3 objects into one ParameterWrapper object. Using the command object, it calls the command’s action and passes it the ParameterWrapper object.

CommandBase Class

This class implements the ICommand interface which defines the action and checks to see if the action can be invoked at this moment. Silverlight 4 uses it with the existing built in commands. It contains 2 delegates – one that is called to see if the action can be run and one which is the action itself.

C#
public class CommandBase : ICommandFromEvent
{
	private bool canExecuteLocal = false;
	private readonly Func<object, bool> isExecuteAllowed = null;
	private readonly Action<object> commandAction = null;

	public CommandBase(Action<object> commandDelegate, 
                           Func<object, bool> canExecuteDelegate = null)
   {
        commandAction = commandDelegate;
        isExecuteAllowed = canExecuteDelegate;
   } 

It also implements a custom interface that allows me to call just the one method and the isAllowed check and execution will be done.

CommandBinder Class

This class binds an XAML declared CommandBase to a control and an event. Every event type requires its own CommandBinder. The commandBinder contains the name of the event that it will ‘kidnap’. It also contains 2 (static) attached properties: Command and ComandParameter. Each of these properties requires their respective (static) getter and setter. The Command property also requires the (static) callback. The callback instantiates a class I call Executor. The Executor class is the one that kidnaps the event (rewire it as well as calling the action from the CommandBase). Here is the first part of the code:

C#
private const string EName = "SelectionChanged"; //the event name
public static readonly DependencyProperty CommandProperty = 
				DependencyProperty.RegisterAttached
   (
      "Command",                             //Property name
      typeof(ICommand),                      //Property type
      typeof(SelectionChangedCommandBinder), //Type of property owner
      new PropertyMetadata(CommandChanged)   //Callback invoked on property
                                                  value has changed
    );

public static void SetCommand(DependencyObject depObj, ICommand cmd)
{
    depObj.SetValue(CommandProperty, cmd);
}

public static ICommand GetCommand(DependencyObject depObj)
{
    return (ICommand)depObj.GetValue(CommandProperty);
}
//the callback
protected static void CommandChanged(DependencyObject depObj,
                    DependencyPropertyChangedEventArgs depArgs)
{ //create an Executor for a given even and event-args type
	var executor = new Executor<selectionchangedeventargs />(EName);
	executor.CommandGetter = GetCommand;
	executor.ParameterGetter = GetCommandParameter;
	executor.CommandChanged(depObj, depArgs);
}

A similar code exists for the CommandParameterProperty (minus the callback). The CommandChanged (callback) instantiates a new Executor for every new binding. This is the place in which the static nature of the attached property ‘stops’ and a new instance is being assigned to each and every event/control/command. While instantiating an Executor class, the callback passes to it the name of the event, a delegate to find the BaseCommand and a delegate to find the CommandParameter. Since the Executor is a Generic class, the callback implicitly specifies the type of the event arg type.

The Executor Class

This generic class wires a generic event handler to the event. Firstly, by using reflection it finds the event based on its name, next it creates a delegate out of the local generic handler, lastly it adds this delegate to the event so that the next time the event is raised, it will invoke the local handler.

C#
public void CommandChanged(DependencyObject depObj,
               DependencyPropertyChangedEventArgs depArgs)
{ //use reflection to add a given handler as a 
  //delegate to the given event name
   if (depObj != null)
   {
      Type type = depObj.GetType();
      var eventInfo = type.GetEvent(eventName);
      if (eventInfo != null)
      { //event name yield an event
         var delegateType = eventInfo.EventHandlerType;
         if (delegateType != null)
         { //create a delegate from the handler
            Delegate handler = Delegate.CreateDelegate(
                     delegateType, this, "TheHandler");
            if (handler != null)
            { // delegate is good, add it to the event.
               eventInfo.AddEventHandler(depObj, handler);
            }
         }
      }
      else
      {
         MessageBox.Show(string.Format(
                  "missing types for Event [{0}] in [{1}]",
                  eventName, depObj));
      }
   }
}

The local generic handler uses the delegates that were passed in and calls the command’s action.

C#
private void TheHandler(object sender, T e)
{
   var commander = sender as DependencyObject;
   if (commander != null)
   { //get the CommandBase
      var cmd = commandGetter(commander);
      if (cmd != null)
      { //wrap all the data by one object
         var param = new ParameterWrapper(parameterGetter(commander), 
		                             sender, e as RoutedEventArgs);
         var sCmd = cmd as ICommandFromEvent;
         if (sCmd != null)
         { //as part of this call check if the command is allowed to be executed
            sCmd.SmartExecute(param);
         }
         else
         { //Execute checking might have been done earlier
            cmd.Execute(param);
         }
      }
   }
}

XAML Declaration of the Command

The nice part of all of this commanding design is the ease with which I declare the command and the command parameter in XAML:

XML
<sdk:DataGrid	AutoGenerateColumns="True"
	HorizontalAlignment="Left"
	Margin="5"
	VerticalAlignment="Top"
	Grid.Row="1"			
	ItemsSource="{Binding DataToPresent}"
	cmd:SelectionChangedCommandBinder.Command="{Binding ItemSelectedCommand}"
	cmd:SelectionChangedCommandBinder.CommandParameter=
	          "{Binding SelectedItem, RelativeSource={RelativeSource self}}"
	/>

The above XAML snippet declares the DataGrid - the last 2 lines deal with the command declaration.

First I bind the ItemSelectCommand (represent an instance of CommandBase) with the Command Attached Property that resides in the SelectionChangedCommandBinder class.

The second line uses some XAML acrobatics to bind the SelectedItem property of the current control to the CommandParameter Attached Property that resides in the SelectionChangedCommandBinder class.

Steps to Create a Command

Every event that I want to convert to a command requires its own class with the 2 attached properties. The good news is that most apps need a limited number of event types. Also the creation process is quite simple, but process nonetheless.

  1. Create new command binder (I follow a name convention therefore I copy an existing one and then replace old event name by new one [I clear search criteria ‘Match whole Word’ - I get 6 replacements).
    • In the callback of command section, instantiate the Executor with the correct event handler type.
  2. Create a new command property (I always do it in the BaseViewModel in the region ‘command properties’).
  3. Add a new Action (I create a virtual one in the BaseViewModel in the region ‘command actions’.
  4. Instantiate the CommandBase in BaseViewModel in the Initialize method.
  5. In the specific view, bind the command in the XAML.

Step 1

C#
public class KeyDownCommandBinder
{
   private const string EName = "KeyDown";
   public static readonly DependencyProperty 
                 CommandParameterProperty = DependencyProperty.RegisterAttached
      (
      "CommandParameter",                            //Property name
      typeof(object),                                //Property type
      typeof(KeyDownCommandBinder),                  //Type of property owner
      new PropertyMetadata(CommandParameterChanged)  //Callback invoked on 
	                                                 //property value has change
      );

   public static void SetCommandParameter(DependencyObject depObj, object param)
   {
      depObj.SetValue(CommandParameterProperty, param);
   }

   public static object GetCommandParameter(DependencyObject depObj)
   {
      return depObj.GetValue(CommandParameterProperty);
   }

   private static void CommandParameterChanged(DependencyObject depObj, 
                              DependencyPropertyChangedEventArgs depArgs)
   {
   }

   public static readonly DependencyProperty 
                     CommandProperty = DependencyProperty.RegisterAttached
      (
      "Command",                             //Property name
      typeof(ICommand),                      //Property type
      typeof(KeyDownCommandBinder),          //Type of property owner
      new PropertyMetadata(CommandChanged)   //Callback invoked on 
	                                         //property value has change
      );

   public static void SetCommand(DependencyObject depObj, ICommand cmd)
   {
      depObj.SetValue(CommandProperty, cmd);
   }

   public static ICommand GetCommand(DependencyObject depObj)
   {
      return (ICommand)depObj.GetValue(CommandProperty);
   }

   public static void CommandChanged(DependencyObject depObj, 
                                    DependencyPropertyChangedEventArgs depArgs)
   {
      var executor = new Executor<keyeventargs />(EName);
      executor.CommandGetter = GetCommand;
      executor.ParameterGetter = GetCommandParameter;
      executor.CommandChanged(depObj, depArgs);
   }
}

Step 2

C#
private ICommand keyDownCommand = null;
public ICommand KeyDownCommand
{
	get { return keyDownCommand; }
	set
	{
		keyDownCommand = value;
		OnPropertyChanged("KeyDownCommand");
	}
}

Step 3

C#
protected virtual void KeyDownAction(object param)
{
	MessageBox.Show("KeyDown Action is not ready");
}

Step 4

C#
KeyDownCommand = new CommandBase(KeyDownAction);

Step 5

C#
cmd:KeyDownCommandBinder.Command="{Binding KeyDownCommand}"
cmd:KeyDownCommandBinder.CommandParameter=
		"{Binding Text, RelativeSource={RelativeSource self}}"

Or

C#
cmd:LoadedCommandBinder.Command="{Binding LoadedCommand}"
cmd:LoadedCommandBinder.CommandParameter="[ClassicView]"

History

  • First draft

License

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


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
General:) Pin
Kubajzz30-Sep-10 23:19
Kubajzz30-Sep-10 23:19 
GeneralRe: :) Pin
David Furshpan1-Oct-10 4:52
David Furshpan1-Oct-10 4:52 
GeneralRe: :) Pin
Kubajzz3-Oct-10 22:39
Kubajzz3-Oct-10 22:39 

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.