Click here to Skip to main content
15,867,756 members
Articles / Desktop Programming / XAML

CommandTree - Improved CommandMap for DelegateCommands

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
14 Jan 2016CPOL7 min read 12.7K   187   6  
Organize your DelegateCommands hierarchical in one Viewmodel-Object - A Style generates a proper Menu from that. You also can set individual CommandBindings, to Buttons or MenuItems.

Introduction

A few days ago, I came in contact with the CommandMap-Class, as published here, by Robert Winkler. It uses the TypeDescriptionProviderAttribute, and some little cumbersome stuff, to achieve, that XAML takes DelegateCommands as bindable Property, although they are no Properties, but only laying within a Dictionary.

I took that basic idea, that one can make a Collection publish each of its elements as a different bindeable Property. And then I developed my own CommandHelpers.

Concept

I extended the (hopefully) wellknown Delegate-Command-class with some Viewmodel-like Properties, named Header, ID and Children. I named the new class TreeCommand.
Now a Button or a MenuItem can bind its Header (or Button: Content) simply to the Header of the TreeCommand, where it is bound to, anyway. And in Xaml-Designer, you can specify an ID, which addresses a specific Command of the CommandTree. ("CommandTree" I named the Container-Class of those TreeCommands)

Still more comfortable is the feature, that MenuItems can bind their ItemsSource-Property to the Commands Children.
In effect, in XAML, you need no longer set up any MenuItem. Just bind the Menu in general to the CommandTree, and the BindingSystem sets up the complete MenuItem-hierarchy, according to the hierarchy as defined in the CommandTree-Object.

Note: These are two different and independent ways to access TreeCommands: either let XAML generate the complete MenuItem-Tree, or address them individually by ID.

In the Sample-Sources, I also bound the CommandTree to a common Treeview - this illustrates a bit the inner logic of the hierarchical IDs ("numbers" at the left side of the TreeNodes):

Image 1

The Hierarchical ID

The ID is a string (not a number!), which represents the index of the nodes. Each char addresses one level. For Instance "13" addresses the second node in the Tree, and from that its fourth Child (headered as "Command1").
To make more than 10 Nodes accessible, letters are valid too, e.g., 'a' addresses the 10th child, and 'z' addresses the 36th.

I don't believe that anybody ever will create Menus, where a single Item is populated with more than 36 submenus, but null problemo: the logic doesn't end at 'z', but continues on upto char.MaxValue.
Means: the count of direct SubCommands  within a single parent-TreeCommand is limited by 65438 - more Items would blast the ID-generation as it is implemented yet.
Note: This "limitation" only refers to direct children - nested childrens are not meant.

Deriving Headers from Method-Names

I made many overloads to make composing a CommandTree as simple as possible. Some overloads allow to omit specifying the Command-Header - in this case, it simply derives the header from the method-Name, which the DelegateCommand will execute - samples will follow in the "flow-interface"-section.
(Side-Note: On anonymous methods, it's not that representable, but at least is valid.)

Advanced Headers

I designed the TreeCommand.Header-Property as Object. Usually, it will contain a string, but you can put in whatever you want. And Xaml-Styling and Templating give big space to be creative.
(But I admire: I didn't try it out for myself, yet.)

Flow-Interface-Design

In general, a flow-interface-Design appears, when you make a setter-Method return the Owner-Object itself. See StringBuilder-Class: The Append/Insert/Replace - methods all return the addressed StringBuilder itself, so you can concatenize several actions within one Line:

C#
sb.AppendLine("Hello folks!").Append("my name is ").AppendLine
(System.Environment.MachineName).AppendLine("I am the third line.");

(you can do so, not must ;) )

The same principle works on my TreeCommand:

C#
root.AddFlow(LoadDataset).AddFlow(SaveDataset, () => myDataset.HasChanges());

This sets up two commands, the second includes an anonymous canExecute-Delegate.

But I carried it to the top, by mis-using overloaded Indexers as flow-design-add-methods, and get rid of the need to type a Method-Name at all:

C#
var secondCommand = root[LoadDataset]["_ChildMenus..."][UpdateEmployees, 
() => lineCount() > 2]["Save", SaveDataset, () => lineCount() > 4][1];

That one shows four different ways to add ChildCommands, within a one-liner. Note the last Index performs no flow-Interface, but is a real Index, and retrieves the second TreeCommand, (labeled as "_ChildMenus...").

Like it or not, and if not, you have permission to remove the code, which achieves (or perpetrates?) that ;)

Reuseable Styles

You can ease the Bindings to a CommandTree very much, when you write (or copy out) some reuseable Styles, which takes over most of the (boiler-plate) work:

XML
<Style TargetType="MenuItem">
   <!-- derive Header from Command -->
   <Setter Property="Header" Value="{Binding Command.Header, 
    RelativeSource={RelativeSource Self},FallbackValue=##Err##}"/>
</Style>

The above binds automatically the MenuItem-Header to the TreeCommand-Header, and moreover it alerts Binding-Mismatches on GUI.
Since that works already at DesignTime, you have direct Feedback in the Visual-Preview, when you try and bind to an invalid ID.

In the shadow of the above MenuItem-Style, a MenuItem is completed by only bind its Command, for instance the third TreeCommand (2) within the first TreeCommand (0) within the CommandTree (Commands):

XML
<MenuItem  Command="{Binding Commands.02}"/>

Don't worry, that the ID won't be transparent, and you don't know the actually addressed command. As said, the Visual-Preview of the Xaml-Editor displays the commands Header.
(And you can try out arbitrary IDs - the Preview will accompany you faithfully.)

(There is a corresponding Style for Buttons I don't repeat here.)

Next the Style to set up a complete Menu with your CommandTree:

XML
<Style x:Key="commandTreeMenu" TargetType="Menu">
  <Setter Property="ItemContainerStyle" >
    <Setter.Value>
      <Style TargetType="MenuItem">
        <!-- request Header from TreeCommand -->
        <Setter Property="Header" Value="{Binding Command.Header, 
         RelativeSource={RelativeSource Self},FallbackValue=##Err##}"/>
        <!-- request ItemsSource (SubMenuItems) from TreeCommand -->
        <Setter Property="ItemsSource" 
         Value="{Binding Command, RelativeSource={RelativeSource Self}}"/>
      </Style>
    </Setter.Value>
  </Setter>
</Style>

Now the complete Menu is set up by this One-Liner:

XML
<Menu ItemsSource="{Binding Commands}" Style="{StaticResource commandTreeMenu}"/>

I repeat: You don't need to touch this Menu anymore. All changes to the CommandTree in the Viewmodel are at the same time Changes to the Menu in XAML (except renaming the CommandTree-Property itself).

The PropertyDescription-Magic

One of the CommandTrees Concerns is to derive CustomTypeDescriptor, override the GetProperties()-Method, and retrieve a Set of PropertyDescriptors (for each Property one Descriptor). Databinding fetches these PropertyDescriptors, and uses them to access the Properties (means: the TreeCommands). See the mentioned Override:

C#
public class CommandTree : CustomTypeDescriptor, IEnumerable<TreeCommand> {
   //...

   /// <summary> for internal use, to support Databinding </summary>
   [EditorBrowsable(EditorBrowsableState.Never)]
   public override PropertyDescriptorCollection GetProperties() {
      //unconventional, but efficient: the PropertyDescriptors are Fields of the TreeCommand-Instances
      return new PropertyDescriptorCollection(SetupIDs().Select(cmd => cmd.Descriptor).ToArray());
   }

Hmm - and how the PropertyDescriptor looks like?
Again, there exists an abstract base-class I can derive, and only do the absolutely necessary overrides:

C#
  1  publicclass DelegatePropertyDescriptor : PropertyDescriptor {
  2  
  3     private Func<string> _GetName; private Func<object> _GetValue;
  4     
  5     public DelegatePropertyDescriptor(Func<string> getName, Func<object> getValue)
  6        : base(getName(), null) {
  7        if (getName == null || getValue == null) throw new ArgumentNullException();
  8        _GetName = getName; _GetValue = getValue;
  9     }
 10     public override bool IsReadOnly { get { return true; } }
 11     public override bool CanResetValue(object component) { return false; }
 12     public override Type ComponentType { get { throw new NotImplementedException(); } }
 13     public override object GetValue(object component) { return _GetValue(); }
 14     public override string Name { get { return _GetName(); } }
 15     public override Type PropertyType { get { return _GetValue().GetType(); } }
 16     public override void ResetValue(object component) { throw new NotImplementedException(); }
 17     public override void SetValue(object component, object value) { throw new NotImplementedException(); }
 18     public override bool ShouldSerializeValue(object component) { return false; }
 19  }

If you look closely, you see, that this PropertyDescriptor knows nothing about its described Property! :wtf:
It simply holds two Delegates (line#3), and in the relevant overrides (#13-#15), it redirects the requests of the Binding-System to somewhere out of here.

"somewhere out of here" means: to the particular TreeCommand-Instance, which holds its own DelegatePropertyDescriptor-Instance - see the TreeCommand-Constructor:

C#
public TreeCommand(object header, Action executeMethod, Func<bool> canExecuteMethod = null)
   : base(executeMethod, canExecuteMethod) {
   // ...
   Descriptor = new DelegatePropertyDescriptor(() => ID, () => this);
}
public readonly DelegatePropertyDescriptor Descriptor;

() => ID, () => this - that is the magics essence.

It says: "Dear Descriptor, go tell the Binding-System, that there is a Property, and submit my ID as its Name.
And the Value of that Property is me myself."

Strange, isn't it?
Remember, how two listings above the CommandTree collects the PropertyDescriptors of all of its TreeCommands, as Return-Value of the GetProperties()-Override.
That means, it is the CommandTree, having all these Properties.

And that's why the BindingSystem accepts these IDs as Binding-Path. :)

The PropertyDescription-Disadvantage

Normal Properties are known in Xaml-Editor-PropertyGrid and Intellisense, and that is very helpful: When you set a Binding, you can follow Intellisenses Proposes, or simply pick the Binding from the "CreateBindings"-Dialogue.
Unfortunately, that's not supported for PropertyDescriptor-"PseudoProperties" - the Xaml-Designer does not know them.

But especially on a CommandTree, the lack of this is not that heavy: At first, the CommandTree-Object itself, as normal Viewmodel-Property is visible, so binding the Menu in complete isn't affected at all.

And even if you want to bind to a specific ID, the "number-like" logic of the ID gives passable orientation.

By the way, that Mis-Feature annoyed me from the very beginning (while examining the original CommandMap), and just to demonstrate the difference, I coded a CommandMap2, with two hardcoded Commands in it. When opening the Databinding-Editor, the CommandMap2 can be expanded to inspect its Sub-Properties, while the CommandMap is not expandable - see picture:

Image 2

Finally, one gave me the link, where that is documented as a Note, on MSDN.

Conclusion

Especially for Standard-Menus, the CommandTree-Class gives a kind of "new look&feel", since the Control shifts completely into the Viewmodel, and in XAML, there is nothing left to do anymore.
But even while designing specific addressed Commands, it's not that un-helpful ;) .
Note that the classes aren't perfectly worked out yet - several further demands I see upcoming:

  • firstly the support of CommandParameters, maybe even typed Params
  • Icons
  • Tooltips

Another point may be, that my Code-Design in general is improvable. Maybe the pattern "Composition over Inheritance" would have been applicable, as a better approach, instead of deriving DelegateCommand and enhance it with all the mentioned stuff.

And may be not - I didn't try it out. One (reached) Implementation-Goal was, to support to configure a Commanding to a method within one single code-statement.
I don't know, whether that is possible with the Composition-Pattern - I didn't try it out.

So take this article more as an Introduction and proof of concept rather than as a solution carved in stone, of perfect design to outlive from eternity to eternity. ;)

License

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


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

Comments and Discussions

 
-- There are no messages in this forum --