Le Data Binding en Silverlight 2

Benjamin Roux

Cet article vous présentera le Data Binding en Silverlight 2.

N'hésitez pas à commenter cet article ! Commentez ♪

- Titre : Le Data Binding en Silverlight 2

- Auteur : Benjamin Roux

- Parution : 16 septembre 2008

- Licence : Copyright ® 2008 Benjamin Roux. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Data Binding ?

- II. Le Data Binding en Silverlight 2

- II-A. Comment faire

- II-B. Les Converters

- II-C. Les différents modes

- II-D. Afficher une liste d'objets

- II-E. Conclusion

- III. Remerciements

I - Data Binding ?

Le Data Binding est simplement le moyen utilisé pour faire le lien entre votre interface et votre source de données.

Ce concept marque encore plus la séparation entre les différentes couches de votre application (ici entre la couche présentation et les autres).

On appelle l'interface la cible et le fournisseur de données, la source.

II - Le Data Binding en Silverlight 2

II-A - Comment faire

Pour illustrer cet article, nous allons créer une petite application de gestion de tutoriels.

Nous allons d'abord commencer par créer notre classe Tutorial :

public class Tutorial
{
public string Title { get; set; }
public string Author { get; set; }
public string Url { get; set; }
public int Rate { get; set; }
}

Un tutoriel a un titre, un auteur, une url et une note. Rien de bien compliqué.

Maintenant que nous avons notre object métier, nous allons créer notre interface (avec simplement des TextBlock) :

<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Titre :" />
<TextBlock Grid.Row="1" Text="Auteur :" />
<TextBlock Grid.Row="2" Text="URL :" />
<TextBlock Grid.Row="3" Text="Note :" />
<TextBlock x:Name="TxtTitle" Grid.Row="0" Grid.Column="1" Text="{Binding Title}" />
<TextBlock x:Name="TxtAuthor" Grid.Row="1" Grid.Column="1" Text="{Binding Author}" />
<TextBlock x:Name="TxtUrl" Grid.Row="2" Grid.Column="1" Text="{Binding Url}" />
<TextBlock x:Name="TxtRate" Grid.Row="3" Grid.Column="1" Text="{Binding Rate}" />
</Grid>

Et voilà. Vous apercevez par la même occasion la syntaxe à utiliser pour Binder une propriété de notre source, à une propriété de notre cible (ici des TextBlock) :

Property="{Binding PropertyName}"

Maintenant il faut dire à nos TextBlock qui est notre source. Nous allons d'abord créer un tutoriel.

Dans la méthode InitializeComponent de notre Page.xaml.

Tutorial templates = new Tutorial()
{
Author = "Benjamin Roux",
Title = "Templates avances en Silverlight",
Url = "http://broux.developpez.com/articles/csharp/templates-silverlight/",
Rate = 10
};

Maintenant nous allons spécifier pour chacun de nos TextBlock, notre objet :

TxtAuthor.DataContext = templates;
TxtRate.DataContext = templates;
TxtTitle.DataContext = templates;
TxtUrl.DataContext = templates;

Rébarbatif non ?

On peut factoriser tout ça, en spécifiant le DataContext à l'élément qui englobe tous nos TextBlock, c'est-à-dire notre Grid qui se nomme LayoutRoot :

LayoutRoot.DataContext = templates;

Voici le résultat :

À noter qu'il existe une autre manière de spécifier le Binding, cette fois-ci par du code C#. Au lieu de faire par exemple :

Text="{Binding Title}"

Il est possible de faire dans le code behind :

TxtTitle.SetBinding(TextBlock.TextProperty, new System.Windows.Data.Binding("Title"));

Voilà pour la version basique. Vous avez sûrement remarqué que pour afficher l'URL nous avons aussi utilisé un TextBlock et vous vous demandez peut-être pourquoi nous n'avons pas utilisé un HyperLinkButton. Nous allons voir ça dans la prochaine partie.

II-B - Les Converters

Alors pourquoi est-ce que nous n'avons pas utilisé un HyperLinkButton pour afficher mon URL ? Tout simplement, car dans notre classe Tutorial, l'URL est stockée dans un string. Or la propriété NavigateUri veut un objet de type Uri : du coup cela n'aurait pas fonctionné.

Une solution aurait été de rajouter une propriété de type Uri à ma classe Tutorial et de binder cette propriété sur le NavigateUri de mon HyperLinkButton. Mais cela nous fait rajouter une propriété et, de plus, ce n'est pas super propre.

La solution : utiliser un Converter !

Les Converters sont simplement des classes qui permettent de convertir un objet d'un type en un autre.

C'est extrêmement simple à faire : il suffit d'implémenter l'interface IValueConverter.

Exemple avec un Converter, pour convertir un string en Uri :

public class String2UriConverter : IValueConverter
{
#region IValueConverter Members

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is string) return new Uri((string)value);
else return value;
}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}

#endregion
}

Nous allons maintenant remplacer notre TextBlock par un HyperLinkButton :

<HyperlinkButton Grid.Row="2" Grid.Column="1" Content="{Binding Url}"
NavigateUri="{Binding Url, Converter={StaticResource String2UriConverter}}" />

Voici la syntaxe pour utiliser un Converter en XAML :

Property="{Binding PropertyName, Converter={StaticResource String2UriConverter}}"

En C# :

System.Windows.Data.Binding binding = new System.Windows.Data.Binding("PropertyName");
binding.Converter = new Converters.String2UriConverter();

On n'oublie pas de rajouter notre Converter en ressource :

<UserControl x:Class="DataBindingSilverlight.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="clr-namespace:DataBindingSilverlight.Converters"
Width="600" Height="300">
<UserControl.Resources>
<conv:String2UriConverter x:Key="String2UriConverter" />
</UserControl.Resources>
[...]
</UserControl>

Et voilà le tour est joué. Au moment du Binding, notre string est transformé en Uri, objet attendu pour la propriété NavigateUri.

Nous allons maintenant voir les différents modes de Data Binding.

II-C - Les différents modes

Il faut également savoir qu'il existe plusieurs modes de Binding (enum C#). Ces derniers sont répertoriés sur la page de la MSDN.

Pour rappel, on appelle l'interface la cible et le fournisseur de données la source.

Type

Description

OneTime

Met à jour la propriété de la cible.

OneWay

Met à jour la propriété de la cible. Une modification sur la source est répercutée sur la cible (valeur par défaut).

TwoWay

Met à jour la propriété de la cible. Une modification sur la source est répercutée sur la cible et inversement.

Pour choisir le mode, il suffit de le spécifier lors du Binding :

Property="{Binding PropertyName, Mode=OneWay/TwoWay/OneTime}"

En C# :

System.Windows.Data.Binding binding = new System.Windows.Data.Binding("PropertyName");
binding.Mode = BindingMode.OneTime;

Pour les modes OneWay et TwoWay, les changements ne seront pas effectués tout seuls, il faut que la classe utilisée pour le Binding implémente l'interface INotifyPropertyChanged.

Prenons l'exemple avec notre gestionnaire de tutoriels.

Nous allons rajouter un bouton qui modifie la note de notre tutoriel, pour voir le résultat :

<Button Grid.Row="4" Grid.ColumnSpan="2" Content="Changer note" Height="50" Width="100" Click="Button_Click" />

private void Button_Click(object sender, RoutedEventArgs e)
{
templates.Rate = 15;
}

Un clic sur le bouton modifie effectivement la note de notre tutoriel, mais aucun changement visuel n'apparaît. Pour le faire, on pourrait éventuellement définir notre DataContext à null avant de le redéfinir sur notre objet, mais cela reste un bidouillage.

La solution est donc, comme expliqué plus haut, d'implémenter l'interface INotifyPropertyChanged, dans notre classe Tutorial :

public class Tutorial : INotifyPropertyChanged
{
private string mTitle;
private string mAuthor;
private string mUrl;
private int mRate;

public string Title
{
get { return mTitle; }
set
{
mTitle = value;
NotifyPropertyChanged("Title");
}
}

public string Author
{
get { return mAuthor; }
set
{
mAuthor = value;
NotifyPropertyChanged("Author");
}
}

public string Url
{
get { return mUrl; }
set
{
mUrl = value;
NotifyPropertyChanged("Url");
}
}

public int Rate
{
get { return mRate; }
set
{
mRate = value;
NotifyPropertyChanged("Rate");
}
}

#region INotifyPropertyChanged Members

public event PropertyChangedEventHandler PropertyChanged;

public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

#endregion
}

Chaque fois qu'une propriété est modifiée, nous appelons la méthode NotifyPropertyChanged, qui se charge de lancer l'évènement PropertyChanged.

Désormais, lorsque notre source de données sera modifiée, toutes les propriétés de nos objets visuels bindées sur des propriétés de notre objet seront mises à jour.

Nous venons d'illustrer le mode OneWay : la source de donnée a été modifiée.

Pour illustrer le mode TwoWay, nous allons remplacer le TextBlock de la note par une TextBox. On n'oublie pas de modifier le mode de Binding également :

<TextBox x:Name="TxtRate" Grid.Row="3" Grid.Column="1" Text="{Binding Rate, Mode=TwoWay}" />

Désormais, lorsque nous modifierons la valeur de la TextBox et que cette dernière perdra le focus, la source de donnée sera mise à jour !

Voici pour les différents modes de Binding, c'est à vous de choisir le mode qui vous convient selon ce que vous voulez faire :

- OneTime pour des données qui ne changeront pas ;

- OneWay pour des données qui sont susceptibles d'être modifiées ailleurs ;

- TwoWay pour des données que l'utilisateur peut modifier et qui peuvent aussi être modifiées ailleurs…

II-D - Afficher une liste d'objets

Nous avons vu comment afficher un seul objet, nous allons maintenant voir comment afficher une liste d'objets.

Tout d'abord, nous allons créer une liste de tutoriels :

List<Tutorial> tutoriels = new List<Tutorial>();

Tutorial templates = new Tutorial()
{
Author = "Benjamin Roux",
Title = "Templates avances en Silverlight",
Url = "http://broux.developpez.com/articles/csharp/templates-silverlight/",
Rate = 10
};

Tutorial animations = new Tutorial()
{
Author = "Benjamin Roux",
Title = "Les animations en Silverlight",
Url = "http://broux.developpez.com/articles/csharp/animations-silverlight/",
Rate = 10
};
[...]
tutoriels.Add(templates);
tutoriels.Add(animations);

Nous voici avec une liste de Tutoriel.

Pour les afficher, nous avons plusieurs possibilités. Tout d'abord une ListBox, ou encore un ItemsControl (équivalent du Repeater en ASP.NET). C'est ce dernier que nous allons utiliser.

<ItemsControl x:Name="Tutoriels">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Titre :" />
<TextBlock Grid.Row="1" Text="Auteur :" />
<TextBlock Grid.Row="2" Text="URL :" />
<TextBlock Grid.Row="3" Text="Note :" />
<TextBlock x:Name="TxtTitle" Grid.Row="0" Grid.Column="1" Text="{Binding Title}" />
<TextBlock x:Name="TxtAuthor" Grid.Row="1" Grid.Column="1" Text="{Binding Author}" />
<HyperlinkButton Grid.Row="2" Grid.Column="1" Content="{Binding Url}"
NavigateUri="{Binding Url, Converter={StaticResource String2UriConverter}}" />
<TextBox x:Name="TxtRate" Grid.Row="3" Grid.Column="1" Text="{Binding Rate}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

On reprend notre UI d'avant et on l'encadre par un contrôle de type ItemsControl.

On n'oublie pas de spécifier notre source de données à ce contrôle :

Tutoriels.ItemsSource = tutoriels;

Cette fois on utilise la propriété ItemsSource et non DataContext.

Voyons le résultat :

Nous avons tous nos éléments les uns à la suite des autres. Vraiment pratique !

On peut également modifier les éléments comme précédemment : TextBox pour la note, ou modification de la note via un bouton. Tous les changements sont effectués visuellement.

En revanche, il y a un problème : l'ajout ou la suppression d'éléments dans notre liste.

Si l'on rajoute un bouton et que lors du clic sur le bouton on tente de rajouter un élément à notre List, comme ceci.

Tutorial databinding = new Tutorial()
{
Author = "Benjamin Roux",
Title = "Le Data Binding en Silverlight",
Url = "http://broux.developpez.com/articles/csharp/databinding-silverlight/",
Rate = 10
};

tutoriels.Add(databinding);

L'élément n'apparaît pas ! On pourrait comme précédemment mettre la propriété ItemsSource à null pour la remettre sur notre List par la suite, mais c'est encore et toujours du bidouillage.

La solution est d'utiliser une collection qui implémente INotifyCollectionChanged.

Ca tombe bien : Silverlight 2 met à notre disposition une classe qui l'implémente : ObservableCollection<T>.

Cette classe fait tout comme la classe List<T>, sauf qu'elle implémente INotifyCollectionChanged.

On remplace dans notre code :

ObservableCollection<Tutorial> tutoriels = new ObservableCollection<Tutorial>();

Et désormais, lorsqu'un élément est ajouté ou supprimé de notre collection, un évènement est lancé. Il dit à notre ItemsControl que la source a changé et qu'il doit donc faire les modifications nécessaires !

II-E - Conclusion

Voici la fin de notre article sur le Data Binding en Silverlight 2 qui, j'espère, vous sera très utile. Il est à noter que des nouveautés y seront apportées lors de la release de Silverlight 2.

III - Remerciements

Je tiens à remercier toute l'équipe .NET pour leurs conseils et relectures, ainsi que MaliciaR pour sa correction orthographique et surtout grammaticale.