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.
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:
private const string EName = "SelectionChanged"; public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached
(
"Command", typeof(ICommand), typeof(SelectionChangedCommandBinder), new PropertyMetadata(CommandChanged) 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);
}
protected static void CommandChanged(DependencyObject depObj,
DependencyPropertyChangedEventArgs depArgs)
{ 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.
public void CommandChanged(DependencyObject depObj,
DependencyPropertyChangedEventArgs depArgs)
{ if (depObj != null)
{
Type type = depObj.GetType();
var eventInfo = type.GetEvent(eventName);
if (eventInfo != null)
{ var delegateType = eventInfo.EventHandlerType;
if (delegateType != null)
{ Delegate handler = Delegate.CreateDelegate(
delegateType, this, "TheHandler");
if (handler != null)
{ 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.
private void TheHandler(object sender, T e)
{
var commander = sender as DependencyObject;
if (commander != null)
{ var cmd = commandGetter(commander);
if (cmd != null)
{ var param = new ParameterWrapper(parameterGetter(commander),
sender, e as RoutedEventArgs);
var sCmd = cmd as ICommandFromEvent;
if (sCmd != null)
{ sCmd.SmartExecute(param);
}
else
{ 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:
<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.
- 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.
- Create a new command property (I always do it in the
BaseViewModel
in the region ‘command properties’).
- Add a new Action (I create a virtual one in the
BaseViewModel
in the region ‘command actions’.
- Instantiate the
CommandBase
in BaseViewModel
in the Initialize
method.
- In the specific view, bind the command in the XAML.
Step 1
public class KeyDownCommandBinder
{
private const string EName = "KeyDown";
public static readonly DependencyProperty
CommandParameterProperty = DependencyProperty.RegisterAttached
(
"CommandParameter", typeof(object), typeof(KeyDownCommandBinder), new PropertyMetadata(CommandParameterChanged) );
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", typeof(ICommand), typeof(KeyDownCommandBinder), new PropertyMetadata(CommandChanged) );
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(EName);
executor.CommandGetter = GetCommand;
executor.ParameterGetter = GetCommandParameter;
executor.CommandChanged(depObj, depArgs);
}
}
Step 2
private ICommand keyDownCommand = null;
public ICommand KeyDownCommand
{
get { return keyDownCommand; }
set
{
keyDownCommand = value;
OnPropertyChanged("KeyDownCommand");
}
}
Step 3
protected virtual void KeyDownAction(object param)
{
MessageBox.Show("KeyDown Action is not ready");
}
Step 4
KeyDownCommand = new CommandBase(KeyDownAction);
Step 5
cmd:KeyDownCommandBinder.Command="{Binding KeyDownCommand}"
cmd:KeyDownCommandBinder.CommandParameter=
"{Binding Text, RelativeSource={RelativeSource self}}"
Or
cmd:LoadedCommandBinder.Command="{Binding LoadedCommand}"
cmd:LoadedCommandBinder.CommandParameter="[ClassicView]"
History