[UPDATE]
The code for the CommandManager has been rewritten due to some memory management issues. And together with that change, I have decided to put it on Codeplex. My blog posts about it are still more or less valid, but I suggest also taking a look at the code at http://agcommandmanager.codeplex.com/.
[/UPDATE]
Once again I return to the CommandManager that I have blogged about before. And I know it is getting boring and that I some day must leave that behind me and go forward. However, after having had a look at Prism/CAG/CAL and seen how Microsoft solves the commanding infrastructure, I have decided that the CommandManager still fills a purpose.
I also had a plan to retire the CommandManager completely and implement something similar using Behaviors. However, Behavior<T> or what ever it is called, inherits directly from DependencyObject instead of from FrameworkElement. This made it really hard to use in this situation, since it doesn’t support data binding. So I had to scrap that idea for now. It seems as if Microsoft uses this idea in Prism, but introduces an extra static object to get it to work… At least in the Prism implementation I have been looking at.
So why do I return to the CommandManager again? Just to argue that it is great and boost my ego? No…apparently, my previous implementation had a flaw. I might have a lot more, but I discovered one that caused problems. So I have rectified that…
So what was the flaw? Well, if you try using the Commands property and bind one of the properties in a Command, this will not work very well. If you start changing the DataContext or use an element to element binding it fails. Take the following code for example
<TextBox x:Name="txt" Width="100" Height="25" />
<Button Content="Bound Event" HorizontalAlignment="Center">
<cmd:CommandManager.Commands>
<cmd:CommandGroup>
<cmd:Command EventName="{Binding EventName}" ICommand="{Binding Execute}"
CommandParameter="{Binding Text,ElementName=txt}" />
</cmd:CommandGroup>
</cmd:CommandManager.Commands>
</Button>
This does not work. The CommandParameter binding will not work and if you make certain changes to the DataContext everything falls apart. And since binding the CommandParameter like this can be very useful, I had to fix it.
I won’t go through all the code changes I have made, but I will point out the major once and explain why the fix it. I have however also refactored the code a bit, making it smaller and easier to read.
But first and foremost, why does it fail? Well, the reason is that the bindings that we are using to bind the values depend on a DataContext. The Commands property will automatically be affected by the buttons DataContext. So if that changes, the Commands property would automatically be notified of this. However, the Commands property is then set to a complex object (CommandGroup). That is just a value. It will not be affected by any change. This is also why the following code works even in the old implementation
<Button Content="Click" HorizontalAlignment="Center"
cmd:CommandManager.CommandEventName="{Binding EventName}"
cmd:CommandManager.Command="{Binding Execute}"
cmd:CommandManager.CommandParameter="{Binding Text,ElementName=txt}">
</Button>
Cause, when the DataContext changes, all the bindings in this example is updated. In the previous example the value for the Commands property is not data bound and will not be refreshed.
So I had to figure out a way for the DataContext changes to be reflected in the Command objects. As long as these inherit from FrameworkElement, and get their DataCantext set whenever the context changed for the button, all the bindings will work like they should. So how do I get notified when the DataContext changes. As I have mentioned in a previous blog post, there is no event for this. So like I did in that post, I added a workaround for this in the CommandManager. I introduced a private attached DependencyProperty that would get notified whenever the DataContext changes.
private static DependencyProperty ContextProperty =
DependencyProperty.RegisterAttached("Context",
typeof(object),
typeof(CommandManager),
new PropertyMetadata(null, ContextChanged));
static void ContextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
CommandGroup cmds = GetCommands(obj);
if (cmds != null)
{
cmds.DataContext = e.NewValue;
}
}
As you can see, I use the callback for that property to set the DataContext of the CommandGroup. The CommandGroup is not a FrameworkElement though, so it doesn’t actually have a DataContext property. So I had to create one on my own. That property in turn passes the object to its CommandCollection by setting a property on the collection. The collection’s property setter in turn uses a loop to set the DataContext of all the children. So by introducing this DependencyProperty and a couple of added properties, I have managed to get the DataContext changes to flow down to the Command objects.
But as you might have noticed, the new DependencyProperty is not actually used in the code that I have shown you. To actually use it, I have modified the OnCommandsChanged method to hook up a binding to the object that has set the Commands property (in the previous example that means that I bind the button to the Context property). It looks like this after the change
static void OnCommandsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = obj as FrameworkElement;
CommandGroup cmds = e.OldValue as CommandGroup;
if (cmds != null)
{
cmds.Owner = null;
element.SetValue(ContextProperty, null);
}
cmds = e.NewValue as CommandGroup;
if (cmds != null)
{
cmds.Owner = element;
element.SetBinding(ContextProperty, new Binding());
}
}
As you can see, I also set the Context property to null if CommandGroup is changed. This makes sure that any orphan Command objects will not cause problems.
This however only solves parts of the problem. It solves general data binding issues, but it still won’t solve my previous example. If you use element bindings, it still will not work. Any binding that uses the ElementName depends on being bound to a FrameworkElement that is placed in the visual tree. Unfortunately, the Command objects will not be a part of the visual tree and thus will not work with element bindings.
This is not acceptable, so I had to solve that as well. While doing this, I refactored the Command class quite a bit. So I will probably have to show a bit more of that code to make it understandable. First of all, it uses 3 DependencyProperties, just like before. They are, as you might have figured out, named CommandParameter, ICommand and EventName (I really would like ICommand to be named Command, but it isn’t allowed to since the class is named that too). The only property that uses a callback is the EventName one, which calls a method called SetUpHandler(). It looks like this
public static DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter",
typeof(object),
typeof(Command),
null);
public static DependencyProperty ICommandProperty =
DependencyProperty.Register("ICommand",
typeof(ICommand),
typeof(Command),
null);
public static DependencyProperty EventNameProperty =
DependencyProperty.Register("EventName",
typeof(string),
typeof(Command),
new PropertyMetadata(new PropertyChangedCallback(OnEventNameChanged)));
public static void OnEventNameChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((Command)obj).SetUpHandler();
}
All of these properties are of course also accompanied by local properties giving “instance access” to them as well.
The SetUpHandler method is responsible for hooking up the handler for the event. So as soon as the EventName changes, the code makes sure that the correct handler is registered for that event. And if there is already a handler hooked up, it removes it to make sure it isn’t running any code when it shouldn’t. The method looks like this
private void SetUpHandler()
{
if (_currentEvent != null)
{
_currentEvent.RemoveEventHandler(Owner, _currentDelegate);
_currentEvent = null;
_currentDelegate = null;
}
if (Owner != null && !string.IsNullOrEmpty(EventName))
{
_currentEvent = GetEventInfo(EventName);
if (_currentEvent == null)
throw new Exception("Cannot find event: " + EventName);
_currentDelegate = GetDelegate();
_currentEvent.AddEventHandler(Owner, _currentDelegate);
}
}
I have also managed to modify the Handler() method to be private. I got a message from a blog reader who suggested that I’d change the reflection code to include some BindingFlags like this
private EventInfo GetEventInfo(string eventName)
{
Type t = _object.GetType();
EventInfo ev = t.GetEvent(eventName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
return ev;
}
private Delegate GetDelegate()
{
EventInfo ev = GetEventInfo(EventName);
return Delegate.CreateDelegate(ev.EventHandlerType, this, this.GetType()
.GetMethod("Handler",BindingFlags.Instance | BindingFlags.NonPublic), true);
}
Besides those three DependencyProperty properties, there is one other called Owner (used to be DependencyObject). The Owner property will be set to the control that “owns” the command. In our case the button. As soon as that property is set, I do a little workaround to get any element bindings to work. So whenever the Owner property is set, I call this method, which looks at one of the properties at the time and corrects any element binding
private void SetUpBindings()
{
if (Owner != null)
{
if (Owner.Parent == null)
{
((FrameworkElement)Owner).Loaded += delegate { SetUpBindings(); };
return;
}
CompensateElementBinding(CommandParameterProperty);
CompensateElementBinding(ICommandProperty);
CompensateElementBinding(EventNameProperty);
}
}
This makes sure that the Owner has actually been placed in a visual tree (placed on the control). If it hasn’t, I wait until the control is Loaded and then run the method again. After that, I call the CompensateElementBinding method for each one of the DependencyProperties.
The CompensateElementBinding checks to see if the property is in fact data bound. If it is, I check to see if the ElementName property set. If that is the case, I locate the indicated element and rebind the properties setting the Source instead of the ElementName property.
private void CompensateElementBinding(DependencyProperty property)
{
BindingExpression bindingExpression = this.GetBindingExpression(property);
if (bindingExpression == null)
return;
Binding binding = bindingExpression.ParentBinding;
if (binding.ElementName != null)
{
FrameworkElement rootVisualElement = GetRootElement();
string path = binding.Path.Path;
string sourceName = binding.ElementName;
object source = rootVisualElement.FindName(sourceName); ;
this.SetBinding(property, new System.Windows.Data.Binding(path) { Source = source });
}
}
The final piece of code that needs to be implemented is the GetRootElement() property. I initially tried to look at the current application’s RootVisual to get hold of the root element of the visual tree. Unfortunately, the GetRootElement() method will be called when my user control is created, which is often before the RootVisual has been set. Just imagine the following code from the App class
private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = new MainPage();
}
This will first create my user control, which will cause all bindings to bind and eventually my GetRootElement() to be called. Not until after this will the RootVisual be set. There are also other scenarios when using this approach will not work. Because of this, I must use a slower method. I must traverse up the tree to find the top most element. After that, I ask it to go down the tree again looking for a control with the correct name. It looks like this
private FrameworkElement GetRootElement()
{
if (Owner == null)
return null;
FrameworkElement parent = (FrameworkElement)Owner.Parent;
while (parent.Parent != null)
{
parent = (FrameworkElement)parent.Parent;
}
return parent;
}
That’s it. It really isn’t that hard once you know what needs to be done. Too bad it takes a bit of time to figure out what needs to be done.
Is have zipped up the new version together with a VS2008 solution containing the CommandManager, a test application and a web application hosting the test application. Feel free to download it and use it wherever you need it. You are of course welcome to mention me and my blog to your friends and family if you appreciate the code. If you have any questions, don’t hesitate to ask them but I can’t promise that I can answer them. But any feedback is appreciated!
Code: FiftyNine.CommandManager.zip (141.85 kb)
Oh yeah…I just remembered. The CommandManager is now Silverlight 3 only. Sorry about that. But Silverlight 2 doesn’t support mucking around with bindings from code like I do. On the other hand, you can’t use element bindings in 2 so you are probably fine with the previous version. Otherwise, you can always take the source code and remove the element binding specific stuff and it should work with Silverlight 2.