DataBinding pour les applications Windows
Bonjour à tous, je me présente, je m’appelle Damien, actuellement développeur Windows au sein de Webwag mobile. Aujourd’hui je vais vous présenter le DataBinding qui est le coeur du design pattern MVVM (Model – View – ViewModel) que nous utilisons dans toutes nos applications Windows et qui permet de séparer la vue de la logique et de l’accès aux données.
Qu’est ce que c’est :
Le DataBinding permet de lier une donnée ou un événement à un contrôle.
La liaison se fait via le DataContext.
namespace SampleBinding { public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); var Context = new MainPageViewModel(); this.DataContext = Context; } } }Pour simplifier l’utilisation du Binding, je me sers de la bibliothèque mvvmLight qui permet en autre de mettre à jour la vue automatiquement lorsque une variable change de valeur ainsi que l’ajout de la classe RelayCommand qui permet de lier un événement simplement.
Binding d’un champ text :
Voyons comment Lier un champ text de la vue MainPage à une variable de la classe MainPageViewModel.
Pour se faire nous avons besoin de créer une variable de type string (Title) dans MainPageViewModel et de lier cette dernière à la propriété Text du contrôle TextBlock via le mot clef Binding suivi du nom de variable (Title)
<Grid Background="White"> <Grid.RowDefinitions> <RowDefinition Height="150"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock Text="{Binding Title}" Foreground="Black" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/> </Grid>namespace SampleBinding { public class MainPageViewModel : ViewModelBase { #region Properties private string title; public string Title { get { return title; } set { Set(ref title, value); } } #endregion public MainPageViewModel() { Title = "Titre de la page"; } } }Dans cet example ViewModelBase est une classe de mvvmLigth et Set est une méthode de ViewModelBase qui permet de mettre la vue à jour en temps réel.
Nous venons de voir qu’avec le Binding, il est possible de modifier la vue très facilement via les propriétés d’une classe. mais cela va encore plus loin car la liaison peut être bidirectionnel, pour cela il faut rajouter Mode=TwoWay au binding classique.
<TextBox PlaceholderText="{Binding PlaceHolder}" Text="{Binding FieldTextBox, Mode=TwoWay}" Grid.Row="3" Foreground="Black" FontSize="20" HorizontalAlignment="Left" VerticalAlignment="Center"/>namespace SampleBinding { public class MainPageViewModel : ViewModelBase { #region Properties private string title; private string placeHolder; private string fieldTextBox; public string Title { get { return title; } set { Set(ref title, value); } } public string PlaceHolder { get { return placeHolder; } set { Set(ref placeHolder, value); } } public string FieldTextBox { get { return fieldTextBox; } set { Set(ref fieldTextBox, value); } } #endregion .... } }Dans cet example la propriété FieldTextBox prendra automatiquement la valeur du contrôle TextBox lorsque l’utilisateur changera sa valeur.
Binding d’un événement :
La liaison d’un événement n’est pas si différente de ce que l’on a pu voir avec les champs Text, en effet la ou nous avions besoin d’une propriété de type string dans la classe MainPageViewModel, nous aurons besoin d’une propriété de type ICommand que nous lierons à la propriété Command d’un contrôle si il en dispose.
J’utilise la classe RelayCommand de la bibliothèque mvvm qui hérite de ICommand.
Prenons le cas d’un bouton.
<Button Content="Click0" Command="{Binding ClickEvent0}" Grid.Column="0" HorizontalAlignment="Center"/> <Button Content="{Binding TitleButton1}" Command="{Binding ClickEvent1}" Grid.Column="1" HorizontalAlignment="Center"/> <Button Content="Click2" Command="{Binding ClickEvent2}" CommandParameter="{Binding Title}" Grid.Column="2" HorizontalAlignment="Center"/> <Button Content="Click3" Command="{Binding ClickEvent3}" CommandParameter="{Binding Title}" Grid.Column="3" HorizontalAlignment="Center"/>namespace SampleBinding { public class MainPageViewModel : ViewModelBase { #region Properties private string title; private string placeHolder; private string fieldTextBox; private string titleButton1; .... public string TitleButton1 { get { return titleButton1; } set { Set(ref titleButton1, value); } } public ICommand ClickEvent0 { get; private set; } public ICommand ClickEvent1 { get; private set; } public ICommand ClickEvent2 { get; private set; } public ICommand ClickEvent3 { get; private set; } #endregion public MainPageViewModel() { Title = "Titre de la page"; PlaceHolder = "Identifiant"; TitleButton1 = "Click1"; ClickEvent0 = new RelayCommand(() => { Title = "Change Titre 0" }); ClickEvent1 = new RelayCommand(ClickEventMethode1); ClickEvent2 = new RelayCommand<string>((str) => { Title = str + " ClickEvent2"; }); ClickEvent3 = new RelayCommand<string>(ClickEventMethode3); } private void ClickEventMethode1() { Title = "Change Titre 1"; } private void ClickEventMethode3(string str) { Title = str + " ClickEventMethode3"; } } }il est possible de d’envoyer un paramètre à un événement, pour cela il faut passer par la propriété CommandParameter du contrôle.
Binding d’une liste :
Pour lier une liste, il faut créer dans le MainPageViewModel une propriété de type List et de la lier ItemSource. ainsi que les différents champs de la liste aux éléments d’un item.
Reprenons le cas pratique d’une application qui liste les clubs de Ligue 1 vue pour la RecyclerView d’android.
namespace SampleBinding { public class Club { public string Name { get; set; } public string LogoUrl { get; set; } public string Year { get; set; } public string WebSiteUrl { get; set; } } }namespace SampleBinding { public class MainPageViewModel : ViewModelBase { #region Properties private string title; private string placeHolder; private string fieldTextBox; private ObservableCollection<Club> list; .... public ObservableCollection<Club> List { get { return list; } set { Set(ref list, value); } } #endregion public MainPageViewModel() { .... List = new ObservableCollection<Club>(); CreateClub(); //Ajoute des clubs à List } .... } }<GridView ItemsSource="{Binding List}" ItemTemplate="{StaticResource ItemListTemplate}" Grid.Row="2"> </GridView><DataTemplate x:Key="ItemListTemplate"> <Grid Width="150" Height="150" Background="OrangeRed"> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Image Source="{Binding LogoUrl}" Stretch="None" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="0"/> <TextBlock Text="{Binding Name}" Grid.Row="1" Foreground="Black" FontSize="15" VerticalAlignment="Center" HorizontalAlignment="Center"/> <TextBlock Text="{Binding Year}" Grid.Row="2" Foreground="Black" FontSize="12" Margin="0,0,0,10" VerticalAlignment="Center" HorizontalAlignment="Center"/> </Grid> </DataTemplate>Binding Contrôle Propriété :
il est possible de lier la propriété d’un contrôle à un autre contrôle.
Pour cela il faut au préalable avoir nommé son contrôle via x:Name, puis de se servir de l’attribut ElementName et de l’attribut Path.
<GridView x:Name="ListClubs" ItemsSource="{Binding List}" ItemTemplate="{StaticResource ItemListTemplate}" Grid.Row="2"> </GridView> <TextBlock Text="{Binding ElementName=ListClubs, Path=SelectedItem.Name}" Grid.Row="3" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>Dans cet example je lie le nom de l’élément sélectionné (SelectedItem.Name) de la liste des clubs (ListClubs) au champs Text Du TextBlock.
Binding Converter :
Grâce aux converters il est possible de convertir une donnée vers un autre type.
<GridView x:Name="ListClubs" ItemsSource="{Binding List}" ItemTemplate="{StaticResource ItemListTemplate}" Grid.Row="2"> </GridView> <TextBlock Text="{Binding ElementName=ListClubs, Path=SelectedItem.Name}" Foreground="{Binding ElementName=ListClubs, Path=SelectedItem.Name, Converter={StaticResource ConverterNameToColor}}" Grid.Row="3" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>Definition du converter dans les ressources de la page.
<Page.Resources> <local:ConverterColor x:Key="ConverterNameToColor"/> </Page.Resources>namespace SampleBinding { public class ConverterColor : IValueConverter { public Converter(object value, Type targetType, object parameter, string culture) { if (value is string) { string name = value as string; if (name.Tolower() == "bordeaux") { return new SolidColorBrush(Color.fromArgb(255, 109, 7, 26)); } } return SolidColorBrush(Colors.Black); } } public ConverterBack(object value, Type TargerType, object parameter, string culture) { return value; //ne sert pas dans notre example } }dans cet exemple j’affiche le text en rouge de l’élément sélectionné si ce dernier s’agit de Bordeaux. Pour cela j’ai lié la propriété Foreground au nom de l’objet sélectionné de la liste des clubs puis j’ai appliqué le converter ConverterNameToColor.
Ajouter un Binding à un contrôle :
il est possible d’ajouter une liaison à un contrôle si il ne possède pas celui qui vous intéresse.
Par exemple dans le cas d’une list, si l’on souhaite récupérer l’événement Click sur un item, il faut faire ceci :
<GridView x:Name="ListClubs" ItemsSource="{Binding List}" SelectionMode="None" IsItemClickEnabled="True" ItemClick="ListClubs_ItemClick" ItemTemplate="{StaticResource ItemListTemplate}" Grid.Row="2"/>namespace SampleBinding { public sealed partial class MainPage : Page { .... private void ListClubs_ItemClick(object sender, ItemClickEventArgs e) { (this.DataContext as MainPageViewModel).ItemClick(e.ClickedItem as Club); } } }Créons un extension au liste :
namespace SampleBinding { public static class ConstantBinding { public const string Command = "Command"; public const string CommandParameter = "CommandParameter"; } public static class ListViewBaseItemClickCmd { #region Register public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached(ConstantBinding.Command, typeof(ICommand), typeof(ListViewBaseItemClickCmd), new PropertyMetadata(null, OnCommandChanged)); #endregion #region Getter / Setter public static ICommand GetCommand(DependencyObject attached) { return (ICommand)attached.GetValue(CommandProperty); } public static void SetCommand(DependencyObject attached, ICommand value) { attached.SetValue(CommandProperty, value); } #endregion #region EventChanged private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is ListViewBase) { (d as ListViewBase).ItemClick += ListBaseItemClick; } } private static void ListBaseItemClick(object sender, ItemClickEventArgs e) { if (sender is ListViewBase) { var command = GetCommand(sender as ListViewBase); var parameter = e.ClickedItem; if (command != null && command.CanExecute(parameter)) { command.Execute(parameter); } } } #endregion } }Nous pouvons maintenant lier une variable de type ICommand à nos LisView ou GridView.
<GridView x:Name="ListClubs" ItemsSource="{Binding List}" SelectionMode="None" IsItemClickEnabled="True" local:ListViewBaseItemClickCmd.Command="{Binding ClickItem}" ItemTemplate="{StaticResource ItemListTemplate}" Grid.Row="2"> </GridView>