nRoute - CommandRelay - Get access to a control’s context from a DataTemplate

3 minutes read

One common issue when using the MVVM pattern from a Silverlight 4 application is to have access to the current control’s data context from items controls’ data templates.

Let’s look at the following application :

datagrid

It is composed of a data grid bound to a collection of books. Inside the view model that contains this collection, we can also find a command that displays books.

Here is the source code of this page, it’s view model and the book class :

public class Book
{
    public long ID { get; set; }
    public string Author { get; set; }
    public string Title { get; set; }
}
public class MainPageViewModel : ViewModelBase
{
    private readonly IEnumerable<Book> _books;
    private readonly ICommand _showAuthorCommand;

    public MainPageViewModel()
    {
        var list = new List<Book>();
        for (int i = 0; i < 10; ++i)
            list.Add(new Book
            {
                ID = i,
                Author = "Author " + i,
                Title = "Title " + i
            });

        _books = list;
        _showAuthorCommand = new ActionCommand<Book>(ShowAuthor);
    }

    public IEnumerable<Book> Books
    {
        get { return _books; }
    }

    public ICommand ShowAuthorCommand
    {
        get { return _showAuthorCommand; }
    }

    private static void ShowAuthor(Book book)
    {
        MessageBox.Show(book.Author);
    }
}
<Grid x:Name="LayoutRoot" Background="White">
    <sdk:DataGrid ItemsSource="{Binding Books}">
        <sdk:DataGrid.Columns>
            <sdk:DataGridTemplateColumn>
                <sdk:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Button Content="Show Author"
                                Command="{Binding ShowAuthorCommand}"
                                CommandParameter="{Binding}" />
                    </DataTemplate>
                </sdk:DataGridTemplateColumn.CellTemplate>
            </sdk:DataGridTemplateColumn>
        </sdk:DataGrid.Columns>
    </sdk:DataGrid>
</Grid>

What we want to do here is calling the ShowAuthorCommand command of the page’s view model passing it the current book as a parameter in order to do some sort of computing (here we will just display the book’s author in a MessageBox).

But if we execute the code above, the command is not invoked. The button used to invoke it is inside a data template and therefore, its data context is a book and not the page’s view model and so it is impossible to use the command directly.

The data template does not have access to the page’s view model but it has access to the static resources defined in the page.

What we will do with nRoute is to save the command inside a static resource at the page level and invoke it. This concept is not specific to nRoute and can be reimplemented in another way. I just think it is beautifully implemented in Route.

First we will create an instance of CommandRelay inside the page’s static resources (it is not the same and the RelayCommand from MVVMLightToolkit). You can translate the hereafter in english by “I declare a variable named ShowAuthorCommandRelay” :

<UserControl.Resources>
    <n:CommandRelay x:Key="ShowAuthorCommandRelay" />
</UserControl.Resources>

Then inside the page’s behavior, in addition to the BridgeViewModelBehavior that maps the vue and its view model, we add a BridgeCommandBehavior that will initialise the instance of the CommandRelay previously declared :

<i:Interaction.Behaviors>
    <n:BridgeViewModelBehavior />
    <n:BridgeCommandBehavior CommandRelay="{StaticResource ShowAuthorCommandRelay}"
                             CommandSource="{Binding ShowAuthorCommand}" />
</i:Interaction.Behaviors>

The CommandRelay property is set to the CommandRelay to initialize and the CommandSource property is set to the command (here it’s the ShowAuthorCommand command from our view model).

You can translate the lines of code above in english by : “Initialize ShowAuthorCommandRelay with the ShowAuthorCommand command”.

Now we just need to invoke the command in the button using the following code :

<DataTemplate>
    <Button Content="Show Author"
            Command="{Binding Command, Source={StaticResource ShowAuthorCommandRelay}}"
            CommandParameter="{Binding}" />
</DataTemplate>

We see that the syntax used to invoke the command has changed since our first try. Here we use the Command property of the ShowAuthorCommandRelay static resource. Because as previously said, we can access static resources from a DataTemplate.

If we execute the code corrected we can see that the MessageBox shows up correctly.

NB : In Silverlight 5, in this kind of scenario we would probably have used one of the new binding options known as RelativeSource AncestorType but the CommandRelay’s technique will still be valid.

As usual you can download the source code of the application on my skydrive.

Updated:

Leave a Comment