Organising custom Commands in a WPF TreeView using the Model-View-ViewModel design pattern

In my current project’s interface, the main elements of the GUI are a TabControl holding any number of workspaces, and a side panel with commands to launch the desired workspaces. It is closely based on a MVVM demo I saw, where the commands are displayed as a list of hyperlinks. However, since I have many possible workspaces, and they are logically grouped together, I wanted the side panel to list the available workspaces in a collapsible TreeView, and have the user open the desired workspace by clicked the corresponding command link.

In this post I will show you how I put together a tree of commands that can be modified at runtime, using the Model-View-ViewModel (MVVM) pattern. The result looks something like this:

CropperCapture[2]

The Model-View-ViewModel Design Pattern

MVVM is an interface design pattern is ideally suited for use with WPF, due to the way WPF GUI code binds to data and refreshes the display. I am not going to explain or demonstrate the MVVM pattern here – if you are unfamiliar with it, check out Josh Smith’s excellent introduction over at MSDN. My code here is based closely on the Demo Application code available in that article.

Putting Commands in ViewModels

RelayCommand

In his article’s demo code, Josh uses a RelayCommand class, which implements ICommand, to wrap actions that can be invoked by the View. The ViewModel then hosts a RelayCommand, and in the View, a button or hyperlink binds to the RelayCommand. Some action is performed by the ViewModel when the command is invoked. You can find the source code for the RelayCommand class in Figure 3 of Josh’s article. Here I will just show how it is used, as this will make the rest of this post make more sense. Below is the ViewModel’s initialisation of a RelayCommand, and beneath that the binding to that command by the View:

// In the ViewModel:

private RelayCommand _demoCommand;

// Command Property:
public RelayCommand DemoCommand
{
    get
    {
        if (_demoCommand == null)
        {
            // Initialize the command using a lambda expression:
            _demoCommand = new RelayCommand(param => DemoCommandTarget());
        }

        return _demoCommand;
    }
}

private void DemoCommandTarget()
{
    // Do stuff that you want the command to do...
}
        <!-- In the View: -->

        <Button Command="{Binding Path=DemoCommand}">Click Me!</Button>

CommandViewModel

Josh then goes a step further by declaring a ViewModel for commands, allowing him to define a default DataTemplate for displaying command links to the user. The CommandViewModel wraps the RelayCommand, which is injected upon construction. It further exposes a DisplayName property (which is implemented in the ViewModelBase class). Binding in the View is then to the CommandViewModel, rather than directly to the RelayCommand. Below is a slightly simplified version of the CommandViewModel from Josh’s article, which we are going to be modifying soon:

// Represents an actionable item displayed by a View.
public class CommandViewModel : ViewModelBase
{
    public CommandViewModel(string displayName, ICommand command)
    {
        base.DisplayName = displayName;
        this.Command = command;
    }

    public ICommand Command { get; private set; }
}

Grouping Commands into a Tree

In order to group CommandViewModels into a tree structure, we declare two more classes. We declare CommandTreeItem as a new abstract (and empty) baseclass for CommandViewModel, as well as a new CommandTreeGroup class. We let the CommandTreeGroup class expose an ObservableCollection of CommandTreeItem baseclass instances, thus allowing it to hold both CommandViewModel instances and more CommandTreeGroup instances. The code is below, but if your head is spinning from all these class names (mine is), check the image below the code for a class diagram that hopefully makes everything clear:

public class CommandTreeGroup : CommandTreeItem
{
    //region Fields

    private ObservableCollection<CommandTreeItem> _commandTreeItems;

    //endregion

    //region Constructor

    public CommandTreeGroup(string displayName)
    {
        this.DisplayName = displayName;
    }

    //endregion

    //region Properties

    public ObservableCollection<CommandTreeItem> CommandTreeItems
    {
        get
        {
            if (_commandTreeItems == null)
                _commandTreeItems = new ObservableCollection<CommandTreeItem>();

            return _commandTreeItems;
        }
    }

    //endregion
}

The updated class structure now looks like this:

Class_Diagram

Initializing the Command Tree

We can now expose an ObservableCollection of CommandTreeItems in the MainWindowViewModel (lines 19-32), and fill it up with commands, groups of commands and subgroups of commands (lines 39-63). The code below creates the commands shown in the example tree at the very top of this post:

/// <summary>
/// ViewModel for application's main window.
/// </summary>
public class MainWindowViewModel : ViewModelBase
{

    //region Constructor

    public MainWindowViewModel()
    {
        base.DisplayName = "MVVM Command Tree Demo";
        CreateInitialCommands();           
    }

    //endregion

    //region Properties

    private ObservableCollection<CommandTreeItem> _commands;

    public ObservableCollection<CommandTreeItem> Commands
    {
        get
        {
            if (_commands == null)
                _commands = new ObservableCollection<CommandTreeItem>();

            Console.WriteLine("Commands queried, returning {0} items", _commands.Count);

            return _commands;
        }
    }

    //endregion

    //region Command Creation

    private void CreateInitialCommands()
    {
        // Create a groupless command:
        CommandViewModel groupLessCommand = new CommandViewModel("Groupless Command", new RelayCommand(param => Console.WriteLine("Clicked Groupless Command")));
        this.Commands.Add(groupLessCommand);

        // Create a Group:
        CommandTreeGroup group1 = new CommandTreeGroup("Command Group");
        this.Commands.Add(group1);

        // Add some Commands to the Group:
        CommandViewModel commandA = new CommandViewModel("Command A", new RelayCommand(param => Console.WriteLine("Clicked Command A.")));
        group1.CommandTreeItems.Add(commandA);

        CommandViewModel commandB = new CommandViewModel("Command B", new RelayCommand(param => Console.WriteLine("Clicked Command B.")));
        group1.CommandTreeItems.Add(commandB);

        // Create an inner group inside the first group:
        CommandTreeGroup innerGroup = new CommandTreeGroup("Inner Group");
        group1.CommandTreeItems.Add(innerGroup);

        // Add a command to the inner group:
        CommandViewModel innerCommandB = new CommandViewModel("Add More Commands", new RelayCommand(param => this.AddCommand()));
        innerGroup.CommandTreeItems.Add(innerCommandB);            
    }

    private void AddCommand()
    {
        // Add a second groupless command:
        CommandViewModel newGrouplessCommand = new CommandViewModel("New Groupless Command", new RelayCommand(param => Console.WriteLine("Clicked New Groupless Command")));
        this.Commands.Add(newGrouplessCommand);
    }

    //endregion

}

In the code above, most commands are just lambdas that print out the command name when invoked, with the exception being innerCommandB, which calls AddCommand() to add another command to the tree at run time. Because the root list and the sub lists are all implemented as ObservableCollections, the View will automatically update to show the additional command.

Displaying the Command Tree

The rest of the niftiness happens over in the View, which really isn’t all that complicated. Here is the complete view code, with comments below:

<Window x:Class="MVVM_CommandTree_Demo.View.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:MVVM_CommandTree_Demo.ViewModel"
        Title="{Binding DisplayName}" Height="600" Width="800">
    <DockPanel>
        <TreeView ItemsSource="{Binding Path=Commands}">
            <TreeView.Resources>

                <DataTemplate DataType="{x:Type vm:CommandViewModel}">
                    <TreeViewItem>
                        <TreeViewItem.Header>
                            <Hyperlink Command="{Binding Path=Command}">
                                <TextBlock Text="{Binding Path=DisplayName}" />
                            </Hyperlink>
                        </TreeViewItem.Header>
                    </TreeViewItem>
                </DataTemplate>

                <DataTemplate DataType="{x:Type vm:CommandTreeGroup}">
                    <TreeViewItem Header="{Binding Path=DisplayName}"
                                  ItemsSource="{Binding CommandTreeItems}" />
                </DataTemplate>

            </TreeView.Resources>
        </TreeView>                       
    </DockPanel>
</Window>

We start off in line 7 with a TreeView, for which we bind ItemsSource to the ObservableCollection of CommandTreeItem instances in the MainWindowViewModel, to form the root of our command tree.

We then declare two DataTemplates to tell the TreeView how to display a CommandViewModel and a CommandTreeGroup respectively.

DataTemplate for Commands

The first DataTemplate, for CommandViewModel, is intended to display the tree’s leaf nodes – the actual commands. It renders a TreeViewItem with a Hyperlink as header. The Hyperlink Command binds to CommandViewModel.Command, while the Hyperlink Text binds to CommandViewModel.DisplayName:

<DataTemplate DataType="{x:Type vm:CommandViewModel}">
    <TreeViewItem>
        <TreeViewItem.Header>
            <Hyperlink Command="{Binding Path=Command}">
                <TextBlock Text="{Binding Path=DisplayName}" />
            </Hyperlink>
        </TreeViewItem.Header>
    </TreeViewItem>
</DataTemplate>

DataTemplate for Command Groups

The second DataTemplate, for CommandTreeGroup, is intended to display sub groups of commands. It renders a TreeViewItem with a plain text Header, bound to CommandTreeGroup.DisplayName. In addition, the TreeViewItem’s ItemsSource is bound to the ObservableCollection of CommandTreeItems in the group, thus creating the next set of branches and leaves:

<DataTemplate DataType="{x:Type vm:CommandTreeGroup}">
    <TreeViewItem Header="{Binding Path=DisplayName}"
                  ItemsSource="{Binding CommandTreeItems}" />
</DataTemplate>

That’s it!

Super simple stuff! I hope that was useful. You can download the complete demo code here. Please leave a comment below, and let me know if something didn’t make sense or if you have ideas of awesomeness to add!

Update: I get quite a number of hits on this page, as well as a number of downloads for the demo code, and I’m always left wondering “Did you find what you were looking for? Did this help you?” If it did (or did not), please leave a comment, I would love to know!

Advertisements
This entry was posted in Software Development and tagged , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s