A little while ago I got a question on my blog about how to attach multiple commands to a control. That was something that is so obvious and so obviously not supported in the first version of the command manager. So I quickly wrote a message back saying that I would build that and it would be up on the blog soon. Well…I ran into some trouble…so it took a little longer than expected. And the solution might not be the best, but it works. So here it is…the CommandManager 2.0.
First off, I will only publish the changes in the code. It is only an extension of the old one. I have however refactored a bit of the old manager as well, so take a look at the new manager’s code by downloading the zip at the bottom of the page… The blog post is mostly to show you what the problems were and how I decided to implement it… The old implementation is available here
So, the goal of this exercise was to enable something like this
1: <Rectangle Width="50" Height="50">
2: <Cmd:CommandManager.Commands>
3: <Cmd:CommandGroup>
4: <Cmd:Command EventName="MouseEnter" CommandParameter="Enter" ICommand="{Binding MouseEnterCommand}" />
5: <Cmd:Command EventName="MouseLeave" CommandParameter="Leave" ICommand="{Binding MouseLeaveCommand}" />
6: </Cmd:CommandGroup>
7: </Cmd:CommandManager.Commands>
8: </Rectangle>
So how hard can that be. Well, it isn’t, but it was. The solution is not hard, I just had to think a little different then I did to begin with. Why? Well, you will see. DataContext and bindings make certain things a little tricky when using attached properties…apparaently.
The first step is to create a new class called CommandGroup. This is, as you see, the type that the Commands property will be. It isn’t very complicated, but has a little tweak that took me some time to figure out… Just create a new class called CommandGroup. It’s going to hold a collection of commands internally, have a readonly property for that collection, and two other internal properties. One called ChildDataContext of type object and one called Owner of type DependencyObject. This two internal methods will only forward their information to the collection. The collection object will be created next. But first off, here is the CommandGroup
1: [ContentProperty("Children")]
2: public class CommandGroup
3: {
4: CommandCollection _children = new CommandCollection();
5:
6: public CommandCollection Children
7: {
8: get { return _children; }
9: }
10: internal object ChildDataContext
11: {
12: get { return Children.ChildDataContext; }
13: set { Children.ChildDataContext = value; }
14: }
15: internal DependencyObject Owner
16: {
17: get { return Children.Owner; }
18: set { Children.Owner = value; }
19: }
20: }
Oh yeah…I forgot to mention. It should also be adorned with a ContentProperty attribute, which tells the Xaml parser that all child objects should be added to the Children property.
The CommandCollection is a simple little class that wraps a List<Command> and exposes it through an IList interface. It also has two properties as you have already seen. The ChildDataContext is responsible for delegate the DataContext to the individual Commands so that their databindings work. (This was my problem…I had some problem getting the DataContext to flow nicely through the system since it isn’t inherited in attached properties like it is in the visual parts) The Owner property is there to delegate the DependencyObject that uses the commands collection so that the command can bind it’s handler to the correct object. So if we ignore the IList interface things, it looks sort of like this…
1: public class CommandCollection : IList
2: {
3: List<Command> _items = new List<Command>();
4: object _childContext;
5: DependencyObject _owner;
6:
7: internal List<Command> Items
8: {
9: get { return _items; }
10: set { _items = value; }
11: }
12: internal object ChildDataContext
13: {
14: get { return _childContext; }
15: set
16: {
17: _childContext = value;
18: foreach (var item in Items)
19: {
20: item.DataContext = _childContext;
21: }
22: }
23: }
24: internal DependencyObject Owner
25: {
26: get { return _owner; }
27: set
28: {
29: _owner = value;
30: foreach (var item in Items)
31: {
32: item.DependencyObject = _owner;
33: }
34: }
35: }
36:
37: [IList Members]
38: [ICollection Members]
39: [IEnumerable Members]
40: }
So…it wasn’t much harder than that. Well…there are actually a few more things to do before I’m done. I need to register the attached property…or actually two of them. The first one is simple…it is the CommandGroup property. It looks like this
1: public static readonly DependencyProperty CommandsProperty =
2: DependencyProperty.RegisterAttached("Commands",
3: typeof(CommandGroup),
4: typeof(CommandManager),
5: new PropertyMetadata(new PropertyChangedCallback(OnCommandsChanged)));
6:
7: public static CommandGroup GetCommands(DependencyObject obj)
8: {
9: return (CommandGroup)obj.GetValue(CommandsProperty);
10: }
11: public static void SetCommands(DependencyObject obj, CommandGroup commands)
12: {
13: obj.SetValue(CommandsProperty, commands);
14: }
15: static void OnCommandsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
16: {
17: FrameworkElement element = obj as FrameworkElement;
18: element.SetBinding(CommandsContextProperty, new Binding());
19: CommandGroup cmds = e.OldValue as CommandGroup;
20: if (cmds != null)
21: {
22: cmds.Owner = null;
23: }
24: cmds = e.NewValue as CommandGroup;
25: if (cmds != null)
26: {
27: cmds.Owner = obj;
28: }
29: }
So…the registration was simple…and the getters and setters…but what the H happened in the callback method. Well…that’s where the DataContext magic is being created. Well…lets just first look at the other parts. What it does is quite simple. It looks if there is an old CommandGroup in the property and in that case sets the Owner property to null. That will cause all the registered CommandGroup to set the Owner property on the CommandCollection, which in turn sets the object on the Commands so that they disconnect their handlers. What? Well…you will understand if you look at the complete code…I promise you… The other part of the callback is setting another binding. This binding binds another attached property to an “empty” binding…hence it will bind the CommandsContext property of the object to it’s DataContext. So if the DataContext ever changes…the CommandsContext property will change as well. Why is this important? Well, it causes the context to flow all the way from the object down through the CommandCroup to the CommandCollection and finally down to the Command objects… So…where is the nice “flow code”…well it looks like this
1: private static readonly DependencyProperty CommandsContextProperty =
2: DependencyProperty.RegisterAttached("CommandsContext",
3: typeof(object),
4: typeof(CommandManager),
5: new PropertyMetadata(new PropertyChangedCallback(OnCommandsContextChanged)));
6:
7: static void OnCommandsContextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
8: {
9: CommandGroup cmds = obj.GetValue(CommandsProperty) as CommandGroup;
10: if (cmds == null)
11: return;
12: cmds.ChildDataContext = e.NewValue;
13: }
Notice that the CommandsContextProperty is private and will never be seen or used externally. It is only there to cause that “flow”.
So what was the change I did that caused a refactor in the previous manager’s code? Well, I refactored a little bit of it and created a new public object out of a previous private one and modified it’s implementation. The new object is the one called Command. That class has 3 dependency properties. One for the event name, one for the ICommand and one for the command parameter. On top of that, it has logic for connecting and disconnecting eventhandlers as well as a few other little things. So the refactor of that made me modify the old manager implementation as well to use that new object. It’s all in the code that is available for download…here!
So…that’s it…a new and improved manager. And also a little challenge. It is probably not THAT hard, but I haven’t had time to have a look at it yet. The Command object has a “public void Handler(object sender, EventArgs e)” method that is used as a handler for all the possible events that you whish to connect it to… Well…too much information…so…the problem. Well, I want to make it private, but the logic for connecting the handler breaks if I do, even if I try telling the reflection method that it is not public. Well…is you can figure it out, don’t hesitate to send me an e-mail…
I also want to add a tiny disclaimer to mention that it is a “raw copy” of the implementation. I haven’t had time to refactor and rewrite and make everything nice… That is up to you…