nRoute – CommandRelay – Accéder au contexte parent depuis un DataTemplate

3 minutes read

Un problème récurrent lorsqu’on fait du MVVM en Silverlight 4 est d’avoir accès à certains éléments du DataContext courant dans des DataTemplate de nos ItemsControl.

Prenons l’exemple de l’application ci-dessous :

clip_image0014

Celle-ci est composée d’une DataGrid bindée sur une collection de livres. Dans le view model qui contient cette collection de livre, on trouve aussi une commande qui sert à afficher les livres.

Voici donc le code de cette page, de son view model et de la classe livre :

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>

Ce que nous souhaitons faire ici c’est appeler la commande ShowAuthorCommand du view model de la page courante en lui passant en paramètre le livre courant afin qu’il en fasse un traitement (ici affichage de l’auteur du livre dans une MessageBox mais cela pourrait être autre chose). Cependant si on exécute le code ci-dessus la commande n’est pas invoquée. Le Button servant à invoquer la commande se trouve ici dans un DataTemplate son DataContext est donc ici un livre et non le view model de la page. On ne peux donc pas utiliser la commande directement.

Si le DataTemplate n’a pas accès au DataContexte de la page il a néanmoins accès aux StaticResources définies dans la page.

Ce que l’on va faire maintenant avec nRoute va être d’enregistrer la commande à invoquer dans une ressource statique au niveau de la page et ensuite de l’invoquer. Le même concept n’est pas spécifique à nRoute cependant mais je trouve qu’il l’implémente intelligemment.

Tout d’abord nous allons créer dans les ressources statiques de la page une instance de CommandRelay (ne pas confondre avec RelayCommand du MVVMLightToolkit). Vous pouvez traduire la ligne ci-dessous en français en disant : « Je déclare une variable du nom de ShowAuthorCommandRelay » :

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

Ensuite dans les Behavior de la page, en plus du BridgeViewModelBehavior qui sert à mapper la vue et son view model on va rajouter un BridgeCommandBehavior qui va initialiser l’instance de CommandRelay précédemment déclarée :

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

La propriété CommandRelay est mise à sur le CommandRelay à initialiser et la propriété CommandSource sur la valeur de la commande (ici c’est la commande ShowAuthorCommand de notre view model).

Vous pouvez traduire les lignes de code ci-dessus en français par : « Initialise-moi ShowAuthorCommandRelay avec la commande ShowAuthorCommand ».

Maintenant il ne reste plus qu’à invoquer la commande dans notre Button en utilisant le code suivant :

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

On voit que la syntaxe pour l’invocation de la commande a changé. Ici on pointe sur la propriété Command de la ressource statique ShowAuthorCommandRelay. Et comme dis précédemment, on peut sans problème accéder aux ressources statiques de la page dans un DataTemplate.

Si on exécute le code ainsi corrigé on peut voir que la MessageBox s’affiche correctement.

NB : En Silverlight 5 dans un tel scénario on aurait probablement utilisé une nouvelle option de Binding du nom de RelativeSource AncestorType mais la technique des CommandRelay sera de toute façon toujours valable.

Vous pouvez comme d’habitude télécharger le code source d’application sur mon skydrive.

Updated:

Leave a Comment