Warning : This page has been marked as an archive because the author consider that its content is no longer relevant.

Récemment j’ai été confronté à un problème assez ennuyeux. J’ai créé un custom ItemsControl (cf. article précédent et j’avais besoin de récupérer l’instance de l’objet graphique créé grâce à l’ItemTemplate dans la méthode OnItemsChanged.

Mon CustomItemsControl est bindé sur une collection d’objets non-graphiques d’un ViewModel et j’ai besoin d’effectuer une opération sur l’objet graphique. En prenant appui sur l’article précédent, cette opération sera de récupérer la valeur de la propriété attachée bindée dans le DataTemplate.

Tout d’abord nous surdéfinissons la méthode OnItemsChanged dans le CustomItemsControl et écrivons le squelette de base :

protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
    if (e.Action == NotifyCollectionChangedAction.Reset)
    {
    }
    else if (e.Action == NotifyCollectionChangedAction.Add)
    {
    }
}

Notez bien que nous effectuerons aussi notre action dans le cas où la collection a été réinitialisée (NotifyCollectionChangedAction.Reset) ce qui arrive lors de l’initialisation du binding sur ItemsSource par exemple.

Afin de pouvoir récupérer notre objet graphique nous devons attendre que celui-ci ait été généré. Une méthode pour celà consiste à attendre la prochaine passe de Layout. Pour celà nous allons donc nous abonner à l’évènement LayoutUpdated et capturer l’état de nos variables grâce à une méthode anonyme.

Voici le code pour faire celà :

protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
    EventHandler onUpdated = null;

    if (e.Action == NotifyCollectionChangedAction.Reset)
    {
        onUpdated = (sender, args) =>
        {
            HandleItems(Items);
            LayoutUpdated -= onUpdated;
        };
    }
    else if (e.Action == NotifyCollectionChangedAction.Add)
    {
        onUpdated = (sender, args) =>
        {
            HandleItems(e.NewItems);
            LayoutUpdated -= onUpdated;
        };
    }

    if (onUpdated != null)
        LayoutUpdated += onUpdated;
}

Vous noterez que dans le cas où la collection a été réinitalisée nous utilisons directement Items alors que dans le cas où il s’agit d’un ajout d’objets nous utilisons e.NewItems. Notez aussi que le désabonnement à l’évènement LayoutUpdated intervient dans la méthode anonyme.

C’est le principe de la capture de variables ou “closure” qui est ici utilisé. Je vous invite à vous documenter sur ce point (lien en anglais) si vous ne connaissez pas le principe.

La méthode HandleItems peut dès lors accéder aux objets graphiques en utilisant l’ItemContainerGenerator de notre ItemsControl :

private void HandleItems(IEnumerable items)
{
    foreach (var t in items)
    {
        var container =
            ItemContainerGenerator.ContainerFromItem(t);
        var child = VisualTreeHelper.GetChild(container, 0);
        var index = GetIndex(child);

        DoSomething(index);
    }
}

private static void DoSomething(int index)
{
    // Do something with index.
    MessageBox.Show(index.ToString());
}

De même que dans l’article précédent, nous n’avons plus qu’à utiliser VisualTreeHelper.ContainerFromItem de la même manière que dans le panel pour accéder à la propriété attachée Index.

Vous pouvez télécharger le code complet de cette application sur mon skydrive.

Comments