After having worked a bit with the Mode View ViewModel pattern in Silverlight, I've sort fallen into a trance chanting "MVVM, MVVM, MVVM". It gives the developer such a good platform to work with. The MVVM pattern is a modified version of the MVC and MVP patterns. By now, you are probablyabout to closer your browser due to the pattern rant I'm on. Well, don't. This isn't about MVVM really, only a little. The MVVM pattern was "created" by John Gossman at Microsoft, specifically to target WPF. This also means that it works great in Silverlight. But Silverlight is smaller, and missing some features... The CommandManager is one of them. This post shows a way to handle this shortcoming.
The Silverlight .NET framework contains an ICommand interface, that is used extensively in WPF. In Silverlight however, there is no Command implementation and no manager to work with it. Several bloggers in the community have tried to solve this with different manager implementations, but I couldn't find one that fit my needs. Most of them are limited to handle commands connected to button clicks. I wanted one that could handle any event. So here it is...
But first I wanna explain why you need a command manager and what the ICommand is used for. If you use MVVM you have a viewmodel that wraps your model and the data needed for presentation. In the VM you also add different logic needed for the view. Logic that can for example be used to show and hide "loading" symbols... Unfortunately, we still need to add functionality in the "code-behind" for our user control. We need to handle button clicks and then have the clicks make some changes to the VM. So, basically we are just adding evenhandlers in our code to route the call to the VM. The VM in turn makes some changes to its data and that is reflected in the view through its bindings. What if we could bind the eventhandlers directly to thw VM. Telling the button to call a method on the VM. Well, that is easy, just add eventhandler methods on the VM and have the "code-behind" for the view hook them up. No...that makes it so that the view must know about its VM, which is bad since we might wanna re-use the view for different VM. So to keep it decoupled, we need another solution.
In WPF, this means Commands. Several controls in WPF supports a Command property if the type ICommand. A object implementing the ICommand interface is then exposed from the VM as a property. That command is then bound to the Command property through data binding. That way the VM can "handle events" in the view without the view needing to care what happens. That means that we get good decoupling and that we can test the VM with automated test very easily.The problem in Silverlight is that no controls expose a Command property, and as such makes this whole implementation great but impossible.
The handling of commands in WPF is done through an object called the CommandManager. This is another object that is missing in Silverlight. This is the class that this post tried to create. It will also contain a simple Command implementation, but it is all built on ICommand so it can easily be exchanged for something more specific.
So, the needs are the following. I need to equip every control in Silverlight with a couple of properties. One property defining what ICommand to work with, as well as one that defines what event should cause the execution of the command. Finally, the ICommand interface also specifies that there should be a parameter that should be sent to the Execute() method. So a parameter property is needed as well. Well...this sounds perfect for a couple of attached DependencyProperties...and that is of course how I have decided to solve it.
Start off by creating a new Silverlight Class Library. This is definitely something that will be re-used, so I recommend adding it to a class library. In the class library, add a single, static class called CommandManager. You could name it what ever you want, but mine is called CommandManager and all code will reflect this. Inside the CommandManager, add 3 static DependencyProperty objects - Command, CommandEventName and CommandParameter.
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command",
typeof(ICommand),
typeof(CommandManager),
new PropertyMetadata(new PropertyChangedCallback(OnCommandChanged)));
public static readonly DependencyProperty CommandEventNameProperty =
DependencyProperty.RegisterAttached("CommandEventName",
typeof(String),
typeof(CommandManager),
new PropertyMetadata(new PropertyChangedCallback(OnCommandEventNameChanged)));
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter",
typeof(object),
typeof(CommandManager),
new PropertyMetadata(new PropertyChangedCallback(OnCommandParameterChanged)));
They all have callback methods defined. By the way, all methods and properties and things are static since it is a static class... Since we are registering attached properties we also need to add get and set methods for each of these properties.
public static ICommand GetCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand command)
{
obj.SetValue(CommandProperty, command);
}
public static string GetCommandEventName(DependencyObject obj)
{
return (string)obj.GetValue(CommandEventNameProperty);
}
public static void SetCommandEventName(DependencyObject obj, string commandEventName)
{
obj.SetValue(CommandEventNameProperty, commandEventName);
}
public static string GetCommandParameter(DependencyObject obj)
{
return (string)obj.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(DependencyObject obj, string commandParameter)
{
obj.SetValue(CommandParameterProperty, commandParameter);
}
Implementing those are REALLY boring, but they are needed... Copy/Paste is good... Next I have decided to use a class to keep track of all the objects with commands and their information. This class will contain all the different parts of the "command declaration", that is the ICommand to use, the EventName and the DependencyObject declaring it. It will also be responsible for hooking up the eventhandlers. The class is declared as a private class inside the CommandManager. This keeps it from "poluting" the namespace with things that nobody needs. I call the class CommandDeclaration. It looks like this:
private class CommandDeclaration
{
DependencyObject _object;
ICommand _cmd = null;
string _eventName = String.Empty;
public CommandDeclaration(DependencyObject obj)
{
_object = obj;
}
private void VerifyEventName(string name)
{
EventInfo ev = GetEventInfo(_eventName);
if (ev == null)
throw new Exception("Cannot find event: " + _eventName);
}
public DependencyObject Object
{
get
{
return _object;
}
}
public object CommandParameter
{
get;
set;
}
public ICommand Command
{
get
{
return _cmd;
}
set
{
if (_cmd == value)
return;
if (_cmd != null)
DisconnectHandler();
_cmd = value;
if (_cmd != null)
ConnectHandler();
}
}
public string EventName
{
get
{
return _eventName;
}
set
{
if (_eventName == value)
return;
if (!string.IsNullOrEmpty(_eventName))
DisconnectHandler();
_eventName = value;
if (string.IsNullOrEmpty(_eventName))
return;
VerifyEventName(_eventName);
ConnectHandler();
}
}
}
urn;
VerifyEventName(_eventName);
if (!String.IsNullOrEmpty(_eventName))
ConnectHandler();
}
}
}
As you might have noticed in the previous code snippet, I haven't shown of all the code. I would like to explain it before I do. But, what you see is that every time the Command and EventName is changed, the code disconnects any previous handler and connects a new if needed. This is done through calls to ConnectHandler() and DisconnectHandler(). To make my life simpler I have also added a little helper method called GetEventInfo(). It uses reflection to get hold of an EventInfo object representing the event to use. The method looks like this
private EventInfo GetEventInfo(string eventName)
{
Type t = _object.GetType();
EventInfo ev = t.GetEvent(eventName);
return ev;
}
So, it is basically just a little wrapper for some reflection. If everything checks out, the ConnectHandler() and DIsconnectHandler() methods come into play. Lets start by looking at the ConnectHandler() method. But first, there is one more method involved. The Handler() method. It has a generic eventhandler signature so that it can be used for any event that might be used. So it will be the actual handler for the event that has been defined, and will then "route" the call to the command.
public void Handler(object sender, EventArgs e)
{
if (Command != null && Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
}
The ICommand defines a CanExecute() method as well. This is called to verify that the command can actually execute. Next up is the ConnectHandler(). It starts by verifying that both the Command and EventName has been set. Since these will be set independently, they need to be checked. After this check passes, it gets hold of the event. If it doesn't exist, it throws an ugly exception. That should probably be cleaned up and throw something more specific. After that it goes on to connect the handler
internal void ConnectHandler()
{
if (Command != null && !String.IsNullOrEmpty(EventName))
{
EventInfo ev = GetEventInfo(_eventName);
if (ev == null)
throw new Exception("Cannot find event: " + _eventName);
ev.AddEventHandler(_object, GetDelegate());
}
}
As you can see, a lot of the magic is actually not in here. It just gets a Delegate from the GetDelegate() method and adds it to the event. The GetDelegate() method gets hold of an EventInfo class to see what type of event it is. It then creates a new delegate for this signature and returns it
private Delegate GetDelegate()
{
EventInfo ev = GetEventInfo(EventName);
return Delegate.CreateDelegate(ev.EventHandlerType, this ,this.GetType().GetMethod("Handler"), true);
}
The last piece of this pussle is the DisconnectHandler() method. It starts by verifting that there is an event name and then gets hold of an EventInfo class and then removes the handler.
internal void DisconnectHandler()
{
if (!String.IsNullOrEmpty(EventName))
{
EventInfo ev = GetEventInfo(EventName);
if (EventName != null)
{
ev.RemoveEventHandler(_object, GetDelegate());
}
}
}
Now...after having built this little "utility" class, it is time to get back to the CommandManager class. Since the different properties will be set independently at different times, there has to be a way to locate the CommandDeclaration for a specific DependencyObject. This is solved by adding a generic Dictionary to the CommandManager, as well as a little helper method to retrieve it.
static Dictionary<DependencyObject, CommandDeclaration> _commandDeclarations;
static CommandManager()
{
_commandDeclarations = new Dictionary<DependencyObject, CommandDeclaration>();
}
private static CommandDeclaration GetCommandDeclaration(DependencyObject obj)
{
if (!_commandDeclarations.ContainsKey(obj))
{
CommandDeclaration decl = new CommandDeclaration(obj);
_commandDeclarations.Add(obj, decl);
}
return _commandDeclarations[obj];
}
The GetCommandDeclaration is also be responsible for adding a new declaration if none is available. This makes the future implementation so much simpler.
The next part is to implement the functionality using the DependencyProperty callbacks. They are so simple, that we can take them all in one go
static void OnCommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
GetCommandDeclaration(obj).Command = (ICommand)e.NewValue;
}
static void OnCommandEventNameChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
GetCommandDeclaration(obj).EventName = (string)e.NewValue;
}
static void OnCommandParameterChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
GetCommandDeclaration(obj).CommandParameter = e.NewValue;
}
That's it. That's all there is to it. Next up is to test it out to see if it works. I did that the uggly way. This is how...
Add a new Silverlight Application to the project. Whether or not you want to have a web application or a dynamic page is up to you. Start by adding a new class to the project. I called mine MyCommand. As you probably realized, it will be the class implementing the ICommand interface. So, do that. Add the ICommand interface to the class. To keep it simple, I let my CanExecute() return true at all times. Next I decided that I would let my MyCommand raise an event when it was executed. So to satisfy this need, I created a new EventArgs class called ParameterEventArgs. Since the Execute() method takes a parameter in, it needs to be forwarded to the event handler. So the ParameterEventArgs looks like this
public class ParameterEventArgs : EventArgs
{
public object Parameter;
}
Next I added an event to my MyCommand class and had the Execute() method raise it. Yes...there should have been a protected virtual OnExecuting method in between, but I don't care. It's a proof of test to verify my implementation.
public class MyCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (Executing != null)
Executing(this, new ParameterEventArgs() { Parameter = parameter });
}
public event EventHandler<ParameterEventArgs> Executing;
}
Next on my agenda was a little ViewModel. So I created a new class called ViewModel. In a real implementation it would probably have a better name...but once again...it's a test...
The model is simple. It implements INotifyPropertyChanged and exposes a single string property called ModelText. Besides that single text property, it has 2 ICommand properties called ClickCommand and MouseEnterCommand. The two ICommand properties are then populated from the constructor and have their Executing events wired to some simple handlers
public class ViewModel : INotifyPropertyChanged
{
string _modelText;
public ViewModel()
{
MyCommand cmd = new MyCommand();
cmd.Executing += new EventHandler<ParameterEventArgs>(ClickCommand_Executing);
ClickCommand = cmd;
MyCommand cmd2 = new MyCommand();
cmd2.Executing += new EventHandler<ParameterEventArgs>(MouseEnterCommand_Executing);
MouseEnterCommand = cmd2;
}
void ClickCommand_Executing(object sender, ParameterEventArgs e)
{
ModelText = "Button Clicked";
}
void MouseEnterCommand_Executing(object sender, ParameterEventArgs e)
{
ModelText = e.Parameter.ToString();
}
public ICommand ClickCommand { get; set; }
public ICommand MouseEnterCommand { get; set; }
public string ModelText
{
get
{
return _modelText;
}
set
{
_modelText = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("ModelText"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
There are probably several not so beautiful things with this VM, but once again...test... Finally it is time to set up the Page.xaml.
The first thing on the list was to add a reference to the CommandManager project and add that namespace to the Xaml. Next I added some simple controls to it, making it ugly as hell, but using the commands...
<UserControl x:Class="CmdManager.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Cmd="clr-namespace:CommandManager;assembly=CommandManager"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="Yellow">
<TextBlock Text="{Binding ModelText}" HorizontalAlignment="Left" VerticalAlignment="Top" />
<Button HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,60,0,0" Content="Click me!"
Cmd:CommandManager.CommandEventName="Click"
Cmd:CommandManager.Command="{Binding ClickCommand}" />
<Rectangle Width="50" Height="50" x:Name="rect" Fill="Red"
Cmd:CommandManager.CommandEventName="MouseEnter"
Cmd:CommandManager.Command="{Binding MouseEnterCommand}"
Cmd:CommandManager.CommandParameter="Mouse Enter" />
</Grid>
</UserControl>
As you can see in the Xaml I added, I use several data binding expressions. I bing the Text of my TextBlock to the ModelText property of my VM and the commands in my controls to the ClickCommand and MouseEnterCommand. The final step before testing it is to add the VM to the control. Generally this would probably be done using Dependency Injection or something more complex. But in my simple test, it is just created in the constructor and added to the DataContext.
public Page()
{
InitializeComponent();
ViewModel vm = new ViewModel();
this.DataContext = vm;
}
So, that is it... It should work. At least it does for me. If there is anything idea for improvements, questions or thoughts, please add them as comments. It's always interesting to see what people likes and dislikes. The code is available as a zip here: CommandManager.zip (594.28 kb)