Developpez.com

Club des développeurs et IT pro
Plus de 4 millions de visiteurs uniques par mois

Developpez.com - Microsoft DotNET
X

Choisissez d'abord la catégorieensuite la rubrique :


Documentation des outils de Developpez

Date de publication : 12/09/2006

Par Florent GABRIEL (homepage)
 

Une initiation à l'utilisation des DataBinding sur des objets ADO.NET en Visual Basic .NET .


Introduction
II. Comment lier un contrôle à une source ?
II-A. Instancier un objet Binding
II-B. Le lier au contrôle graphique
II-C. Et lier et remplir un contrôle graphique liste
III. DataBinding et relation
III-A. DataRelation
III-B. Lier la table parente
III-C. Et lier la table enfant
III-D. Et enfant vers parent ?
IV. BindingManager
IV-A. Assure la cohésion des Bindings
IV-B. Accéder au BindingManager
IV-C. Utiliser le BindingManager
IV-C-1. Gérer la position en cours :
IV-C-2. Obtenir l'élément en cours :
IV-C-3. Valider et annuler les modifications de la ligne en cours
IV-C-4. Intercepter un changement de position
IV-C-5. CurrencyManager et PropertyManager
V. Parse et Format (modifier à la volée)


Introduction

Le FrameWork .NET intègre une fonctionnalité destiné à simplifié la vie des humbles programmeurs que nous sommes : le DataBinding.

Le DataBinding ("liaison de données") est l'art de lier avec une simplicité déconcertante des source de données tel que des DataTable, des ArrayList ou encore des "Property" d'un objet.

Tout l'intérêt (peut-être devrais-je dire "toute la magie") de ces liaisons est qu'elles fonctionnent alors de façon synchronisée: si la source de donnée liée est modifiée, la modification se répercute dans le contrôle, et réciproquement, une modification dans le contrôle entraîne automatiquement une modification dans la source liée.
Encore mieux, dans le cas d'une DataTable, lier un contrôle-liste (un ListBox par exemple) va nous permettre de sélectionner la ligne que nous choisissons d'éditer. Le tout en quelques lignes de codes.

Ce document est initiation au DataBinding, je me contenterai ici d'exposer les liaisons qui utilisent des sources de données liste. Mais il est bon de savoir que les sources de données peuvent aussi être des "property" d'objet: il est alors possible de lier la "property" Checked (de type booléenne) d'un CheckBox à la "property" Visible d'une fenêtre Form: cocher le CheckBox fera dès lors apparaître la fenêtre, et la décocher la rendra invisible (ceci toujours en une ligne de code).

Le langage que j'utilise ici est le Visual Basic, mais ce n'est que pour illustration ... le passage vers un autre langage ne doit pas être bien sorcier.


II. Comment lier un contrôle à une source ?


II-A. Instancier un objet Binding

La classe Binding permet de faire le lien entre des propriétés d'un contrôle et des membres de sources de données. Il prends trois arguments :
    ' Déclaration du Binding :
    Dim monBinding As Binding = New Binding("PropertyContrôle",DataSource,"DonnéeMembre")
PropertyContrôle
(String)
  La propriété du contrôle graphique à lier (le "Text" pour un TextBox, "Checked" pour un CheckBox...
DataSource
(Object)
  Représente la source de données.
CheminNavigationSource
(String)
  Pour un DataTable, c'est le nom de la colonne.

II-B. Le lier au contrôle graphique

Un contrôle graphique peut être lié à plusieurs binding; il gère en faite une collection de Bindings (ControlBindingsCollection) accessible par sa méthode DataBindings. Il est dès lors possible de lier les différentes propriétés d'un contrôle, stockés dans cette collection.

Ainsi, si nous avons un DataTable "maTable", dont nous voulons lier la colonne "maColonne", à un TextBox "monTextBox" :
    ' Déclaration du Binding :
    Dim monBinding As Binding = New Binding("Text" , maTable  , "maColonne")

    ' Ajout aux Bindings d'un TextBox
    monTextBox.DataBindings.add(monBinding)

Il est aussi possible de le coder en une ligne :
    monTextBox.DataBindings.add("Text",maTable,"maColonne")

II-C. Et lier et remplir un contrôle graphique liste

Dans le cas d'un DataTable, on peut lier un contrôle liste (ListBox, ComboBox ...) à une colonne de cette DataTable. Dès lors, il listera non seulement les champs de la colonne spécifié, mais aussi, comme les liaisons d'une même source (comme nous le verrons dans le chapitre suivant) sont interdépendant, la sélection d'une position dans cette liste conditionnera la position de la ligne en cours d'édition; ligne à laquelle sont peut-être lié à d'autres contrôles de la même source.

Lier un contrôle de liste est différent d'un Binding avec un contrôle simple car il ne s'agit pas simplement d'une propriété d'un contrôle, mais d'ajouter des éléments à une liste.

Pour cela, nous devons utiliser deux "Property" du contrôle en question
    monContrôleList.DataSource = DataSource
    monContrôleList.DisplayMember = "NomMembreDataSource"

Ainsi, pour continuer notre exemple, nous souhaitons lier la colonne "maColonneID" à notre contrôle-liste monControleListe :
    monControleListe.DataSource = maTable
    monControleListe.DisplayMember = "maColonneID"
warning Affecter au DisplayMember un membre n'existerais pas n'est pas fatale à l'exécution; l'affichage sera évidemment erroné : il s'afficherait alors une liste de "System.Data.DataRowView".

Pour capturer un DisplayMember inexistant, l'exception `System.ArgumentException`.


III. DataBinding et relation

Nous allons voir ici comment "binder" les tables liées par des relations.
Les contrôles doivent être liés à une même source de donnée pour assurer une cohésion entre ceux-çi : c'est le DataSet qui permet de contenir plusieurs tables, mais aussi des relations entre ces tables.


III-A. DataRelation

Nous allons d'abord créer une relation.

Soit deux tables tableParent et tableEnfant définit ainsi :
	Dim tableParent As New DataTable("TABLE_PARENT")
	Dim tableEnfant As New DataTable("TABLE_ENFANT")
La table TABLE_PARENT à la structure :
ID représentant l'identifiant (la clef primaire)
NOM le nom d'une personne
PRENOM et son prénom

Et la table TABLE_ENFANT :
ID_PARENT la clef étrangére de la table parent
ADRESSE l'adresse
NUM_TEL un numéro de téléphone éventuel pour cette adresse

info Pour rappel, un DataSet est une sorte de collection de tables, dans lequel on peut aussi lier ces tables par des DataRelation(s))

Nous plaçons donc nos tables dans un DataSet :
   ' Déclaration et instanciation du DataSet :
    monDataSet As New DataSet
    
    ' Et on y ajoute les tables :
    monDataSet.Tables.Add(tableParent)
    monDataSet.Tables.Add(tableEnfant)

Nous allons ensuite y ajouter une relation liant nos deux tables.
Pour instancier une relation, une syntaxe possible est :
   New DataRelation (NomRelation As String _
                           ,Colonne_parent As DataColumn _
                           ,Colonne_enfant As DataColumn_
                        )<br/>
   ' On définit la relation :
   Dim relation_NomVersAdresse as New DataRelation ("NomVersAdresse" _
                     , tableParent.Columns("ID") _
                     , tableEnfant.Columns("ID_PARENT") )

   ' et on l'ajoute au DatSet :
   monDataSet.Relations.add(relation_NomVersAdresse)

III-B. Lier la table parente

On définit les Bindings concernant les champs de la table "TABLE_PARENT".
Nous devrons utiliser la même source pour la table enfant que pour notre table parent : le DataSet.
On peut voir que pour accéder à une colonne d'une table, la syntaxe est la même que pour accéder à une objet d'un objet : la hiérachie se fait par le point :
   ' Une ListBox affichant les identifiants :
   maListBox_ID_PARENT.DataSource = monDataSet
   maListBox_ID_PARENT.DisplayMember = "TABLE_PARENT.ID"

   ' et des TextBoxs pour respectivement "NOM" et "PRENOM"
   monTextBox_NOM.Bindings.add("Text",monDataSet, "TABLE_PARENT.NOM")
   monTextBox_PRENOM.Bindings.add("Text",monDataSet, "TABLE_PARENT.PRENOM")

III-C. Et lier la table enfant

Et enfin, pour afficher la liste des adresses pour la colonne parente en cours,
il faut simplement utiliser la syntaxe "ColonneParent.NomRelation.NomColonneEnfant",
ou même "NomRelation.NomColonneEnfant" :
   ' Binding sur une ListBox
   ' on la lie aux adresses qui correspondant à la ligne parente en cours
   maListBox_ADRESSES.DataSource = monDataSet
   maListBox_ADRESSES.DisplayMember = "NomVersAdresse.ID_PARENT"

III-D. Et enfant vers parent ?

Maintenant, nous voudrions avoir la liste de toutes les adresses; propriétaires confondus, et le nom du propriétaire pour l'adresse à la position en cours.

Jusqu'à maintenant (version 2 incluse de MSDN), il n'est pas proposé de solution du type :
  new Binding ( ''Text'' , monDataSource , ''Parent(NomRelation).NomColonneParent'')

Microsoft proposes d'étendre la classe TypeDescriptor et d'y faire des surcharges. Méthode certainement extrêmement propre, mais compliqué et longue à mettre en place.

Une solution certes moins élégante, mais plus simple est d'ajouter des colonnes calculées à la DataTable enfant :

   ' Ajouter des colonnes calculées à partir d'une colonne de la table parent :
   monDataSet.Tables(''TABLE_ENFANT'').Columns.Add("NomPropriétaire" _
                                                   , GetType (String) _
                                                   , "Parent(NomVersAdresse).NOM" )

   monDataSet.Tables("TABLE_ENFANT").Columns.Add("NomPropriétaire" _
                                                , GetType (String) _
                                                , "Parent(NomVersAdresse).PRENOM" )

Une colonne calculée est très intéressante du fait que le résultat se rafraîchit dés qu'une modification est effectuée dans les sources des données utilisées pour le calcul.
Mais, le système ne sait pas inverser un calcul, la colonne calculée est Read-Only.
Il est par contre possible de modifier les données qui veulent modifier cette colonne calculée (voir )


IV. BindingManager


IV-A. Assure la cohésion des Bindings

Le BindingManager gère la cohésion entre bindings d'une même source.

Comme nous l'avons vu précédemment, en liant des contrôles (dont un contrôle de liste) sur une même source de donnée, nous pouvons naviguer dans cette source de données: sélectionner un élément dans le contrôle liste placera les autres contrôles dépendant de cette même source sur cette ligne sélectionné. Et c'est le BindingManager qui gére entre autre la modification de cet index.

Schéma BindingManager
info Une fenêtre Form gère plusieurs BindingManager Un BindingManager gère une et une seule source de donnée. Un BindingManager peut gérer (et c'est son principe) plusieurs Bindings.

IV-B. Accéder au BindingManager

La méthode BindingContext de la classe Form retourne la collection de tous les BindingManager de cette fenêtre. Pour accéder au BindingManager lié à une source, on utilise la même méthode que pour n'importe quelle autre collection :


Dans l'exemple qui suit :

  • Nous recupérons le BindingManager lié à un DataTable que nous appellerons monDataTable
  • Nous appelons BindingContext dans la classe de la fenêtre courante, nous utilisons donc Me. On peut aussi très bien récupérer le BindingManager d'une autre Form extérieure à la classe courante
    Dim bm as BindingManagerBase
    bm = Me.BindingContext(monDataTable)

Et dans le cas du BindingManager lié à une table d'un DataSet :
    Dim bm_DataSet as BindingManagerBase
    bm_DataSet = Me.BindingContext(monDataSet , "NomTable")

IV-C. Utiliser le BindingManager

Voici quelques méthodes de la classe BindingManagerBase.


IV-C-1. Gérer la position en cours :

Position est une `Property` qui permet d'obtenir ou de spécifier la ligne en cours :
    ' obtient la position de la ligne en cours dans la DataTable
    Dim positionCourante As Integer
    positionCourante = Me.BindingContext(monDataTable).position

    ' se place à la ligne 13 de la DataSource
    Me.BindingContext(monDataTable).position = 13

    ' avance à la position suivante
    Me.BindingContext(monDataTable).position += 1
warning La première position est à 0 et la derniére à Count - 1. Si la position spécifié sort des limites, la position est directement placée à la derniére position dans le cas d'un débordement au-delà du maximum, ou à la position 0 dans le cas d'une position avant le premier.

IV-C-2. Obtenir l'élément en cours :

Current renvoie une image de l'élément en cours. Dans le cas d'un DataTable, cette méthode retourne un DataRowView, pour laquelle on peut obtenir la DataRow correspondante par la méthode row.


IV-C-3. Valider et annuler les modifications de la ligne en cours

Une ligne d'une DataView bindé n'est pas directement validée après la modification, via des contrôle qui lui sont lié, de ses champs. C'est ce qui est intèressant dans l'utilisation de DataBinding sur un DataView plutôt que sur un DataTable. On peut ainsi annuler les modification avant qu'il ne soit vraiment validé, ou alors forcer la validation :
    ' Valider la ligne
    Me.BindingContext(monDataView).EndCurrentEdit
    ' Annuler la modification sur la ligne
    Me.BindingContext(monDataView).CancelCurrentEdit

IV-C-4. Intercepter un changement de position

Un événement PositionChanged est généré dans un BindingManager après qu'un changement de position se soit produit dans celui-çi. On peut intercepter cet événement et le traiter dans une fonction :

Voici deux façons de faire :
    <b>AddHandler Me.BindingContext(monDataView)<b>.PositionChanged , AddressOf maFonction  
	
    Sub maFonction(sender As Object, e As EventArgs)
         '  ....
    End Sub
    Sub maFonction(sender As Object, e As EventArgs) Handles Me.BindingContext(monDataView).PositionChanged
    	' ....
    End Sub
info Dans la fonction, l'objet Sender correspond au BindingManager qui a appelé cette fonction.


IV-C-5. CurrencyManager et PropertyManager

En réalité, la classe BindingManagerBase est virtuelle, elle ne peut pas être instanciée. Elle implémente les méthodes de base et sera étendu par d'autre classes. Ainsi, deux classes héritent de BindingManagerBase : CurrencyManager et PropertyManager :

  • CurrencyManager : gère les source de données implémentant une des interface IList,IListSource ou IBindingList (DataTable,DataView,DataSet,ArrayList…)
  • PropertyManager : gère des propriétées d'un objet. Même si Position et Count sont accessible, elle sont inutiles.
info Comme la classe BindingManagerBase est parente de ces deux classes, elles peuvent prendre le type BindingManagerBase.


V. Parse et Format (modifier à la volée)

Il est possible de modifier à la voler la valeur qui transitent entre un contrôle et une source de donnée.
Pour ça, on utilise les événements Parse et Format de la classe Binding correspondante.

  • Parse : se produit quand la source est modifié, et va donc (après un passage dans notre fonction de "parsage") modifier le contrôle (DataSource vers contrôle)
  • Format : lui, est généré lorsque le contrôle va modifié la source (contrôle vers source)
Il suffit de les intercepter via un AddHandler et de faire les traitements dans la fonction qu'appellera notre AddHandler :
    AddHandler monBinding.Parse , AddressOf maFonction_Parse
    AddHandler monBinding.Format , AddressOF maFonction_Format
On ajoute ensuite les fonctions correspondantes avec la signature (Object ,ConvertEventArgs) afin de récupérer les données envoyés par l'événement Parse ou Format :
    ' Fonction sur l'évenement Parse :
    Sub maFonction_Parse (Sender As Object , cEvArgs As ConvertEventArgs)
        ' Traitement
    End Sub

    ' Fonction sur l'évenement Format :
    Sub maFonction_Format (Sender As Object , cEvArgs As ConvertEventArgs)
        ' Traitement
    End Sub

C'est ce deuxième argument de type ConvertEventArgs qui va nous permettre de modifier la valeur en transite. En quelque sorte, c'est un pointeur sur la valeur que nous voulons traiter.
Nous pouvons ainsi modifier par sa `Property` Value de type Object, la valeur qui va modifier, dans un cas, le DataSource, ou dans l'autre, le contrôle.


Dans l'exemple qui suit, la source de donnée est de type Date, et nous souhaitons que cette date s'affiche dans un format "12.11.05" .
    <i>' Ajout du Handler</i>
    AddHandler monBinding.Format , AddressOf maFonction_Format

    ' La fonction de traitement :
    Sub maFonction_Format (Sender As Object, cEvArgs As ConvertEventArgs)
        cEvArgs.Value = Format (cEvArgs.Value , "dd.MM.yy")
    End Sub
idea Dans ce cas précis, la propriété lié de ce contrôle devra être ReadOnly. Dans le cas contraire, il faudra aussi écrire une fonction interceptant le Parse sur ce Binding, et de modifier ce cEvArgs.Value pour qu'il soit dans le format acceptable pour une donnée de type Date.



Valid XHTML 1.1!Valid CSS!

Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
Responsable bénévole de la rubrique Microsoft DotNET : Hinault Romaric -