lundi 25 mars 2013

Utiliser un ViewModel dans un DataTemplate de liste

Problématique

WP8 et WP7 ne supportent pas toutes les propriétés liées aux options de binding RelativeSource. Il est alors compliqué de créer un binding à l'intérieur d'un datatemplate vers un DataContext de plus haut niveau.
Je m'explique....
Exemple simple avec une liste. La liste est dans une page XAML dont le DataContext est un ViewModel nommé MainViewModel. A l'intérieur de cette liste, des items. La source des items (ItemsSource) est bindée à une ObservableCollection contenant des ItemsViewModel. Cette ObservableCollection est un attribut de la classe MainViewModel (le DataContext de la page). Dans le XAML, il est possible de définir comment est affiché un item de la liste par l'intermédiaire d'un ItemTemplate. Vue de l'ItemTemplate, le Datacontext n'est plus le DataContext de la page mais un des items. En effet, la source des items est bindée au DataContext de la page mais à l'intérieur d'un ItemTemplate, c'est l'item le DataContext!
Mais alors comment faire pour utiliser un attribut du MainViewModel pour tous les items de la liste? En WPF, il est possible d'utiliser des RelativeSource. Malheureusement la version de Silverlight utilisée pour WP7 et WP8 ne supporte pas les propriétés qui nous intéresse dans notre cas.

Voyons comment s'y prendre.....

La classe MainViewModel

Dans notre exemple, la propriété commune à tous les items de la liste sera la couleur d'un rectangle affiché dans chaque item. Tous les items de la liste afficheront donc le même rectangle avec la même couleur.
C'est l'attribut MyColor qui sera utilisé!

Nous retrouvons dans le MainViewModel l'observable collection qui contient les données de type ItemViewModel.


public class MainViewModel : INotifyPropertyChanged
    {
        public MainViewModel()
        {
            this.Items = new ObservableCollection<ItemViewModel>();
        }

        public ObservableCollection<ItemViewModel> Items { get; private set; }

        public Brush MyColor
        {
            get
            {
                return new SolidColorBrush(Color.FromArgb(0xFF, 164, 196, 0));
            }
        }...


La classe ItemViewModel

La classe ItemViewModel contient les données à afficher pour chaque item de la liste. L'attribut que nous allons affiché pour chaque item est LineOne.

   public class ItemViewModel : INotifyPropertyChanged
    {
         private string _lineOne;
        public string LineOne
        {
            get
            {
                return _lineOne;
            }
            set
            {
                if (value != _lineOne)
                {
                    _lineOne = value;
                    NotifyPropertyChanged("LineOne");
                }
            }
        }...


Le XAML


Info: L'application utilisée a été nommée SampleApp et les ViewModels sont dans un dossier "ViewModels". L'information est utile pour la définition du namespace XAML. 

La partie intéressante se situe dans le XAML. L'astuce consiste à créer une ressource à la PhoneApplicationPage qui est du type MainViewModel de la manière suivante:
   <phone:PhoneApplicationPage.Resources>
        <local:MainViewModel x:Key="ViewModel"></local:MainViewModel>
   </phone:PhoneApplicationPage.Resources>


 Ceci fonctionne en ayant préalablement défini le namespace suivant dans le header de la page XAML:
xmlns:local="clr-namespace:SampleApp.ViewModels"

La liste suivante est définie dans le XAML:
            <phone:LongListSelector x:Name="MainLongListSelector" Margin="0,0,-12,0" ItemsSource="{Binding Items}" SelectionChanged="MainLongListSelector_SelectionChanged">
                <phone:LongListSelector.ItemTemplate>
                    <DataTemplate>
                      <StackPanel Margin="0,0,0,17">
                        <Rectangle Height="50" Fill="{Binding MyColor, Mode=OneWay, Source={StaticResource ViewModel}}" />   
                          <TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
                      </StackPanel>
                    </DataTemplate>
                </phone:LongListSelector.ItemTemplate>
            </phone:LongListSelector>



On retrouve un Binding traditionnel vers l'attribut LineOne de l'ItemViewModel correspondant à l'item de liste "en cours d'affichage". Mais ce qui nous intéresse se situe au niveau du rectangle. A l'aide de cette astuce, la propriété Fill est Bindée à la propriété Mycolor du MainViewModel:
Fill="{Binding MyColor, Mode=OneWay, Source={StaticResource ViewModel}}"


Aucun commentaire:

Enregistrer un commentaire