Composite Application Guidance WPF/Silverlight - Requis : le DataBinding - 3

12 Dec
2009

Comme je vous l’avez écrit dans mon précédent article, quelques prérequis doivent être connus afin de continuer à bien notre quête. En effet, le chemin sera long avant de maîtriser tous les tenants et aboutissants de cette matière vaste. Selon l’adage, “petit train ira loin”, je m’efforcerai le plus possible à rentrer dans les détails, quitte parfois à sortir de notre but initial. Prenons par exemple cet article, il traîte du databinding, bien qu’il se détourne de notre but (Composite Application Guidance), il pourra néanmoins vous servir pour comprendre le principe du binding dans wpf et silverlight (ce qui pourra vous servir pour d’autres projets). Et finalement, ce n’est pas cela être Agile ?  Se développer de manière indépendante et ensuire recoller les morceaux (ici l’apprentissage) afin de former un tout (la connaissance).

Qu’est ce que le databinding ?

Selon Ms, c’est la méthode qui permet de lier les composants UI (textbox, listbox,…) à la couche métier. En effet, si les données changent dans la couche métier et que le binding est correctement configuré, les éléments UI se mettront à jour automatiquement. Mais l’inverse est également vrai, si l’utilisateur rentre une valuer dans un textbox, par le binding, le changement sera automatiquement reflété dans la couche métier.

Concept du databinding :

ms752347_databindingmostbasicen-usvs_90fig 1 (from MSDN)

  • La databinding est le lien entre l’objet cible (target en anglais) et l’objet source. Chaque Binding possède quatre composants, un “binding target object” (l’objet cible), une “property target” (la propriété cible de l’ojet cible), un “binding source object” (la source de donnée), et un “path” c’est à dire un chemin vers la valeur se trouvant dans l’objet source.

Par exemple, si vous vouler “binder” le contenu d’un Textbox à la propriété Nom d’un objet Humain, votre objet cible sera la TextBox,
la propriété cible sera la propriété Text de l’object Textbox, la valeur à exploité (le “path”) est la propriété source Nom de l’objet source Humain.

  • La propriété de l’objet cible que nous voulons manipuler est un “Dependency Property”  traduction une propriété dépendante. Ceci afin de pouvoir lier cette propriété par databinding. Presque toutes les propriétés des éléments UI (textbox, listbox) sont des “Dependence Property” sauf les “Read-Only”. En effet, ces classes dérivent tous du DependencyObject class (voir ici et ici) qui permet de définir des DependencyProperty qui support le binding.
  • L’objet source n’est seulement un object CLR personnalisé (un object comme que vous avez créé comme, par exemple, humain), cela pourrait être tout aussi bien, des objets qui proviennet d’ADO.NET, de webservice ou encore, d’un noeud XML qui contient des données.

A noter que pour créer ces liens (ce databind), vous utilisez des BindingOjbects, c’est grâce à ces objets que le lien peut se faire.

La direction du flux de donnée :

dataflowfigure 2 (from MSDN)

En effet, le flux de données à un sens, comme l’on peut le voir dans la figure 2. Il y a un sens aux flèches. Le flux de donnée va d’un object source vers un objet cible. La propriété name du textbox pourrait être tout aussi bien être la cible que la source. Ce n’est plus la propriété text du TextBox qui se “remplit” de la proriété Nom de l’objet Humain mais c’est maintenant la propriété text du TextBox qui “remplit” la propriété Nom de l’objet Humain. Ceci implique donc trois sens pour les flux de données (noms donnés en anglais).

  • One Way: la propriété cible change selon les données de la propriété source mais l’inverse n’est pas vrai. Ceci convient pour les contrôles de type Read-Only.
  • Two Way: le flux de données se propagent dans les deux sens, la propriété est tantôt une propriété source, tantôt une propriété cible. Générallement, nous utiliserons Two Way pour les objets de type formulaire. Par exemple, la propriété Text du TextBox et la propriété IsChecked de l’objet CheckBox est par défaut Two Way.
  • OneWayToSource: il est l’inverse de OneWay, il met à jour la propriété source lorsqu’il y a un changement dans la propriété cible.
  • One Time (ne se trouve pas dans la figure 2): Initialise la propriété source mais les changements futures ne seront pas propagés. Ce type de “binding” (de lien) est utilisé si vous voulez prendre un instantané de l’état de votre couche métier. Il est plus ou moins équivalent au OneWay mais est plus performant, il est donc à utiliser lorsque les valeurs sont statiques.

Comment détecter des changements dans l’objet source (ceci uniquement pour OneWay et TwoWay) ?

L’objet doit implémenter un méchanisme de notification, en implémentant l’interface INotifyPropertyChange (nous verrons pourquoi plus tard).

Que déclenche un changement dans la source ?

Les databindings qui sont TwoWay et OneWayToSource doivent être attentif aux changements de la propriété cible afin de les propagers vers l’objet source. Le sens du flux de données est assuré par la propriété Mode de l’objet Binding.

Cependant, est-ce que la valeur de votre objet source (par exemple la propriété Nom de l’objet Humain) change t’elle lorsque vous être entrain de changer cette valeur (lors de la saisie dans le TextBox) ou après que vous ayez fini (de changer la valeur du TextBox) ?

En fait, c’est la propriété UpdateSourceTrigger qui détermine ce qui va déclencher la mise à jour de l’objet source.

dataflowupdatesourcetriggerfigure 3 (from MSDN)

Si nous regardons la figure 3, les points représentés aux extrémités des flèches illustrent le rôle de l’UpdateSourceTrigger.

Si l’UpdateSourceTrigger à la valeur “PropertyChanged”, dès que la valeur dans le textbox changera, la valeur de l’object source changera également. Par contre, si l’UpdateSourceTrigger à comme valeur “LostFocus”, la valeur de l’objet source sera propagée lorsque nous nous retirerons du TextBox (passer à un autre textbox par ex.). La valeur par défaut de UpdateSourceTrigger dépend de l’objet. Pour un TextBox cette valeur est, par défaut, LostFocus. En effet, si nous devrions propager la valeur à chaque fois que nous entrons une lettre dans le textbox, les performances diminueraient, c’est pour cette raison que la valeur par défaut de UpdateSourceTrigger est LostFocus et non pas PorpertyChanged.

Il existe également pour le textbox une troisième valeur pour UpdateSourceTrigger, Explicit, utilisée par exemple pour mettre à jour la valeur seulement lors d’un clique de bouton par l’utilisateur (plus d’info sur l’UpdateSourceTrigger : ici ).

Créer un Binding :

Passons au pratique, nous allons établir un lien à l’aide de l’objet binding. Je ne vais pas vous le cacher, pour commencer je vais prendre un exemple MSDN.

Nous avons un object source qui est une classe MyData, elle a une propriété string ColorName dont la valeur est rouge. Nous voulons lier la couleur du background à la valeur ColorName de MyData.

Voici le code en XAML : pour plus d’info sur la syntaxt du binding (ici)

xaml_exemple11

Et voici le code C# :

cexmple1

Ce binding est un binding OneWay en effet, la propriété background du TextBox est par défaut OneWay.

databinding_backgroundtextboxtfigure 4

Le background property de l’object textbox est de type brush, néanmoins nous lui passons un string, ceci fonctionne par une conversion implicite (plus d’info ici).

Spécifier le Binding Source :

Ici le binding source est défini spécifiant une valeur au DataContext. Le DataContext du Window1 est l’objet MyData. TextBoxt hérite de ce datacontext par l’objet Window1 (son parent). En donnant le chemin vers la propriété qui doit être lié dans le background du textblock. Celui-ci sera où prendre ces données. Si nous ne définissons aucune valeur pour le datacontext, le background du textbox ne prendrait pas la couleur rouge.

Nous obtenons alors le résultat suivant :

appli1

Il est intéressant de définir le datacontext dans un niveau parent lorsque vous voulez utilisez ce contexte pour plusieurs enfants. Dans le cas contraire, vous pouvez définir ce contexte rien que pour l’élément que vous voulez lier.

La Classe BindingExpression :

Comme défini plus haut, Binding class est la classe haut niveau pour définir un Binding. Le BindingExpression qui est une sous-jacente de l’objet Binding, cet objet permet de maintenir la connexion entre l’objet source et l’objet target. Un objet Binding contient toute l’information et peut être partagée par plusieurs BindingExpression. Une BindingExpression est une expression d’instance qui ne peut partager ses informations, elle contient toute l’information de l’instance du Binding.

bindingexpression

myDataObject est une instance de la classe MyData, myBinding est la source de l’objet Binding, MyData est une classe qui contient la propriété string MyDataProperty. Cet exemple lie le contenu text d’une instance de TextBLock myText vers MyDataProperty.

Dans ce cas, vous pouvez réutiliser l’objet myBinding, par exemple, en liant à un propriété d’un TextBlock. A ce moment, il n’y aura qu’un objet Binding mais deux instances de BindingExpression qui se partage l’objet Binding.

DataConversion :

Dans l’exemple précédent, la propriété background du Texbox est lié à une propriété string avec la valeur « Red ». Cela fonctionne car le type Brush est capable de convertir un string en un objet Brush .

dataconversionfigure 5

Comme l’on peut remarquer sur la figure 5, le Type Brush à une conversion par défaut qui permet de convertir un objet de type string en objet de type Brush.

Que se passerait t’il si notre objet source avait un type Color pour la propriété ColorName. Nous devrions implémenter l’interface IValueConverter (pour plus d’info sur IValueConverter, cela se passe ici). De cette manière l’objet binding sait comment convertir un objet Color en objet de type SolidColorBrush.

ivalueconverter

Par l’implémentation de cette inferface, notre schéma devient alors :

dataconversionwithivalueconverterfigure 6

Généralement, pour les types cible connus, la conversion par défaut devrait fonctionner. En cas de doute, vous devriez implémenter l’interface IValueConverter.

Voici une liste de cas où l’utilisation de cette interface est préconisée :

  • Vos données devront être affichées différement suivant la culture que vous utilisez. Vous pourriez implémenter un convertisseur de change et/ou un convertisseur de date utilisée par une certaine culture.
  • La valeur qui est manipulée ne servira pas à changer la propriété Text du Textbox mais d’autres valeurs comme une source d’image, la couleur ou le style de text affiché. Les convertisseurs peuvent alors être utilisés en convertissant des propriétés qui ne semblerait pas lié entres-elles. Comme par exemple, lier la valeur d’un textbox à une couleur de fond pour une cellule d’un tableau.
  • Plus d’un contrôle ou même plusieurs propriétés d’un contrôle peuvent être liées à une même source. Générallement, le “Binding” primaire affichera le texte et d’autres types de “Binding” posséderont certaines particularités sur la manière de relier les éléments entre eux, ceci tout en continuant à utiliser la même source d’information.
  • Dans le cas où nous utiliserons des MultiBinding , dans ce cas un propriété cible sera lié à une collection de Binding. Dans le cas d’un multibinding vous utiliserez l’interface IMultiValueConverter afin de produire une valeur finale à partir des valeurs contenues dans votre collection de Binding.

Binding to Collection :

Dans un Binding, un objet source peut être soit traité comme un simple objet dans lequel ses propriétés contiennent des données soit comme une collection de données d’objets polymorphiques groupés ensembles (comme le résultat d’une query provenant d’une base de données). Ici nous avons toujours utilisés des objets simples mais nous aurions pu utiliser des ItemsControl comme des ListBox, des ListView, des TreeView afin d’afficher des collections d’objets.

databindincollectionobjetfigure 6

Comme l’on peut remarquer dans le diagramme précédent, pour lié un ItemsControl à une collection d’objet, nous devons utiliser la propriété ItemsSource. ItemsSource est en quelque sorte le contenu du ItemsControl. Par défaut la direction du flux de donnée pour un ItemsControl sera OneWay.

Comment implémenter les collections ?

Vous pouvez utiliser n’importe quelles collections qui utilisent l’interface IEnumerable. Pour gérer un binding qui permettrait l’insertion, la suppression ou l’update de donnée automatiquement retranscrite dans l’UI, la collection doit implémenter l’interface INotifyCollectionChange. Cette interface permet d’exposer un évênement qui sera déclenché à chaque changement dans la collection.

WPF founit la classe ObservableCollection(T) qui est une implémentation d’une collection de donnée qui comprend l’interface INotifyCollectionChange. A noter que pour supporter le transfert de données entre l’objet cible et l’objet source, chaque objet dans la collection doit implémenter l’interface INotifyPropertyChange.

Avant d’utiliser votre propre collection, tentez d’utiliser ObservableCollection(T) ou l’une des classes de collection existante comme List(T), Collection(T), BindingList(T), parmis d’autres.

Collection Views

Une fois que votre ItemsControl est lié à un collection de données, vous pourriez vouloir trier, filtrer ou grouper les données. Pour faire cela, vous utilisez les collection views, qui sont des classes qui implémente l’interface ICollectionView.

Que sont les Collection Views ?

Une collection est une couche supérrieur par rapport à une “binding source collection” ce qui vous permet de naviguer et d’afficher la collection source triée, flitrée et groupée en queries, ceci sans changer la source sous-jacente elle même. Une “collection view” maintient également un pointeur vers l’item courant dans la collection. Si la collection source implémente l’interface INotifyCollectionChange, les changements seront déclenchés par l’évênement CollectionChanged  qui sera propagé aux vues.

Parce ques les vues ne changent pas la collection source adjacente, chaque collection source peut avoir plusieurs vues associées à elle. Par exemple, vous pourriez avoir une collection d’objets Task. Avec l’utilisation de vues, vous pourriez afficher la même donnée sous différentes vues. Par exemple, à gauche vous afficheriez les Tasks triées par priorité et de l’autre triée par date.

Commen créer une vue ?

Une façon de créer et utiliser une vue est d’instancier les objets vues directement et les utiliser comme source pour le binding.

collectionviewsourcefigure 7

listboxwithcollectionviewfigure8

Comme nous voyons dans l’exmple ci-dessus qui provient de MSDN, nous pouvons voir que le ListBox à pour Object source un CollectionViewSource lui-même lié à une liste d’objet AuctionItems . Vous pourriez réutililiser ce CollectionViewSource pour un autre type de contrôle, ce qui est important ici est de ne pas oublié de définir le x:Key dans la CollectionViewSource afin de que le contrôle puisse savoir où il va chercher ses données.

Le tableau suivant nous montre quels types de données (vue) sont crées comme CollectionView par défaut (IList -> ListCollectionView). Le résultat serait le même si nous utiliserions un CollectionViewSource afin de créer les CollectionViews.

sourcecollectiontypetocollectionviewtype

Utiliser une Default View

Spécifier une collection view comme un “binding” source est une des solutions pour créer et utiliser des collection view. WPF crée également une default collection view pour chaque collection utilisée comme source du binding. Is vous liez directement un contrôle, WPF la lie à sa vue par défaut. Cette vue par défaut est partagée par tout les “bindings” de la même collection, donc un changement fait à la vue par défaut par un contrôle lié ou du code (trier, filtrer,…) est reflétée dans tous les autres bindings de la même collection.

Collection Views avec ADO.NET DataTables

En vue d’améliorer les performances, les collection views pour ADO.NET DataTable ou les DataView délèguent le tri, les filtres au DataView. En conséquent, le tri et le filtrage sera partagé à travers toutes les collections du datasource. Afin de mettre en place le tri et le filtrage sur chaque collection view, vous devez initialiser chaque collection view avec son propre DataView object.

Sorting

Comme mentioné ci-dessus, les vues peuvent trier un collection. Comme il existe dans la collection adjacente, vos données pourraient ne pas être dans un ordre pertinant. La vue au dessous de cette collection adjacente vous permet d’imposer un ordre et de changer l’ordre par défaut, basé sur des critères de comparaison que vous définissez. Parceque c’est un client-based view des données, un scénario commun pourrait être un utisateur qui veut trier les colonnes d’un tableau suivant ses valeurs correspondantes. En utilisant les vues, ce tri personnalisé pourrait être appliqué, et ce, sans faire de changement dans la collection de la couche adjacente et sans devoir faire de query dans cette couche adjacente (plus d’info ici).

Voici un exemple de tri qui trie par Catégorie et par StartDate :

databindingsorting

Vous pouvez trouver un exmple de binding intéressant sur MSDN, ici.

Filtering :

Une vue peut également appliquer un filtre à une collection. Cela ne permet d’afficher qu’un sous-esemble d’une collection. Vous pouvez également trier sous des conditions personnalisées.

Voici un exemple de filtre qui provient de la démo fournie par MSDN.

filtering

The ShowOnlyBargainsFilter event handler

filtering2

Grouping :

Exepté pour les classe internal qui sont retranscrite en IEnumerable collection, toutes les collections views supporte la fonctionnalité de grouping. Ceci permets aux utilisateurs de subdiviser la collection dans la collection view dans un groupe logique. Les groupes peuvent être explicite, l’utilisateur fournit une liste de groups ou implitcit, les groups sont générés dynamiquement dépendamment des données.

Voici un exemple de fonction de grouping :

addgrouping

Pointer sur l’item actuel:

Les vues supportent également la notion d’item actuel. Vous pourriez naviguer à travers les objets dans une collection view. Pendant que vous naviguer, vous passez d’un item à un autre et un pointeur d’item vous permet de retirer les informations de l’item actuel.

Comme WPF est lié à une collection uniquement en utilisant une vue, toutes les liaisons vers les collections possèdent ce pointeur d’item. Quand vous liez à une vue le caractère “/” désigne le chemin vers la valeur actuel. Dans l’exemple suivant, le data context est une collection view, le premier binding est lié à la collection, le second binding est lié à l’item actuel de la collection et le troisième est lié à la propriété Description de l’item actuel.

Ex :

bindingpath

Le Master Detail Binding Scenario

La notion d’item actuel est utile pour le Master Detail Binding Scenario. Si nous regardons dans l’exemple ci-dessous où le contenu du ListBox détermine le contenu affiché dans le ContentControl. Vous pouvez déterminer le Master Detail Binding scenario en liant deux ou plus contrôle à une même vue.

masterdetailscenario

Ces deux contrôles sont liés à une même vue (listingDataView). Cette exemple marchera car lorsque un objet singleton (ici ContentControl) est lié à une collection view, il est automatiquement à l’item actuel (CurrentItem) de la vue. Si le list control n’est pas lié à un objet CollectionViewSource comme dans l’exemple, alors, vous devriez définir la propriété IsSynchronizedWithCurrentItem à “true” pour que cela fonctionne. L’exemple ci-dessous ne fonctionnerait pas si il n’utiliserait pas un template (voir prochain article).

DataTemplate :

Je reviendrai dans un prochaine sur l’utilisation de DataTemplate.

En attendant, voici une série de lien qui pourra vous être utiles :

http://msdn.microsoft.com/en-us/library/ms752347.aspx#specifying_the_binding_source

http://msdn.microsoft.com/en-us/library/ms752347.aspx#data_conversion

http://msdn.microsoft.com/en-us/library/ms752347.aspx#data_conversion

http://msdn.microsoft.com/en-us/library/dd458894.aspx

http://msdn.microsoft.com/en-us/magazine/cc163299.aspx

http://odetocode.com/articles/740.aspx

1 Response to Composite Application Guidance WPF/Silverlight - Requis : le DataBinding - 3

Avatar

Steven

January 14th, 2010 at 5:56 am

Bonjour,

Je suis tombé sur votre blog par hasard. Après avoir parcouru quelques articles, je l’ai immédiatement placé en favori !

J’ai particulièrement apprécié vos articles sur le Data Binding et les Routed Event. Ce que j’apprécie, c’est que les choses sont clairement expliquées (c’est plutôt rare). De plus, vous parlez également des mécanismes qui sont utilisés dérrière ces notions, ça aide fortement à la compréhension.

J’ai vraiment hâte de lire votre article sur les Dependency Property :)

Comment Form

top