Silverlight – Binding sur des propriétés attachées dans le ItemTemplate d’un contrôle héritant de ItemsControl

2 minutes read

Dans cet article je vais vous montrer comment on peut parvenir en Silverlight (celà marche aussi avec WP7) à faire celà :

<Controls:CustomItemsControl ItemsSource="{Binding Items}">
    <Controls:CustomItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid Background="{Binding ColorName}"
                  Controls:CustomItemsControl.Index="{Binding Index}">
                <TextBlock Text="{Binding ColorName}" />
            </Grid>
        </DataTemplate>
    </Controls:CustomItemsControl.ItemTemplate>
    <Controls:CustomItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Controls:CustomPanel />
        </ItemsPanelTemplate>
    </Controls:CustomItemsControl.ItemsPanel>
</Controls:CustomItemsControl>

La classe CustomItemsControl hérite de ItemsControl. Notre instance de CustomItemsControl est bindée sur une collection d’objets non-graphiques contenant deux propriétés, Index et ColorName. Comprenez la propriété Index comme étant la position de l’objet dans notre ItemsControl.

Afin de pouvoir afficher ces objets nous créons un DataTemplate où à chaque instance de Grid est affectée une propriété attachée Index provenant de CustomItemsControl qui est elle-même bindée sur la propriétée Index de l’objet non-graphique.

Cette propriété est utilisée par le CustomPanel pour positionner l’objet à l’endroit voulu (un peu à la manière des Grid.Row et Grid.Column ou des Canvas.Left et Canvas.Top).

Tout d’abord nous allons créer la classe CustomItemsControl sous forme de CustomControl.

Voici le code C# :

public class CustomItemsControl : ItemsControl
{
    public static int GetIndex(DependencyObject obj)
    {
        return (int)obj.GetValue(IndexProperty);
    }

    public static void SetIndex(DependencyObject obj, int value)
    {
        obj.SetValue(IndexProperty, value);
    }

    public static readonly DependencyProperty IndexProperty =
        DependencyProperty.RegisterAttached("Index", typeof(int),
        typeof(CustomItemsControl), new PropertyMetadata(0));

    public CustomItemsControl()
    {
        DefaultStyleKey = typeof(CustomItemsControl);
    }
}

Et voici le style par défaut :

<Style TargetType="Controls:CustomItemsControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Controls:CustomItemsControl">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <ItemsPresenter />
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Voyons maintenant le code de notre CustomPanel :

public class CustomPanel : Panel
{
    protected override Size MeasureOverride(Size availableSize)
    {
        var childrenAvailableSize =
            new Size(availableSize.Height / 4, availableSize.Width / 4);

        foreach (var child in Children)
            child.Measure(childrenAvailableSize);

        if (double.IsInfinity(availableSize.Height) ||
            double.IsInfinity(availableSize.Width))
            return new Size(0, 0);

        return availableSize;
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        if (double.IsInfinity(finalSize.Height) ||
            double.IsInfinity(finalSize.Width))
            return new Size(0, 0);

        double width = finalSize.Width > finalSize.Height ?
            finalSize.Height * 0.25 :
            finalSize.Width * 0.25;

        foreach (var child in Children)
        {
            var index = CustomItemsControl.GetIndex(VisualTreeHelper.GetChild(child, 0));

            var rect = new Rect(width * (index % 2), width * (index / 2), width, width);
            child.Arrange(rect);
        }

        return new Size(4 * width, 4 * width);
    }
}

Notre panel se charge donc d’afficher 4 éléments sous forme d’une grille dont les colonnes sont de tailles uniformes. La ligne la plus importante ci-dessus est celle ci-dessous :

var index = CustomItemsControl.GetIndex(VisualTreeHelper.GetChild(child, 0));

En effet, l’objet “child” est une instance de ContentPresenter et pour récupérer notre Grid (et donc la valeur de notre propriété attachée) nous devons aller chercher l’enfant de ce ContentPresenter ce qui se fait en utilisant VisualTreeHelper.GetChild.

Je vous invite à télécharger le code source d’exemple sur mon skydrive.

NB : Il est aussi important de ne pas surdéfinir la méthode GetContainerForItemOverride de CustomItemsControl. Si cette méthode est surdéfinie le Binding n’est pas effectué. Je n’ai pas trouvé d’explication à celà néanmoins si quelqu’un en a une je suis très impatient de l’entendre et je mettrai à jour cet article.

Updated:

Leave a Comment