Vous êtes sur la page 1sur 61

Windows : Les contrôles dépendants (VB.

NET)
J-M Rabilloud

www.developpez.com Reproduction, copie, publication sur un autre


site Web interdite sans l'autorisation de l'auteur.

Remerciements
J'adresse ici tous mes remerciements à l'équipe de la rédaction de "developpez.com" et tout
particulièrement à Alacazam et David Pedehourcq pour le temps qu'ils ont bien voulu passer à la correction
et à l'amélioration de cet article.

INTRODUCTION ...........................................................................................................3

AVANT-PROPOS ..........................................................................................................3
Source de données..................................................................................................................................................... 4

Composants ADO.NET ............................................................................................................................................ 4

Dataset Fortement typé ............................................................................................................................................ 5


Structure et relations............................................................................................................................................... 5
Remplissage ........................................................................................................................................................... 5
Rappels de code...................................................................................................................................................... 7

GENERALITES .............................................................................................................7
Liaisons ...................................................................................................................................................................... 7
Contrôles à liaison simple ...................................................................................................................................... 7
Contrôles à liaisons complexe ................................................................................................................................ 8
Liaison directionnelle ............................................................................................................................................. 8

Fournisseurs .............................................................................................................................................................. 8

Consommateur.......................................................................................................................................................... 9

ADO.NET .................................................................................................................................................................. 9
La connexion .......................................................................................................................................................... 9
Les commandes ...................................................................................................................................................... 9
Les objets consommateurs / fournisseurs ............................................................................................................... 9

1
Les objets de liaison ................................................................................................................................................ 11
La classe Binding (liaison simple)........................................................................................................................ 12
La classe BindingManagerBase............................................................................................................................ 13
La classe CurrencyManager ................................................................................................................................. 13
La classe BindingContext..................................................................................................................................... 15

Validation ................................................................................................................................................................ 15
Les restrictions ..................................................................................................................................................... 16
La validation au niveau du contrôle ..................................................................................................................... 16
La validation au niveau du formulaire.................................................................................................................. 16
Contraintes complexes.......................................................................................................................................... 16
Validation différée................................................................................................................................................ 17

Création Vs Exécution............................................................................................................................................ 17

Comprendre les expressions................................................................................................................................... 17

Notion de relation ................................................................................................................................................... 17


Relation un à un.................................................................................................................................................... 17
Relation un à plusieurs ......................................................................................................................................... 18
Relation plusieurs à plusieurs ............................................................................................................................... 18
Intégrité référentielle ............................................................................................................................................ 18

DATAVIEW..................................................................................................................18
Généralités............................................................................................................................................................... 18

Propriétés................................................................................................................................................................. 18
AllowDelete, AllowEdit & AllowNew................................................................................................................. 18
Count .................................................................................................................................................................... 19
DataViewManager................................................................................................................................................ 19
Item....................................................................................................................................................................... 19
RowFilter.............................................................................................................................................................. 20
RowStateFilter...................................................................................................................................................... 20
Sort ....................................................................................................................................................................... 20
Table..................................................................................................................................................................... 20

Méthodes.................................................................................................................................................................. 20
AddNew ............................................................................................................................................................... 20
Delete ................................................................................................................................................................... 20
Find ...................................................................................................................................................................... 21
FindRows ............................................................................................................................................................. 21
GetEnumerator ..................................................................................................................................................... 21

Discussion autour du concept DatarowView ........................................................................................................ 21

J'ai perdu la vue ! ................................................................................................................................................... 21

TABLEAU, LISTE ET LISTE TYPEE ..........................................................................22

CONTROLES DEPENDANTS.....................................................................................24
Propriétés dépendantes .......................................................................................................................................... 24

Les liaisons simples ................................................................................................................................................. 24


Construire les liaisons à la création ...................................................................................................................... 24
Création d’un composant de navigation ............................................................................................................... 28
Zone de texte (label & TextBox).......................................................................................................................... 31

2
Texte Formaté (MaskEdBox & RichTextBox)..................................................................................................... 32
Vrai / Faux (CheckBox & RadioButton) .............................................................................................................. 32
Les Dates .............................................................................................................................................................. 32
Représentation numérique .................................................................................................................................... 34
La navigation relationnelle ................................................................................................................................... 35

Les liaisons complexes ............................................................................................................................................ 36


Zone de liste (ListBox & ComboBox) ................................................................................................................. 36
ErrorProvider........................................................................................................................................................ 45

LE DATAGRID ............................................................................................................47
Présentation............................................................................................................................................................. 47
La source de données............................................................................................................................................ 47
Le contrôle Datagrid............................................................................................................................................. 47
La mise en forme .................................................................................................................................................. 47

Le contrôle Datagrid............................................................................................................................................... 47
Mappage ............................................................................................................................................................... 47
Notions de ligne.................................................................................................................................................... 48
Notions de colonne............................................................................................................................................... 50
Notions de cellule................................................................................................................................................. 50
Navigation ............................................................................................................................................................ 50
Edition .................................................................................................................................................................. 51
Evénements .......................................................................................................................................................... 52

Mise en forme.......................................................................................................................................................... 53
Au niveau du contrôle .......................................................................................................................................... 53
Au niveau de la table ............................................................................................................................................ 54
Au niveau des colonnes ........................................................................................................................................ 54

Manipulation de cellule .......................................................................................................................................... 57

Contrôles contenus.................................................................................................................................................. 58

CONCLUSION.............................................................................................................60

Introduction
Dans ce cours, nous allons étudier la gestion des contrôles lorsqu’ils sont liés à une source de données.
On parle alors de contrôles dépendants ou consommateurs. L’ensemble de ce cours basera ses exemples sur
la base de données ‘Northwind’ pour Access en français. On utilisera le fournisseur managé pour OleDb,
donc l’espace de nom System.Data.OleDb. Ce cours ne portera que sur les contrôles des applications
Windows.Forms, celle-ci étant déjà un vaste sujet. Après un rappel sur le travail avec des données, nous nous
livrerons à une étude pour plusieurs contrôles, celle-ci étant agrémentée d’exemples.
Une connaissance correcte d'ADO.NET est indispensable. Vous trouverez toutes les informations en
lisant http://dotnet.developpez.com/cours/ADO.NET/
Bonne lecture

Avant-propos
Comme je viens de vous le signaler, nous allons travailler sur la base Northwind.mdb avec le fournisseur
managé pour OLEDB. J’ai fais ce choix car la base étant en Français, la lisibilité des exemples s’en trouve
grandement augmentée.

3
Source de données
Le schéma de la base est le suivant :

Composants ADO.NET
De même il m'arrivera d'utiliser des composants de données directement sur ma feuille.
Ils suivront alors la dénomination suivante :

Où dta<Nom> représente un DataAdapter et dtv<Nom> un Dataview.


Toujours pour des raisons de lisibilité nous utiliserons un Dataset fortement typé DsNWind. L'instance
créée sur la feuille sera notée DsNWind2.

4
Dataset Fortement typé
Structure et relations
La structure du Dataset fortement typé reprend stricto sensu celle du schéma. J'y ai ajouté les relations
dont vous trouverez les noms en vert.

Remplissage
La simple description d'un Dataset fortement typé ne suffit pas à son remplissage lors de l'exécution. Le
code de remplissage si j'utilise mes composants sera de la forme
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
Me.dtaCategories.Fill(Me.DsNWind2.Catégories)
Me.dtaClients.Fill(Me.DsNWind2.Clients)
Me.dtaEmployes.Fill(Me.DsNWind2.Employés)
Me.dtaFournisseurs.Fill(Me.DsNWind2.Fournisseurs)
Me.dtaMessagers.Fill(Me.DsNWind2.Messagers)
Me.dtaProduits.Fill(Me.DsNWind2.Produits)
Me.dtaCommandes.Fill(Me.DsNWind2.Commandes)
Me.dtaDetailCommande.Fill(Me.DsNWind2.Détails_commandes)
End Sub

5
Si on n’utilise pas les composants de feuille, il faut créer et paramétrer les DataAdapter dans le code, ce
qui est un peu plus long.
Public Function RempliDataset() As DsNWind

Dim MonDsNWind As New DsNWind


Dim strConn As String = "Provider = Microsoft.Jet.OLEDB.4.0;User
ID=Admin;Data Source=C:\tutoriel\nwind.mdb;"
Dim strCommand As String = "SELECT * FROM "
Dim dtaCategories As New OleDb.OleDbDataAdapter(strCommand &
"Catégories", strConn)
dtaCategories.MissingSchemaAction =
MissingSchemaAction.AddWithKey
dtaCategories.Fill(MonDsNWind.Catégories)
Dim dtaClients As New OleDb.OleDbDataAdapter(strCommand &
"Clients", strConn)
dtaClients.MissingSchemaAction = MissingSchemaAction.AddWithKey
dtaClients.Fill(MonDsNWind.Clients)
Dim dtaEmployes As New OleDb.OleDbDataAdapter(strCommand &
"Employés", strConn)
dtaEmployes.MissingSchemaAction = MissingSchemaAction.AddWithKey
dtaEmployes.Fill(MonDsNWind.Employés)
Dim dtaFournisseurs As New OleDb.OleDbDataAdapter(strCommand &
"Fournisseurs", strConn)
dtaFournisseurs.MissingSchemaAction =
MissingSchemaAction.AddWithKey
dtaFournisseurs.Fill(MonDsNWind.Fournisseurs)
Dim dtaMessagers As New OleDb.OleDbDataAdapter(strCommand &
"Messagers", strConn)
dtaMessagers.MissingSchemaAction = MissingSchemaAction.AddWithKey
dtaMessagers.Fill(MonDsNWind.Messagers)
Dim dtaProduits As New OleDb.OleDbDataAdapter(strCommand &
"Produits", strConn)
dtaProduits.MissingSchemaAction = MissingSchemaAction.AddWithKey
dtaProduits.Fill(MonDsNWind.Produits)
Dim dtaCommandes As New OleDb.OleDbDataAdapter(strCommand &
"Commandes", strConn)
dtaCommandes.MissingSchemaAction = MissingSchemaAction.AddWithKey
dtaCommandes.Fill(MonDsNWind.Commandes)
Dim dtaDetailCommande As New OleDb.OleDbDataAdapter(strCommand &
"[Détails Commandes]", strConn)
dtaDetailCommande.MissingSchemaAction =
MissingSchemaAction.AddWithKey
dtaDetailCommande.Fill(MonDsNWind.Détails_commandes)

End Function
N.B : Comme j'ai intégré les relations dans le schéma XSD, l'ordre de remplissage n'est pas anodin. Je
dois nécessairement remplir les tables parentes avant les tables enfants.

6
Rappels de code
La syntaxe de manipulation des dataset fortement typés peut vous perturber si vous n'avez pas l'habitude
de leur manipulation. Je vous donne ici quelques correspondances avec la syntaxe 'classique'
Atteindre la table Fournisseurs
¾ Typé : Me.DsNWind2.Fournisseurs()
¾ Classique : MonDataset.Tables("Fournisseurs")

Désigner la colonne Fonction


¾ Typé : Me.DsNWind2.Fournisseurs.FonctionColumn
¾ Classique : MonDataset.Tables("Fournisseurs").Columns("Fonction")

Ajouter une ligne


¾ Typé : Me.DsNWind2.Fournisseurs.NewFournisseursRow()
¾ Classique : MonDataset.Tables("Fournisseurs").NewRow()

Atteindre une valeur


¾ Typé : Me.DsNWind2.Fournisseurs.AdresseColumn()
¾ Classique : MonDataset.Tables("Fournisseurs").Rows(1).Item("Adresse")

Généralités
Quelque soit le langage utilisé, la gestion des contrôles dépendants est toujours assez similaire. Une
partie concerne la consommation pure, une autre la mise en forme du contrôle.
La mise en forme dépend des capacités des contrôles, et nous la verrons au cas par cas, la consommation
est, elle, beaucoup plus générique. Dans cette première partie, nous allons nous concentrer sur les
fondamentaux de la consommation de données.
Pour pouvoir consommer des données encore faut-il en avoir. Les sources de données sont aussi
nombreuses que diverses, il peut indifféremment s’agir de données issues d’un SGBD ou extraites d’un
fichier, d’un flux de données d’acquisition, d’un mélange de tout cela….
Quoi qu’il en soit, pour qu’un contrôle puisse consommer des données, il faut pouvoir les lui fournir. On
distingue globalement deux types de contrôles.
• Les contrôles liés (ou dépendants) : On utilise des propriétés du contrôle pour le lier à un fournisseur
de données, cela permettant de s’affranchir d’un certain nombre de tâches de gestion. La liaison est
stricte et une modification de la source engendre une action sur le contrôle.
• Les contrôles indépendants : Ils peuvent être temporairement consommateurs de données, mais cette
consommation est gérée par le code et non par une quelconque liaison.
Les contrôles liés sont beaucoup moins lourds à gérer, mais ils imposent de pouvoir fournir une source
de type connue afin de procéder à la liaison. C’est sur ce type de contrôles que nous allons nous pencher.

Liaisons
Un contrôle lié possède toujours une source de nature connue afin de pouvoir créer la liaison.

Contrôles à liaison simple


En général, une propriété d’un contrôle est liée à une source. Un contrôle consommateur à liaison simple
ne consomme qu’une donnée à la fois. C’est une action sur la source qui déclenche une modification sur la
propriété liée. La plupart des contrôles de l’IDE Microsoft Visual Studio supportent la liaison simple.
Bien qu’il soit possible d’utiliser les propriétés de liaison pour le contrôle, il est tout à fait envisageable
de gérer celle-ci entièrement par le code

7
Contrôles à liaisons complexe
Il s’agit là des contrôles pouvant être liés à plus d’une donnée à la fois. Le contrôle Datagrid est le plus
connu d’entre eux, mais d’autres (comme les zones de liste) gèrent aussi ce type de liaison. Les contrôles à
liaison complexe induisent un certain nombre de concepts supplémentaires.
La source doit pouvoir fournir un jeu de données connexes (relationnelle, hiérarchique) cohérent. Dans
DotNet, seuls les objets exposant ou héritant de l’interface IList peuvent être fournisseurs d’une liaison
complexe. Il peut s’agir d’un tableau, d’un Dataset, d’une collection….

Liaison directionnelle
Un contrôle dépendant n’est pas nécessairement consommateur. Il peut être aussi fournisseur de données
ou consommateur / fournisseur. Une liaison n’est jamais directionnelle en elle-même, c’est l’utilisation du
contrôle ou la nature de la source qui induisent cette direction. Comme l’ensemble de ces concepts peut être
difficile à saisir, nous allons étudier un petit exemple simple avant de les détailler.
Public MonTab() As String

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles MyBase.Load
Dim compteur As Long
MonTab = New String() {"un", "deux", "trois"}
Me.ComboBox1.DataSource = MonTab
Me.Refresh()
End Sub

Private Sub ComboBox1_KeyPress(ByVal sender As Object, ByVal e As


System.Windows.Forms.KeyPressEventArgs) Handles ComboBox1.KeyPress
If e.KeyChar = Microsoft.VisualBasic.ChrW(13) Then
ReDim Preserve MonTab(MonTab.GetLength(0))
MonTab(MonTab.GetUpperBound(0)) = Me.ComboBox1.Text
Me.ComboBox1.DataSource = MonTab
Me.ComboBox1.Refresh()
End If
End Sub
Ce type de code permet de bien comprendre la dualité consommateur / fournisseur. Je vais être un peu
abstrait, et imaginer ma liste déroulante comme une zone de texte et une zone de liste.
Au début (dans le load de la forme) ma zone de liste est consommatrice des données du tableau MonTab
qui est donc le fournisseur. Si je saisis quelque chose dans la zone de texte et que j’appuie sur Entrée, je vais
ajouter l’élément au tableau. On pourrait croire alors que la zone de texte de ma liste déroulante est
fournisseur. Pour pouvoir s’entendre sur la terminologie, nous allons expliciter ce que ses termes sous-
tendent.

Fournisseurs
A l’origine, un fournisseur de données était uniquement un schéma défini dans un SGBD. Avec
l’arrivée des ‘couches programmables’ capables de diversifier les sources de données accessibles et a fortiori
avec le XML, la notion de fournisseur de données a beaucoup évoluée. Globalement on peut dire qu’il y a
une source de données externe au programme, un intermédiaire qui prend le rôle de consommateur /
fournisseur, et les consommateurs finaux.

8
Consommateur
Le terme de consommateur est un peu abusif, dans le sens qu’il ne les consomme pas mais les expose.
De fait la liaison consommateur / fournisseur stricte n’existe qu’au sein de l’application. Elle à lieu entre les
objets qui ont récupéré les données et les contrôles dépendants. On parlera donc plutôt de liaison. Bien qu’un
fournisseur puisse être de nature diverse, nous allons dans ce cours nous concentrer sur les objets ADO.NET

ADO.NET
Dans DotNet, le modèle d’accès aux données est ADO.NET. Je vous invite fortement à lire mon cours
précédent sur le sujet si vous ne le maîtrisez pas encore, car je ne reviendrai pas dans les détails ici.
ADO.NET est par nature un modèle déconnecté. C’est à dire que l’on cherche toujours à réduire au
maximum le temps d’ouverture des connexions. Par extension je pourrais même dire que les contrôles
dépendants sont toujours déconnectés car le seul objet connecté d’ADO.NET le DataReader est en lecture
seule.

La connexion
Quelle que soit la source de données, il faut pouvoir aller extraire les données qu’elle contient. Pour
cela, ADO.NET utilise des composants logiciels regroupés dans ce qu’on appelle le fournisseur managé. La
première étape consiste donc toujours à établir une connexion entre le fournisseur de données et votre
application. L’ouverture d’une connexion n’est jamais complexe dès lors qu’on connaît l’emplacement de la
source, et le nom du fournisseur.

Les commandes
Les commandes sont les objets qui manipulent les commandes SQL nécessaires pour extraire ou agir
sur la source de données. Les commandes peuvent être des ordres DDL (Data Definition Language) ou DML
(Data Manipulation Language). De manière générale, toute commande SQL valide peut être utilisée que ce
soit directement, par appel de vue ou de procédure stockée. Dans toute application consommatrice de
données extraites d’un SGBD, il y a manipulation de commandes.

Les objets consommateurs / fournisseurs


Ce sont eux que nous allons utiliser pour faire le lien entre nos contrôles et le SGBD. Dans ADO.NET
ils sont de deux sortes.
Le DataReader
Le DataReader est un objet connecté, c’est à dire qu’il travaille avec une connexion sur le SGBD
ouverte.
Il possède les caractéristiques suivantes :
¾ Lecture seule : il n’est pas possible de modifier les données extraites par un DataReader
¾ En avant seulement : Lecture des enregistrements un à un.
¾ Exclusif : La connexion utilisée ne peut pas l’être par d’autres objets
Le DataReader est assez spécifique dans son utilisation, il ne sert que pour un parcours unique d’un jeu
d’enregistrement.
On obtient un DataReader par appel de la méthode ExecuteReader d’un objet Command.
La syntaxe de la méthode est :
Public Function ExecuteReader( ByVal behavior As CommandBehavior) As …DataReader

9
Le paramètre behavior peut prendre les valeurs suivantes :
Nom de membre Valeur Description
Default 0 La requête peut retourner plusieurs jeux de résultats. Equivaut à la surcharge
ExecuteReader sans paramètre.
SingleResult 1
La requête retourne un jeu de résultat unique.
SchemaOnly 2 La requête retourne uniquement les informations de colonne et n'affecte pas l'état
de la base de données.
KeyInfo 4 La requête retourne des informations de colonne et de clé primaire. Elle est
exécutée sans verrouiller les lignes sélectionnées.
8 La requête retourne normalement une ligne unique. L'exécution de la requête
peut affecter l'état de la base de données. Certains fournisseurs de données .NET
Framework peuvent éventuellement utiliser ces informations pour optimiser les
SingleRow
performances de la commande.
Il est possible de spécifier SingleRow lorsque vous exécutez des requêtes qui
retournent plusieurs jeux de résultats. Dans ce cas, plusieurs jeux de résultats
sont toujours retournés, mais chaque jeu possède une ligne unique.
16 Fournit à DataReader un moyen de gérer les lignes qui contiennent des colonnes
renfermant des valeurs binaires élevées. Plutôt que de charger la ligne entière,
SequentialAccess permet à DataReader de charger les données en tant que flux.
Vous pouvez ensuite utiliser la méthode GetBytes ou GetChars afin de spécifier
un emplacement d'octet à partir duquel démarrer l'opération de lecture, ainsi
qu'une taille de mémoire tampon limitée pour les données retournées.
SequentialAccess Lorsque vous spécifiez SequentialAccess, vous êtes obligé de lire les colonnes
dans l'ordre où elles sont retournées, mais il n'est cependant pas nécessaire de lire
chaque colonne. Lorsque vous avez lu au-delà d'un emplacement du flux de
données retourné, les données situées jusqu'à cet emplacement ne peuvent plus
être lues à partir de DataReader. Si vous utilisez OleDbDataReader, vous pouvez
relire la valeur de la colonne en cours jusqu'à ce que vous lisiez au-delà de celle-
ci. Si vous utilisez SqlDataReader, une valeur de colonne peut être lue à une
seule reprise.
CloseConnection 32 Lorsque la commande est exécutée, l'objet Connection associé se ferme en même
temps que l'objet DataReader.
Un exemple typique de l’utilisation du DataReader est le remplissage d’une zone de liste déroulante
comme dans l’exemple ci-dessous :
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
Dim MaConn As New SqlClient.SqlConnection("packet size=4096;user
id=sa;data source=NOM-SZ71L7KTH17\TUTO;persist security
info=False;initial catalog=pubs;password=monpasse")
MaConn.Open()
Dim strSQL As String = "SELECT Author FROM Authors"
Dim MaComm As New SqlClient.SqlCommand(strSQL, MaConn)
MaComm.CommandType = CommandType.Text
Dim dtaRead As SqlClient.SqlDataReader = MaComm.ExecuteReader()
While dtaRead.Read
Me.ComboBox1.Items.Add(dtaRead.GetSqlString(0))
End While
dtaRead.Close()
End Sub
Il est toujours un peu aventureux de parler de contrôles dépendants dans le cas du DataReader. En effet
il s’agit plus souvent d’un remplissage unique. Principalement le DataReader sert à remplir les zones de liste.
Nous verrons toutefois qu’il peut y avoir une utilisation dans les structures maître / détails.

10
Le Dataset
La classe Dataset est riche et complexe, je ne vais donc pas revenir dessus en détail ici. Pour
schématiser, disons qu’elle permet d’avoir une base de données dans votre application. Un Dataset peut
contenir une ou plusieurs tables, éventuellement liées entre elles, ainsi qu’un certain nombre de contraintes.
Ce Dataset est modifiable, et peut transmettre ses modifications vers une source de données externe par
l’intermédiaire d’un DataAdapter. C’est aussi le DataAdapter qui sert au remplissage du Dataset. A l’échelle
de l’application, c’est le Dataset qui tient le rôle de fournisseur de données. Dans la suite de ce document
nous travaillerons sur un Dataset nommé dsNWind qui contient la base de donnée ‘Northwind’ exemple de
Microsoft.
Exposition des données
Cette notion est primordiale à saisir dans le cas qui nous intéresse. Un Dataset n’expose pas directement
ses tables aux contrôles dépendants.
En fait chaque table expose ses données au travers d’un ou plusieurs objets Dataview qui sont en fait des
images de la table ayant déjà une certaine mise en forme (filtre, tri, etc…). Comme la mise en forme d’un
Dataview peut changer au cours du temps, on les trouve souvent sous le terme de ‘vues dynamiques’. Un
objet DataTable peut exposer plusieurs Dataview, mais il en possède toujours un par défaut. C’est important
de voir que lorsqu’on utilise directement un objet DataTable dans une liaison de données, c’est en fait le
Dataview par défaut qui est lié.
Un objet DataTable est composé d’objets DataColumn qui représentent le schéma, et d’objets DataRow
qui contiennent les données. Un objet Dataview expose les données au travers de la collection
DataRowView.
Il n’y a pas de décrochement entre les vues et les objets sources. Si on modifie une valeur dans un
DataRowView, on modifie également cette valeur dans le DataRow ainsi exposé. Il est ainsi toujours
possible de faire le lien entre les données exposées et les données sources.
Un Dataset utilise aussi un objet DataviewManager qui permet de gérer les Dataview de plusieurs tables.
Comme nous allons beaucoup utiliser les Dataview, je vous ferai faire une petite visite un peu plus loin.
Manipulation des données
Le Dataset est intrinsèquement un objet déconnecté. Ceci présente des avantages et des inconvénients.
Le principal avantage dans le sujet qui nous intéresse est qu’il n’y a pas répercussion immédiate de la
modification des données vers la source de données. Ceci représente une couche de sécurité supplémentaire
dans la manipulation des données. Le deuxième avantage est la gestion des contraintes au niveau du Dataset.
Cela permet de valider les données sans devoir solliciter la source.
Evidemment il y a un inconvénient. Dans un environnement déconnecté, il peut y avoir dépréciation de
la qualité de l'information dans un contexte multi-utilisateurs. Plus j’ai rapatrié mes données depuis
longtemps, moins je suis sur de leurs réalités. Il faut donc mettre au point un système de rafraîchissement
permettant une pertinence acceptable. Il n’y a pas de règles définissant ceci car elles dépendent des
applications.

Les objets de liaison


C’est le cœur même du concept de dépendance. Un contrôle peut très bien consommer des données sans
être lié au fournisseur, ce qui demande alors une gestion par le code. Mais généralement il est plus
intéressant d’utiliser une liaison. Cela permet :
¾ Une automatisation du contrôle
¾ Une cohérence entre plusieurs contrôles connexes
Dans la programmation DotNet, il y a une hiérarchie du contrôle des liaisons.
• BindingContext ⇒ Regroupe les contrôles liés d’un même conteneur
• CurrencyManager ⇒ Regroupe les liaisons à une même source
• Binding ⇒ Concerne une liaison.
On peut schématiser en disant que le BindingContext de la feuille contient l’ensemble des
CurrencyManager qui regroupe les Binding d’une même source.
D’autres objets existent aussi pour certains cas spécifiques.

11
La classe Binding (liaison simple)
L’objet Binding représente une liaison entre une propriété d’un contrôle et une propriété d’un objet. Un
objet Binding comprend le nom de la propriété du contrôle, le nom de la source et éventuellement le membre
de cette source à lier.
Le constructeur est :
Public Sub New( ByVal propertyName As String, ByVal dataSource As Object, ByVal dataMember
As String)
PropertyName est le nom de la propriété du contrôle à lier.
DataSource est le nom de la source de l’objet à lier. Globalement il s’agit souvent ou d’un Dataview ou
d’un DataviewManager, mais tout objet implémentant IList est accessible.
DataMember identifie le nom de la propriété de l’objet à lier. On peut mettre une chaîne vide si l’objet
n’a qu’une propriété à lier, ce qui sous-entend qu’on utilise alors dataSource.ToString.
Propriétés
Vous noterez que l'ensemble de ses propriétés est en lecture seule. Ceci veut dire que l'affectation a
uniquement lieu à l'aide du constructeur.
BindingManagerBase
Renvoie le BindingManagerBase de la liaison. Nous y reviendrons un peu après
BindingMemberInfo
Renvoie le DataMember s'il est défini.
Control
Renvoie le contrôle auquel appartient la liaison. N'oubliez pas qu'un contrôle peut lier plusieurs de ses
propriétés, mais qu'une propriété ne peut être liée qu'une fois
DataSource
Renvoie la source de donnée de la liaison.
IsBinding
Indique si la liaison est active. Une liaison active est une liaison qui n'est pas suspendue, suspension qui
est déclenchée éventuellement par une action sur le BindingManagerBase.
PropertyName
Renvoie la propriété du contrôle mise en jeu par la liaison
Evènements
Les deux événements que nous allons voir sont très souvent utilisés.
Format
De la forme
Public Event Format As ConvertEventHandler
Se produit lorsqu'une valeur de la source est affectée au contrôle c'est-à-dire :
™ A la création de la liaison
™ Si l'enregistrement courant change
™ Si la source est modifiée
L'argument ConvertEventHandler renvoie deux propriétés
• DesiredType Î Type de la valeur
• Value Î valeur
Dans l'exemple suivant, je change le format d'affichage de la date dans la zone de texte.
Private Sub MaLiaison_Format(ByVal sender As Object, ByVal e As
System.Windows.Forms.ConvertEventArgs)
If e.DesiredType Is GetType(String) Then
e.Value = Format(e.Value, "dddd - d - MMMM")
End If
End Sub

12
Private Sub Form1_Activated(ByVal sender As Object, ByVal e As
System.EventArgs) Handles MyBase.Activated
Dim MaLiaison As Binding = New Binding("Text",
Me.DsNWind2.Commandes, "date commande")
AddHandler MaLiaison.Format, AddressOf MaLiaison_Format
Me.TextBox1.DataBindings.Add(MaLiaison)
End Sub

Parse
De la formePublic Event Parse As ConvertEventHandler
Se produit lorsque la valeur du contrôle est modifiée (pas du fait d'une modification de la source)
C'est l'événement inverse du précédent.

La classe BindingManagerBase
Je ne vais pas m'attarder sur cet objet car on utilise plutôt les classes qui héritent de celui-ci. Toutefois
glosons un peu. En soit, les contrôles dépendant à liaison simple n'apportent pas grand chose. L'intérêt réside
dans le fait de pouvoir naviguer derrière dans un jeu d'enregistrements plus ou moins complexes. La notion
d'enregistrement courant n'existe pas directement dans ADO.NET, encore que désigner un élément d'une
collection ne soit pas difficile. Pour que les contrôles dépendant d'une feuille aient un comportement
cohérent, il leur faut un contexte commun. C'est cette notion de contexte que les héritiers de
BindingManagerBase exposent. On obtient d'ailleurs l'objet BindingManagerBase au travers d'un objet
BindingContext qui porte bien son nom. En fait les héritiers sont de deux types. Si vous liez des contrôles à
une source de données vous obtenez un objet CurrencyManager, si vous liez une propriété d'un contrôle à
une propriété d'un autre objet, vous passez par un PropertyManager.
Nous allons détailler ici le plus couramment utilisé, le CurrencyManager

La classe CurrencyManager
Comme je vous le disais, cet objet sert à donner une cohérence à tous les contrôles ayant la même source
de données. Globalement, il simule la position courante d'un enregistrement. Pour ceux d'entre vous qui
arrivez de VB6, disons que c'est l'équivalent des méthodes MoveNext, MoveLast etc…
Le CurrencyManager gère une collection d’objet Binding. On l’utilise pour agir sur la source de données
et non sur les contrôles.
Propriétés
Bindings
Renvoie la collection des objets Binding du CurrencyManager. Lecture seule.
Count
Substitué. Lecture seule.
Comme je vous l’ai dit, un CurrencyManager induit forcément une source de données ayant plusieurs
objets (éléments). Cette propriété renvoie le nombre d’éléments.
Current
Substitué. Lecture seule.
Renvoie l’élément étant actuellement l’élément courant du CurrencyManager. Attention cette propriété
renvoie l’élément sous forme ‘Object’. Il vous appartient de faire le cast si nécessaire.

13
List
Renvoie l’objet IList (la liste des éléments) gérée par le CurrencyManager. Bien qu’ayant l’air inutile,
cette propriété permet souvent une programmation générique assez intéressante.
Par exemple, le code suivant permet de modifier les droits d’édition des contrôles liés à la table
messagers.
Private Sub cmdAllowEdit_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles cmdAllowEdit.Click
Dim MonCM As CurrencyManager =
CType(Me.BindingContext(Me.DsNWind2, "Messagers"), CurrencyManager)
Dim MonDv As DataView = CType(MonCM.List, DataView)
MonDv.AllowEdit = True
End Sub
Position
Définit ou renvoie l’index de l’élément en cours. C’est le navigateur en quelque sorte. Le code suivant
permet la navigation dans les éléments.
Private Sub cmdFirst_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles cmdFirst.Click
MonManager.Position = 0
Me.txtPosition.Text = MonManager.Position.ToString & "/" &
MonManager.Count.ToString & " enregistrements"
End Sub

Private Sub cmdLast_Click(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles cmdLast.Click
MonManager.Position = MonManager.Count - 1
Me.txtPosition.Text = MonManager.Position.ToString & "/" &
MonManager.Count.ToString & " enregistrements"
End Sub

Private Sub cmdPrevious_Click(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles cmdPrevious.Click
If MonManager.Position > 0 Then
MonManager.Position = MonManager.Position - 1
Me.txtPosition.Text = MonManager.Position.ToString & "/" &
MonManager.Count.ToString & " enregistrements"
End If
End Sub

Private Sub cmdNext_Click(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles cmdPrevious.Click
If MonManager.Position < MonManager.Count - 1 Then
MonManager.Position = MonManager.Position + 1
Me.txtPosition.Text = MonManager.Position.ToString & "/" &
MonManager.Count.ToString & " enregistrements"
End If
End Sub

Méthodes
AddNew
Permet l’ajout d’un élément à la liste sous-jacente. Attention, la nature de l’élément devra être la même
que celle des éléments existants. Si votre fournisseur est une DataTable, les éléments de la liste ne sont pas
des DataRows mais des DataRowView car la source d’une liaison à une DataTable est toujours un Dataview.
Refresh

14
Appelle un nouveau remplissage des contrôles dépendants. Cet appel explicite n’est utile que si la source
ne gère pas les notifications de modification (tableau par exemple).
RemoveAt
Enlève l’élément de la liste dont l’index est passé en paramètre.
ResumeBinding & SuspendBinding
Ces méthodes permettent la suspension et la reprise des liaisons gérées par le CurrencyManager. Le
concept de suspension de liaison est un peu particulier. Nous y reviendrons dans la partie sur la validation.
Evénements
CurrentChanged
Se produit quand l'élément lié change. C'est toujours le cas lors d'un changement de la position, mais
l'événement se déclenche aussi dans d'autres cas.
ItemChanged
Cet élément est un peu plus difficile à saisir. Il se produit globalement dans trois cas
¾ Lors d'une suspension ou de la reprise d'une liaison
¾ Lors de la modification de la nature d'un objet lié.
¾ Lors de l'appel de la méthode AddNew du CurrencyManager.
MetadataChanged.
Se produit si les méta données de la source changent. Par exemple l'ajout d'une colonne à la table liée
déclenche cet événement
PositionChanged
Tel que son nom l'indique….

La classe BindingContext
Cet objet est aussi un regroupement de liaisons mais vu du côté des contrôles. Comme nous l'avons vu
précédemment, une source de données regroupe toutes ces liaisons dans un CurrencyManager (ou dans un
PropertyManager). Les contrôles d'un formulaire ne sont pas tous liés forcément à la même source, il peut
donc exister plus d'un CurrencyManager. L'objet BindingContext regroupe tous les objets de liaison des
contrôles d'un container. Par défaut ce container est la feuille. Néanmoins n'importe quel contrôle conteneur
peut exposer un BindingContext. C'est très souvent intéressant de déclarer un BindingContext pour les
contrôles de type GroupBox regroupant des contrôles connexes. Nous n'allons pas aller beaucoup plus loin
sur cet objet, puisqu’en tant que tel, on ne l'utilise principalement que pour accéder aux CurrencyManager
qu'il contient.

Validation
Le concept de la validation est aussi vieux que les interfaces utilisateurs. Les contrôles dépendants
peuvent devenir source de données lorsque l'utilisateur saisit des données. Les sources de données possèdent
des contraintes et des règles afin que ces données soient cohérentes avec le schéma de la source. L'opération
qui consiste à vérifier le respect des contraintes s'appelle 'validation'.

Elle se subdivise généralement en trois catégories.

15
Les restrictions
Les restrictions consistent à utiliser des contrôles particuliers (typés) ou à restreindre les capacités du
contrôle afin de diminuer la chance d'une saisie erronée. Les restrictions sont souvent insuffisantes et doivent
être comprises dans un schéma plus général de validation.
Si le champ qui doit être saisi est une date, il y a beaucoup moins d'erreurs possibles si mon interface
utilise un DateTimePicker plutôt qu'une zone de texte. Néanmoins cela ne suffit pas. Il est tout à fait possible
que l'utilisateur oublie de saisir une valeur, et seule la validation pourra détecter l'erreur.
Les contrôles possèdent souvent des propriétés intrinsèques de restriction (min, max, maxlength….)
Mais il arrive qu'il soit rentable d'ajouter du code de restriction. L'exemple suivant force la saisie d'un entier
dans une zone de texte.
Private Sub txtQuantite_KeyPress(ByVal sender As Object, ByVal e As
System.Windows.Forms.KeyPressEventArgs) Handles txtMini.KeyPress
Dim c As Char
c = e.KeyChar
If Not (Char.IsDigit(c) Or Char.IsControl(c)) Then
e.Handled = True
End If
End Sub

La validation au niveau du contrôle


La validation de niveau contrôle est presque toujours événementielle. On la place principalement sur les
événements Validate ou LostFocus. VB.NET permet de contrôler la pertinence d’une validation avec des
propriétés telle que Modified ou DataChanged. En effet quand celle-ci est vraie, la valeur dans le contrôle
diffère de la valeur de l’enregistrement en cours et le code de validation doit s’exécuter.

La validation au niveau du formulaire


Beaucoup plus fréquente, mais potentiellement piégeuse, la validation au niveau formulaire consiste à
vérifier tous les contrôles liés en même temps, à un moment jugé pertinent. C’est très souvent la pertinence
du moment qui pose problème. En effet, pour savoir quand il convient de contrôler le formulaire, il faut
savoir quand les données des contrôles liés sont effectivement envoyées vers la source sous-jacente.

Contraintes complexes
Les sources de données complexes du genre Dataset intègrent un autre type de contrainte que les
contraintes de types. Il s’agit des contraintes d’intégrité, souvent nommées à tort, contraintes relationnelles.
Les contraintes d’intégrité ne portent pas sur le type de la donnée mais plutôt sur la valeur ou
l’existence de celle-ci.
Unicité
La contrainte d’unicité indique qu’une donnée ne peut exister qu’une fois dans un champ. C’est le cas
des clés primaires des tables. Certains champs éliminent cette contrainte en gérant eux-mêmes l’attribution
des valeurs, on les appelle auto-incrémentées ou TimeStamp. Dans d’autre cas il vous appartiendra de
vérifier cette unicité.
Relationnelle
Elles demandent qu’un champ d’une autre table ou parfois de la même table possède une valeur valide.
Un choix du type de contrôle peut grandement aider l’utilisateur dans ce cas. Elles peuvent toutefois être
assez dures à gérer. Pour autoriser cette gestion, on est parfois obligé de suspendre temporairement la liaison
pour éviter de bloquer l’utilisateur. On utilise alors les méthodes ResumeBinding & SuspendBinding de
l’objet CurrencyManager

16
Existence
Il s’agit juste de champs devant avoir obligatoirement une valeur (non NULL).

Validation différée
Il arrive que certains développeurs laissent la source de données gérer la validation. C’est-à-dire que
pour alléger l’exécution du code validation, ils l’intègrent à la gestion d’erreur de la source de données. Pour
ma part c’est un choix que je refuse car il oblige à gérer un mode ‘transactionnel’ dans votre processus, afin
de veiller à ce qu’il n’y ait pas acceptation partielle des modifications d’un enregistrement ayant engendré
des erreurs.

Création Vs Exécution
Il s’agit là d’un problème classique. Il est souvent plus rapide et plus commode de définir les liaisons (et
d’autres propriétés d’ailleurs) lors de la conception. Cela diminue le codage et on profite pleinement des
fonctionnalités intégrées. Malheureusement celles-ci ne suffisent pas toujours à répondre aux besoins. On
tend alors à les compléter par le code. Il arrive un moment ou le code lutte contre les fonctionnalités
intégrées.
Par exemple, on retrouve fréquemment ce problème avec les ensembles de données relationnels. La
liaison de données tend souvent à gêner la manipulation des enregistrements soumis à des contraintes en
cascades.

Comprendre les expressions


Une expression, dans un groupe de données, consiste à ajouter des champs dans le groupe, les valeurs de
ceux-ci étant calculées par une expression. Par exemple :
Me.DsNWind2.Produits.Columns.Add("Somme Vendu",
System.Type.GetType("System.Decimal"), "[Prix unitaire] * [unités
commandées]")
Il s’agit là d’une expression de calcul. Il est aussi possible d’aller ainsi récupérer des valeurs dans les
tables ascendantes / descendantes.
Me.DsNWind2.Clients.Columns.Add("Dernière Commande",
System.Type.GetType("System.DateTime"),
"Max(child(Clients_Commandes).[Date commande])")
Vous noterez qu’avec les contrôles Windows.Forms on agit ainsi sur la source de données et non sur les
contrôles. Il est par contre dès lors possible de créer une liaison entre la colonne expression et un contrôle.
Habituellement on verrouille le contrôle pour l’utilisateur puisqu’il s’agit normalement de valeurs non
saisissables.

Notion de relation
Une relation est le lien logique qui existe entre une ou plusieurs tables d’un ensemble de données. Pour
être fonctionnelle, une relation doit être un objet Relation de l’objet Dataset. Une fois définie, on peut utiliser
cette relation pour naviguer entre les tables. Il est possible de créer des chemins de navigation extrêmement
complexes. Les relations se gèrent différemment selon leur nature.

Relation un à un
A un enregistrement parent correspond un enregistrement enfant. Ces relations sont souvent regroupées
avant l’affichage. Elles sont relativement rares car généralement toutes les données de ce type sont plutôt
regroupées dans la même table.

17
Relation un à plusieurs
C’est le cas le plus fréquent. Toutes les relations de notre exemple sont de ce type. Généralement elles
induisent un affichage de type maître / détails mais on utilise parfois un agrégat qui conduit à une logique de
relation un à un.

Relation plusieurs à plusieurs


Ce sont des relations ou parents et enfants peuvent être multiples. Par exemple un auteur peut avoir écrit
plusieurs livres et un livre peut avoir plusieurs auteurs. On affiche rarement ce type de relation complexe.
Nous verrons dans l’étude des formulaires maître / détails comment gérer ce cas.

Intégrité référentielle
Les relations entraînent une gestion de l’intégrité. Chaque relation définie des règles de comportement
lorsqu’il y a suppression ou modification d’un enregistrement lié. Vous devez impérativement connaître ces
règles car votre code va en dépendre. Ainsi la suppression d’un enregistrement parent devrait engendrer la
suppression des enregistrements enfants. Si votre relation n’est pas autorisée à traiter directement ce cas,
vous devrez soumettre vos requêtes dans le bon ordre

Dataview
Généralités
L’objet Dataview est un objet ‘d’affichage’. Entendez par-là qu’il s’agit d’une couche intermédiaire
entre un objet DataTable et les contrôles dépendant. Un Dataview gère le tri et le filtrage d’un objet
DataTable.
Un Dataview ne peut pas :
¾ Ajouter ou supprimer une colonne existant dans la table
¾ Afficher des données relationnelles
Il s’agit d’un objet dynamique, vous pouvez donc changer ces règles de tri et / ou filtre à l’exécution. Un
objet DataTable peut avoir plusieurs objets Dataview, il en possède toujours au moins un, qu’on peut
atteindre à l’aide de DefaultView. L’objet Dataview est étroitement lié avec son DataTable. Toute action sur
les données exposées par un Dataview se répercute sur les données de la DataTable. Les lignes d’un
Dataview sont des objets Datarowview, les colonnes restent des DataColumn puisque toutes les colonnes
d’une table sont toujours présentent dans le Dataview de cette table.
Un objet Dataview permet aussi de restreindre l’accès aux modifications de la table. Comme il est
possible de créer plusieurs Dataview pour une même table, il y a là une possibilité de contrôler finement les
droits des utilisateurs.

Propriétés
AllowDelete, AllowEdit & AllowNew
Ces propriétés autorisent ou interdisent la manipulation de la table liée. Lorsqu'on tente une opération
dont le Allow correspondant est faux, une exception est levée. Dans le cas d'un datagrid, l'édition est bloquée
ce qui évite de lever une exception.

La méthode AllowEdit est sans effet sur un contrôle à liaison simple.


Private WithEvents GestionPosition As CurrencyManager

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles MyBase.Load

Dim Mabase As New DsNWind

18
Call RempliDataset(Mabase)
Mabase.Messagers.DefaultView.AllowEdit = False
Me.txtNum.DataBindings.Add("text", Mabase.Messagers.DefaultView,
"N° messager")
Me.txtNom.DataBindings.Add("text", Mabase.Messagers.DefaultView,
"nom du messager")
Me.txtTel.DataBindings.Add("text", Mabase.Messagers.DefaultView,
"téléphone")
GestionPosition =
CType(Me.BindingContext(Mabase.Messagers.DefaultView), CurrencyManager)
Me.DataGrid1.DataSource = Mabase.Messagers.DefaultView
End Sub

Private Sub cmdFirst_Click(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles cmdFirst.Click
GestionPosition.Position = 0
End Sub

Private Sub cmdLast_Click(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles cmdLast.Click
GestionPosition.Position = GestionPosition.Count - 1
End Sub

Private Sub cmdPrev_Click(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles cmdPrev.Click
If GestionPosition.Position > 0 Then
GestionPosition.Position = GestionPosition.Position - 1
End If
End Sub

Private Sub cmdNext_Click(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles cmdNext.Click
If GestionPosition.Position < GestionPosition.Count - 1 Then
GestionPosition.Position = GestionPosition.Position + 1
End If
End Sub
Modifiez le texte d'une des zones puis naviguez. La donnée est modifiée et aucune exception n'est levée.

Count
Renvoie le nombre d'enregistrements visibles après l'application des filtres (filtre de ligne et des états de
ligne).

DataViewManager
Renvoie le DataViewManager auquel appartient le DataView. Pour mémoire le DataViewManager gère
toutes les vues d'un Dataset.

Item
Item renvoie un objet DatarowView. Comme je vous l'ai dit, il ne s'agit que d'une représentation d'un
DataRow. Donc par le biais de la propriété Item on peut atteindre le DataRow sous-jacent réel.
Dim MaVue As DataView
MaVue = Me.txtNum.DataBindings.Item("text").DataSource
Dim Maligne As DataRow
Maligne = MaVue.Item(GestionPosition.Position).Row()

19
RowFilter
Attend une chaîne de filtre qui s'applique aux lignes de la table. La chaîne de filtre s'écrit comme une
clause WHERE SQL (si vous ne connaissez pas SQL, vous voilà bien avancé).
La chaîne est donc de la forme Nom du champ – opérateur – critères. Je vous donne quelques exemples :
Mavue.RowFilter = "[N° commande] > 100"
Mavue.RowFilter = "[pays livraison]= 'france'"
Mavue.RowFilter = "[code client] like 'b*'"
Mavue.RowFilter = "[date commande] > #10/01/94# and [date commande] <
#10/10/94#"
Les crochets autour du nom du champs sont obligatoires si celui-ci contient un espace ou un caractère
réservé.

RowStateFilter
Indique un filtre selon l'état des lignes. Pour mémoire c'est une des valeurs suivantes
Membre Valeur Description
None 0 Aucune.
Unchanged 2 Ligne non modifiée.
Added 4 Nouvelle ligne.
Deleted 8 Ligne supprimée.
ModifiedCurrent 16 Version actuelle, qui est une version modifiée des données d'origine
CurrentRows 22 Lignes en cours, y compris les lignes non modifiées, les nouvelles
lignes et les lignes modifiées.
ModifiedOriginal 32 Version d'origine (même si elle a été modifiée et est disponible en tant
que ModifiedCurrent).
OriginalRows 42 Lignes d'origine, y compris les lignes non modifiées et les lignes
supprimées.

Sort
Définit ou renvoie la chaîne de tri utilisée par le DataView. La chaîne se compose du nom du champ
suivi de ASC (pour croissant) ou DESC pour décroissant. Lorsqu'il y a plusieurs champs ils sont séparés par
une virgule est triés prioritairement de la gauche vers la droite.
Mavue.Sort = "[N° commande] ASC, [pays livraison] DESC "

Table
Renvoie ou définit la table source de la vue.

Méthodes
AddNew
Ajoute une ligne au DataView

Delete
Supprime la ligne dont l'index est passé en paramètre. Attention, il s'agit de l'index de DatarowView
dans Dataview et non de l'index de la ligne dans la table. Une ligne supprimée par cette méthode change son
état à 'Deleted' mais n'est pas définitivement supprimée avant un appel de la méthode AcceptChanges.

20
Find
Renvoie l'index de la ligne correspondant aux critères passés en paramètres. Les critères sont passés sous
forme d'un tableau d'objets. Les critères correspondent toujours à des clés de tri c'est à dire aux champs
définis dans la propriété Sort. S'il n'y a pas de correspondance la méthode renvoie –1.
Dim MaVue As DataView
MaVue = Me.DataGrid1.DataSource
MaVue.Sort = "[N° commande] ASC"
MessageBox.Show(MaVue.Find(New Object() {10248}).ToString())

FindRows
Similaire à la méthode précédente mais renvoie un tableau d'objets DatarowView correspondant à tous
les enregistrements correspondant aux critères.

GetEnumerator
Renvoie un énumérateur sur le DataView. L'obtention d'un tel énumérateur donne accès à des méthodes
de parcours rapide vers l'avant de la collection.

Discussion autour du concept DatarowView


Comme un DataRowView est toujours lié à un DataRow, on est en droit de se demander l’utilité de la
chose. En fait cela vient de plusieurs faits.
Une ligne possède plusieurs états potentiels (ajoutés, supprimés, modifiés, normal) et plusieurs valeurs
(originales, courantes, proposé…). Une ligne d’affichage par contre ne peut avoir qu’une apparence. Un
DataRowView est donc un objet DataRow dont les valeurs affichées dépendent des propriétés de filtre du
Dataview parent.
De plus, le filtrage supprime des lignes de l’affichage. Il n’y a donc plus égalité stricto sensu entre la
collection Datarow du DataTable et DatarowView du Dataview. Enfin le tri modifie l’ordre des lignes dans
la collection.
Un DatarowView pointe toujours vers un Datarow. Lorsqu’on modifie un DatarowView, on modifie le
Datarow lié. Par contre il faut se méfier de l’index. On ne peut pas atteindre un Datarow en utilisant celui du
DatarowView, sauf miracle.
De manière générale, lorsqu’on travaille en liaison avec un Dataset, on utilise que des Dataview et des
DatarowView.

J'ai perdu la vue !


Comme une table peut exposer plusieurs vues, et comme on peut se lier sur une table plutôt que sur une
vue, du moins en apparence, il arrive qu’on ne sache plus qu’elle vue manipuler pour contrôler la liaison.
Plus le schéma du Dataset est complexe et plus il est facile de s’y perdre. Nous allons donc essayer de
comprendre ici comment tout cela fonctionne.
J’ajoute à ma feuille une connexion, un DataAdapter (nous n’allons utiliser que la table commande) et
un contrôle Datagrid.
J’utilise alors le code suivant :
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
Dim MaBase As New DataSet
Me.OleDbDataAdapter1.MissingSchemaAction =
MissingSchemaAction.AddWithKey
Me.OleDbDataAdapter1.Fill(MaBase)
Me.DataGrid1.DataSource = MaBase.Tables(0)
Dim MaVue As New DataView(MaBase.Tables(0))
MaVue.AllowEdit = False
End Sub

21
Et vous constaterez que les droits d’édition ne sont pas bloqués.
Pourtant je suis sur que mon contrôle est bien lié à une vue puisqu’il ne peut être lié qu’à une vue (dans
le cas d’un dataset). Tant pis, je me dis que je vais employer le DataviewManager de mon Dataset. Et là je
me rends compte qu’on ne récupère pas d’objet Dataview par le biais du DataviewManager mais juste des
DataviewSettings c’est à dire des paramétrages de Dataview.
Après tout je n’ai qu’à me connecter à ma vue.
Dim MaBase As New DataSet
Me.OleDbDataAdapter1.MissingSchemaAction = MissingSchemaAction.AddWithKey
Me.OleDbDataAdapter1.Fill(MaBase)
Dim MaVue As New DataView(MaBase.Tables(0))
Me.DataGrid1.DataSource = MaVue
MaVue.Sort = "[N° commande] ASC"
MaVue.AllowEdit = False
Là ça fonctionne. Néanmoins poursuivons, si j’ajoute :
MessageBox.Show(MaBase.DefaultViewManager.DataViewSettings.Count.ToString
)
MessageBox.Show(MaBase.DefaultViewManager.DataViewSettings(0).Sort.ToStri
ng)
J’obtiens bien 1 pour le nombre de vues, mais je ne récupère pas la chaîne de tri que j’ai spécifié.
J’aurais d’ailleurs le même phénomène si je faisais :
MessageBox.Show(MaBase.Tables(0).DefaultView.Sort.ToString)
Comprenez-vous le problème ?
Chaque table possède une vue par défaut que l’on peut atteindre par la propriété DefaultView de l’objet
DataTable. Le DataviewManager gère l’ensemble de ces vues par défaut. Si j’ajoute une vue à une table, elle
existe mais n’entre pas dans les vues par défaut. Il n’y a pas de collection pour les vues attachées à une table.
Il convient donc de faire attention à ne pas mélanger la gestion par le DataviewManager et la gestion des
vues gérées par vous.
Mon code pourrait donc être
Dim MaBase As New DataSet
Me.OleDbDataAdapter1.MissingSchemaAction = MissingSchemaAction.AddWithKey
Me.OleDbDataAdapter1.Fill(MaBase)
Dim MaVue As DataView
MaVue = MaBase.Tables(0).DefaultView
Me.DataGrid1.DataSource = MaVue
MaVue.AllowEdit = False
MaVue.Sort = "[N° commande] ASC"
Méfiez-vous de l’objet DataviewManager. Il est à mon avis plus facilement une source d’ennui qu’une
aide.

Tableau, liste et liste typée


Le Dataview n’est pas le seul fournisseur de données, même si c’est le plus fréquent. En fait la liaison
est possible pour toutes les classes implémentant une interface IList, IBindingList, IListSource.
Entrent dans cette catégorie les tableaux, les listes, certaines collections et toutes les classes que vous
créez en implémentant une de ces interfaces. Nous avons déjà vu un exemple de code ou un tableau est la
source d’une zone de liste déroulante.
Je ne vais pas m’étendre sur ce sujet qui concerne plus la notion de fournisseur que de contrôle
dépendant. Néanmoins nous allons voir un petit exemple.
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
Me.ListBox1.DataSource = TransList("clients", "société")
End Sub

22
Private Function TransList(ByVal NomTable As String, ByVal NomChamp
As String) As ArrayList
Dim Maliste As New ArrayList
Dim MaConn As New System.Data.OleDb.OleDbConnection("Provider =
Microsoft.Jet.OLEDB.4.0;User ID=Admin;Data
Source=D:\user\tutos\nwind.mdb;")
Dim MaCommand As New System.Data.OleDb.OleDbCommand("SELECT " &
NomChamp & " FROM " & NomTable, MaConn)
MaConn.Open()
Dim Lecteur As System.Data.OleDb.OleDbDataReader =
MaCommand.ExecuteReader(CommandBehavior.CloseConnection)
While Lecteur.Read
Maliste.Add(Lecteur.GetValue(0))
End While
Return Maliste
End Function
En soit le codage est peu finaud, et sans utilité dans une pensée Windows.Forms client / serveur.
Il est aussi possible de créer une liste de structures, celle-ci pourra être liée à un DataGrid.

23
Contrôles dépendants
Dans cette partie nous n'allons pas procéder comme d'habitude. En effet, une énumération des propriétés
méthodes serait fastidieuse à lire et assez vaine puisque de nombreuses propriétés / méthodes n'ont rien à voir
avec la liaison de données. Donc nous allons plutôt discuter autour d'exemples plus ou moins classiques
d'utilisation des contrôles dépendants.
Il est indispensable de parler tout d'abord du choix des contrôles à utiliser. En théorie, on utilise des
contrôles inaccessibles ou verrouillés pour les données en lecture seule. On essaye aussi toujours d'utiliser
prioritairement les contrôles qui peuvent orienter l'utilisateur dans sa saisie. Enfin, on essaye de regrouper les
contrôles connexes dans des conteneurs pour avoir une programmation plus claire.

Propriétés dépendantes
Les contrôles consommateur de liaison simple, lient donc une de leurs propriétés. Ils ne peuvent pas lier
n'importe laquelle. Les propriétés qu’un contrôle expose à la liaison se retrouvent dans la propriété
DataBindings du contrôle. Vous noterez qu’on trouve des propriétés standards (Text, Value, Tag, etc…) et la
possibilité de lier d’autres propriétés dans la sous-collection ‘avancées…’.
La seule contrainte réelle consiste à vérifier l’égalité de type ou tout au moins l’acceptation du type.
Néanmoins le bon sens doit aussi jouer son rôle. Si vous devez appliquer un formatage particulier à vos
données, il faut chercher un contrôle pouvant appliquer ce formatage soit automatiquement soit au prix d’un
minimum de code. Ayez toujours à l’esprit qu’il y a un moment ou il est plus rentable de traiter tout le
fonctionnement par le code.

Les liaisons simples


Je vous rappelle qu'une liaison simple met en jeu une propriété d'un contrôle avec une propriété d'un
objet ou un champ d'un enregistrement. L'arrivée des contrôles grille de plus en plus évolués tend à faire
disparaître l'utilisation des contrôles plus simples comme consommateurs, ce qui dans le sens de la saisie
n'est pas toujours un avantage. Il est cependant évident que les contrôles de type grille présentent de
nombreux avantages.

Construire les liaisons à la création


Dans cet exemple nous allons construire complètement un formulaire d’affichage enregistrement par
enregistrement en utilisant au maximum l’IDE.
Je vais utiliser le schéma donné en avant propos. Les objets Dataview utilisés (dtv….) sont liés aux
tables du composant DsNwind2 par leurs propriétés Table. Dans le cadre de cet exemple je ne vais travailler
qu’avec la table ‘Employés’, ma source sera donc le composant Dataview ‘dtvEmployes’.
Je dispose d’abord mes contrôles afin d’obtenir le formulaire suivant :

24
Maintenant je vais créer mes liaisons directement dans les propriétés du contrôle, à l’aide de la propriété
DataBindings

Pour l’instant je ne lie pas la zone de texte Hiérarchie ni le PictureBox de la photo.


Je tape ensuite le code suivant :
Private WithEvents MonCm As CurrencyManager
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
'Module1.RempliDataset()
Me.dtaCategories.Fill(Me.DsNWind2.Catégories)
Me.dtaClients.Fill(Me.DsNWind2.Clients)
Me.dtaEmployes.Fill(Me.DsNWind2.Employés)
Me.dtaFournisseurs.Fill(Me.DsNWind2.Fournisseurs)

25
Me.dtaMessagers.Fill(Me.DsNWind2.Messagers)
Me.dtaProduits.Fill(Me.DsNWind2.Produits)
Me.dtaCommandes.Fill(Me.DsNWind2.Commandes)
Me.dtaDetailCommande.Fill(Me.DsNWind2.Détails_commandes)
Me.Refresh()
MonCm = CType(Me.BindingContext(Me.dtvEmployes), CurrencyManager)
Me.lblPosition.Text = MonCm.Position + 1 & "/" & MonCm.Count & "
enregistrements"

End Sub

Private Sub cmdFirst_Click(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles cmdFirst.Click
MonCm.Position = 0
Me.lblPosition.Text = "1/" & MonCm.Count & " enregistrements"
End Sub

Private Sub cmdPrev_Click(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles cmdPrev.Click
If MonCm.Position > 0 Then
MonCm.Position = MonCm.Position - 1
Me.lblPosition.Text = MonCm.Position + 1 & "/" & MonCm.Count
& " enregistrements"
End If
End Sub

Private Sub cmdNext_Click(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles cmdNext.Click
If MonCm.Position < MonCm.Count - 1 Then
MonCm.Position = MonCm.Position + 1
Me.lblPosition.Text = MonCm.Position + 1 & "/" & MonCm.Count
& " enregistrements"
End If
End Sub

Private Sub cmdLast_Click(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles cmdLast.Click
MonCm.Position = 0
Me.lblPosition.Text = MonCm.Count & "/" & MonCm.Count & "
enregistrements"
End Sub
Et à l’exécution j’obtiens :

26
J’ai pour l’instant réalisé mon formulaire avec un minimum de code.
Quelques remarques
Cette interface n’est pas bien construite. Sur un plan du design, il est bien plus cohérent de faire deux
onglets séparant nettement les données personnelles des données professionnelles, ce qui permet le cas
échéant de les sécuriser séparément. De même, et nous y reviendrons plus loin, il est plus intéressant de créer
un composant de navigation indépendant.
Sur le plan de la manipulation des données c’est pire. En toute rigueur, on ne récupère pas les champs
longs de la même manière que les autres. Dans mon remplissage du Dataset, j’ai rapatrié toutes les données
sans me poser de question. Généralement, on ne récupère les champs longs que lorsqu’ils sont nécessaires.
Pour cela on crée des tables supplémentaires ou on utilise une récupération à la demande.
Ajouter la photo
Je n’ai pas ajouté la photo à la création. J’aurais pu pourtant utiliser le DataBindings avancé pour mettre
en relation le champ Photo et la propriété image du PictureBox. Cela n’aurait évidemment pas fonctionné
car une photo stockée dans une base de données est sous la forme d’un objet binaire (tableau de byte). Or il
n’est pas possible pour un PictureBox de faire la conversion automatiquement.
De plus il faut gérer le cas ou là photo n’existe pas, et le PictureBox va déclencher une Erreur sur un
champ NULL. Je vais donc ajouter une procédure à part pour gérer la photo.
Private Sub AffichePhoto()
Dim MaLigne As DsNWind.EmployésRow
MaLigne = Me.dtvEmployes(MonCm.Position).Row
If Not MaLigne.IsPhotoNull Then
Dim Flux As New System.IO.MemoryStream(MaLigne.Photo)
Try
Dim bmp As New Bitmap(Flux)
Me.pctPhoto.Image = bmp
Catch Ex As Exception
Me.pctPhoto.Text = "photo non disponible"

27
End Try
End If
End Sub

Manipuler la hiérarchie.
Le champ « Rend compte à » contient le numéro d’employé du supérieur hiérarchique. En soit
l’information n’est pas parlante. Juste avec la construction de liaison de l’IDE je ne peux pas faire grand
chose de mieux en l’état. Je pourrais évidemment créer la liaison par le code, ce qui me permettrait d’utiliser
une relation dans la propriété DataMember de ma liaison. Du genre
Me.txtHierarchie.DataBindings.Add("text", Me.dtvEmployes,
"Employés_Employés.Nom")
Mais je peux aussi faire le choix de remplacer la zone de texte par une zone de liste déroulante si mon
formulaire doit permettre les saisies ou les modifications.
Il y a enfin une solution plus propre par le code. En effet le simple affichage du nom peut être insuffisant
en précision, puisque deux homonymes ne pourront être distingués. Je peux alors ne pas lier mon contrôle et
appeler du code comme dans le cas de ma fonction. Ma zone de texte peut alors affichée Nom et prénom par
exemple.
Dim MaLigne, MonParent As DsNWind.EmployésRow
MaLigne = Me.dtvEmployes(MonCm.Position).Row
If MaLigne.GetParentRows("Employés_Employés").GetLength(0) > 0 Then
MonParent = MaLigne.GetParentRow("Employés_Employés")
Me.txtHierarchie.Text = MonParent.Nom & " " & MonParent.Prénom
Else
Me.txtHierarchie.Text = ""
End If

Création d’un composant de navigation


Pour simplifier nos problèmes de navigation, il serait judicieux de créer un composant gérant la
navigation dans les enregistrements. Ce que nous allons faire ici.
Il y aurait deux approches possibles :
¾ Je peux faire un composant similaire au ADODC de Visual Basic 6 ; c'est à dire qui se
positionnerait entre la source de données et les contrôles.
¾ Ou je peux intercepter une liaison existante et naviguer sur le BindingContext de cette liaison
C'est cette deuxième approche que je vais choisir. En effet, il n'y a globalement aucun intérêt à placer un
intermédiaire entre la source et les contrôles sauf à ajouter en complexité.
Pour créer un composant, je vais donc créer une solution "bibliothèque de contrôles Windows" et créer
un 'UserControl'.
Je ne ferai pas ici le détail du code, car la création des composants ferait l'objet d'un cours assez dense,
que j'écrirai peut-être à l'occasion.
Bref, je crée mes quatre boutons et mon label dans mon contrôle, et je lui ajoute une propriété
'ControleLié' pour assigner le contrôle qui fournira la liaison.
Le code du composant est le suivant :
Imports System.ComponentModel
Imports System.Data.Common

Public Class UserControl1


Inherits System.Windows.Forms.UserControl

#Region " Code généré par le Concepteur Windows Form "

Private m_CurrManager As CurrencyManager


Private m_Control As Control

Public Property ControlLié() As Control

28
Get
Return m_Control
End Get
Set(ByVal Value As Control)
m_Control = Value
If Me.DesignMode Then
If m_Control.DataBindings.Count = 0 Then
MessageBox.Show("Impossible de lier un contrôle sans
liaison", "ERREUR", MessageBoxButtons.OK, MessageBoxIcon.Error)
m_Control = Nothing
Exit Property
End If
Else
If Not Me.ParentForm Is Nothing Then
Dim MaLiaison As Binding = m_Control.DataBindings.Item(0)
If MaLiaison.BindingMemberInfo.BindingPath.Length > 0
Then
m_CurrManager =
Me.ParentForm.BindingContext(MaLiaison.DataSource,
MaLiaison.BindingMemberInfo.BindingPath)
Else
m_CurrManager =
Me.ParentForm.BindingContext(MaLiaison.DataSource)
End If

End If
End If
End Set
End Property

Private Sub DefPos()


If m_CurrManager Is Nothing AndAlso Not m_Control Is Nothing Then
Dim MaLiaison As Binding = m_Control.DataBindings.Item(0)
If Not MaLiaison.DataSource Is Nothing Then
If MaLiaison.BindingMemberInfo.BindingPath.Length > 0 Then
m_CurrManager =
Me.ParentForm.BindingContext(MaLiaison.DataSource,
MaLiaison.BindingMemberInfo.BindingPath)
Else
m_CurrManager =
Me.ParentForm.BindingContext(MaLiaison.DataSource)
End If
AddHandler m_CurrManager.PositionChanged, AddressOf
m_CurrManagerPositionChangedHandler
End If
End If
If Not m_CurrManager Is Nothing Then
lblEnr.Text = m_CurrManager.Position + 1 & "/" &
m_CurrManager.Count & " enregistrements"
Else
lblEnr.Text = "Contrôle non lié"
End If
End Sub

29
Private Sub m_CurrManagerPositionChangedHandler(ByVal Sender As Object,
ByVal e As EventArgs)
DefPos()
End Sub

Private Sub ctlNavigue_ParentChanged(ByVal sender As Object, ByVal e As


System.EventArgs) Handles MyBase.ParentChanged
If m_CurrManager Is Nothing AndAlso Not m_Control Is Nothing Then
Dim MaLiaison As Binding = m_Control.DataBindings.Item(0)
If Not MaLiaison.DataSource Is Nothing Then
If MaLiaison.BindingMemberInfo.BindingPath.Length > 0 Then
m_CurrManager =
Me.ParentForm.BindingContext(MaLiaison.DataSource,
MaLiaison.BindingMemberInfo.BindingPath)
Else
m_CurrManager =
Me.ParentForm.BindingContext(MaLiaison.DataSource)
End If
AddHandler m_CurrManager.PositionChanged, AddressOf
m_CurrManagerPositionChangedHandler
End If
End If
End Sub

Private Sub cmdFirst_Click(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles cmdFirst.Click
m_CurrManager.Position = 0
DefPos()
End Sub

Private Sub cmdPrev_Click(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles cmdPrev.Click
If m_CurrManager.Position > 0 Then
m_CurrManager.Position = m_CurrManager.Position - 1
DefPos()
End If
End Sub

Private Sub cmdNext_Click(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles cmdNext.Click
If m_CurrManager.Position < m_CurrManager.Count - 1 Then
m_CurrManager.Position = m_CurrManager.Position + 1
DefPos()
End If
End Sub

Private Sub cmdLast_Click(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles cmdLast.Click
m_CurrManager.Position = m_CurrManager.Count - 1
DefPos()
End Sub
End Class
Maintenant il me suffira d'ajouter mon contrôle comme un contrôle normal.

30
Zone de texte (label & TextBox)
Ces contrôles sont principalement liés par leur propriété 'Text'. Vous n'êtes pas forcés de lier des champs
de type chaîne. En effet, par défaut la liaison applique la méthode ToString et génère le cast implicitement.
On a souvent tendance à utiliser le TextBox comme contrôle par défaut et c'est une erreur. En effet, pour
la saisie, il faut alors gérer restrictions et formatage ce qui ne serait pas nécessaire avec des contrôles plus
adaptés.
Formatage
Les zones de textes n'exposent pas de propriété de formatage. Il faut donc travailler sur les événements
Parse et Format que nous avons vu dans les événements de la classe Binding. Une fois encore, je vous
déconseille fortement ce type de programmation. Si le formatage vous est nécessaire, privilégiez l'utilisation
d'un contrôle adapté.
Restriction
Les restrictions sur ce type de contrôles sont de deux types :
Limitation de la saisie par interception de l'événement KeyPress
Il s'agit d'un grand classique de la programmation. Néanmoins c'est souvent un pis-aller car la
complexité de la restriction peut lourdement pénaliser tant les performances que la lisibilité du code.
L'exemple suivant restreint la saisie aux nombres décimaux
Private Sub txtNumE_KeyPress(ByVal sender As Object, ByVal e As
System.Windows.Forms.KeyPressEventArgs) Handles txtNumE.KeyPress
Dim c As Char
c = e.KeyChar
If Not (Char.IsDigit(c) Or Char.IsControl(c)) Then
If Asc(c) <> 46 Or InStr(1, Me.txtNumE.Text, ".",
CompareMethod.Binary) > 0 Then
e.Handled = True
End If
End If
End Sub
Vérification sur la sortie du contrôle
La vérification a posteriori se fait généralement sur interception de l'événement Validating, afin de
retenir le focus sur un contrôle éventuellement en erreur. Il suffit d'activer le paramètre 'Cancel' pour bloquer
la sortie du contrôle. Pour mémoire la séquence événementielle de sortie d'un contrôle est :
1. Leave
2. Validating
3. Validated
4. LostFocus
La vérification sur sortie permet souvent une simplification du code mais peut contenir des pièges.
Private Sub txtNumE_Validating(ByVal sender As Object, ByVal e As
System.ComponentModel.CancelEventArgs) Handles txtNumE.Validating
If Not IsNumeric(txtNumE.Text) Then
MessageBox.Show("La saisie doit être numérique", "Saisie
invalide", MessageBoxButtons.OK, MessageBoxIcon.Warning)
e.Cancel = True
End If
End Sub
Là encore, le code peut rapidement se complexifier si la validation est plus typée. En effet, IsNumeric
permet de savoir si le texte saisi peut être converti en valeur de type 'double' mais il faudrait ajouter du code
pour valider un entier.
Imaginons maintenant que la saisie porte sur une date.

31
Private Sub txtNumE_Validating(ByVal sender As Object, ByVal e As
System.ComponentModel.CancelEventArgs) Handles txtNumE.Validating
If Not IsDate(txtNumE.Text) Then
MessageBox.Show("La saisie doit être une Date", "Saisie
invalide", MessageBoxButtons.OK, MessageBoxIcon.Warning)
e.Cancel = True
End If
End Sub
Il y a là un piège dû au format de la date. IsDate vérifie si la chaîne peut être convertie en date. Ceci ne
tient pas compte du format de celle-ci. Or il est impossible de savoir si 10/12/2004 représente le dix
décembre ou le 12 octobre. Comme nous le verrons, la gestion des dates est source de nombreux problèmes,
aussi l'utilisation de zones de texte pour la saisie des dates est-elle à bannir.
Texte long
La gestion des textes longs n'est pas différente des autres, si ce n'est pour la présentation du texte. En
effet, il est rarement possible de prédire la longueur du texte, et donc de définir correctement la zone de texte
à la création. En fait, on utilise les propriétés 'Multiline' et 'Wordwrap' pour forcer le texte à s'afficher dans
les dimensions du contrôle. Si celui-ci déborde, il faut alors gérer une barre de défilement. N'utilisez pas la
propriété Autosize car elle ne fait rien en mode 'Multiline'. Il est aussi possible d'utiliser un contrôle
RichTextBox qui gère les textes longs plus dynamiquement.

Texte Formaté (MaskEdBox & RichTextBox)


Le contrôle RichTextBox permet d'afficher des textes formatés au format RichText. Celui-ci étant assez
rarement utilisé dans les bases de données je ne vais pas m'étendre dessus. Sachez toutefois qu'il est plus
adapté que le TextBox pour la gestion des chaînes longues.
Le contrôle MaskEdBox est lui purement un contrôle d'affichage de données. Il s'agit d'un contrôle
ActiveX qui n'est pas un composant managé. Cela veut dire que pour l'utiliser, vous devez aller chercher le
composant COM MSMASK32.OCX. Comme il s'agit d'un composant COM, il n'est pas parfaitement
compatible avec les sources de données que nous avons vues.
Le contrôle se lie de la même façon qu'une zone de texte. Mais il faut lui amener une source de type
Recordset ce qui ajoute à la complexité. Il est donc à mes yeux plus judicieux de créer un composant adapté
au besoin.

Vrai / Faux (CheckBox & RadioButton)


Les affichages vrai/faux ne posent aucun problème majeur de gestion des champs booléens. Néanmoins
les contrôles classiques peuvent fonctionner différemment.
RadioButton
Le contrôle RadioButton n'est pas stricto sensu un contrôle d'affichage de données. Il trouve plus son
sens dans le choix entres possibilités exclusives. C'est pour cela qu'il n'expose pas sa propriété 'Checked'
directement dans les DataBinding. Il est possible le cas échéant de faire cette liaison, mais sauf exception, il
est préférable d'utiliser le contrôle CheckBox.
CheckBox
Il faut lier la propriété Checked et non la propriété CheckedState. Attention d’utiliser un libellé qui soit
clair, afin d’éviter que l’utilisateur n’interprète l’inverse de la réponse.
Autres contrôles
Beaucoup de contrôles supportant les liaisons simples exposent des propriétés booléennes. Il est donc
possible de faire des formulaires assez évolués avec un peu d’inventivité. Il est par exemple possible de lier
le champ ‘Indisponible’ avec une propriété ‘Enabled’.

Les Dates

32
Nous allons attaquer là le premier concept fastidieux. Les dates ne sont pas toujours gérées de la même
façon selon les SGBD. Qui plus est, il peut y avoir une différence entre la façon dont l’utilisateur comprend
les dates, et le format qu’utilise le SGBD. Contrairement à une idée préconçue, il faut laisser au maximum
les variables dans le type ‘date’, et donc éviter les cast malencontreux, pour ne pas avoir de problème.
En effet, c'est lorsqu'on envoie des dates sous formes littérales que des problèmes d'interprétation
peuvent arriver. Ainsi Access, ne comprend que les dates au format américain. Dans l’utilisation d’ADO, ce
problème était une des restrictions de l’utilisation de l’objet Command. Il existe de nombreux contrôles
personnalisés utilisant des dates, mais nous allons nous limiter à l’utilisation des deux plus connus.
MonthCalendar
Le contrôle s’affiche sous la forme d’un calendrier présentant de 1 à 12 mois. Bien que je n’ai toujours
pas compris l’intérêt, les valeurs gérées sont de type DateTime (et pour entrer une heure avec un calendrier,
ce n’est pas simple). On ne l’utilise pas souvent en liaison de données car il permet de saisir des plages de
dates et peut sembler un peu complexe à manipuler. Pourtant dans certains cas, il peut être très pratique,
comme nous allons le voir dans l’exemple suivant.
La table Commande possède trois champs Date qui sont :
¾ Date commande
¾ Date Livraison
¾ A Livrer avant
En utilisant un contrôle MonthCalendar, on peut gérer ses trois dates en un seul affichage.
Dim MonDataset As New DsNWind
MonDataset = RempliDataset()
Dim VueCommande As DataView = MonDataset.Commandes.DefaultView
With Me.nudNumCommande
.Minimum = 0
.Maximum = 100000
.DataBindings.Add("value", VueCommande, "N° commande")
End With
Me.MonthCalendar1.CalendarDimensions = (New System.Drawing.Size(2, 1))
Me.MonthCalendar1.MaxSelectionCount = 365
Me.MonthCalendar1.DataBindings.Add("SelectionStart", VueCommande, "date
commande")
Me.MonthCalendar1.DataBindings.Add("SelectionEnd", VueCommande, "à livrer
avant")
Me.MonthCalendar1.DataBindings.Add("TodayDate", VueCommande, "date
envoi")
J’obtiens alors une plage en bleu allant de la date de la commande à la date butoir, la date d’envoi étant
cerclée. Ce type de manipulation n’est pas fréquent, aussi n’allons-nous pas nous y attarder, mais il faut
savoir que ça existe. Notez que la manipulation de la plage modifie les valeurs de la table.
DateTimePicker
Généralement c’est ce contrôle que l’on utilise pour la gestion des champs Dates.
Le même contrôle sert pour gérer les trois modes, c’est à dire DateTime, Date et Time.
Bloquer la saisie en heure
Pour jouer sur les possibilités du contrôle, on attaque d’abord la propriété ‘Format’. Celle-ci accepte les
valeurs suivantes :
Long = Format de date long
Short = Format de date court
Time = Format d’heure
Custom = Format personnalisé.
Sachant que les trois premiers sont les formats définis dans le panneau de configuration.
Pour travailler sur un contrôle ‘horaire ‘ je vais donc choisir le format Time. Je vais ensuite mettre la
propriété ShowUpDown à Vraie. Je dispose alors d’un contrôle dans lequel il n’est plus possible de saisir
que des heures. Il suffit après de réaliser la liaison

33
Format personnalisé
Je reprends ma table commande, et je gère mes dates avec trois contrôles DateTimePicker.
Dim MonDataset As New DsNWind
MonDataset = RempliDataset()
Dim VueCommande As DataView = MonDataset.Commandes.DefaultView
With Me.nudNumCommande
.Minimum = 0
.Maximum = 100000
.DataBindings.Add("value", VueCommande, "N° commande")
End With
Me.dtpCommande.DataBindings.Add("value", VueCommande, "date commande")
Me.dtpButee.DataBindings.Add("value", VueCommande, "à livrer avant")
Me.dtpEnvoi.DataBindings.Add("value", VueCommande, "date envoi")
CM = CType(Me.BindingContext(VueCommande), CurrencyManager)
Il est assez facile de changer le format de la date
With Me.dtpButee
.Format = DateTimePickerFormat.Custom
.CustomFormat = "ddd dd/MM/yy"
.DataBindings.Add("value", VueCommande, "à livrer avant")
End With
With Me.dtpEnvoi
.Format = DateTimePickerFormat.Custom
.CustomFormat = "MMM-dd-yyyy"
.DataBindings.Add("value", VueCommande, "date envoi")
End With
Toujours dans le même schéma, je peux ajouter un libellé dans ma chaîne
With Me.dtpButee
.Format = DateTimePickerFormat.Custom
.CustomFormat = "'à livrer avant le :'ddd dd/MM/yy"
.DataBindings.Add("value", VueCommande, "à livrer avant")
End With

Représentation numérique
Les valeurs de types numériques se gèrent soit par l’intermédiaire de zones de texte, soit à l’aide de
contrôles numériques. J’ai déjà envisagé le cas de zones de texte.
NumericUpDown
Il s’agit d’un contrôle UpDown ne gérant que les valeurs numériques. Il peut travailler sur des valeurs
décimales ou entières. Le contrôle nudNumCommande des exemples précédents en est un exemple.
La saisie clavier est autorisée dans ce type de contrôles, ce qui fait que vous devrez écrire du code pour
vérifier les entrées de l’utilisateur pour vérifier si la saisie est décimale ou entière.
Notez que sur des champs clés de type numérique on peut facilement détourner le contrôle pour lui faire
faire de la navigation.

34
TrackBar
Bien que moins commun dans les contrôles liés, le contrôle TrackBar peut avoir un intérêt, si on
souhaite représenter une valeur dont la position dans une plage est une information pertinente.
Il est toutefois complexe à lier. En effet, par défaut sa valeur de maximum est fixée à 10. Si la première
valeur de la liaison est supérieure au maximum, une exception va être levée. Habituellement, la valeur du
maximum doit donc aussi être une propriété liée à une valeur qui doit toujours être supérieure ou égale à la
valeur liée à la propriété ‘Value’.

La navigation relationnelle
Il est aussi possible de lier des contrôles à des données relationnelles de la vue principale. Il faut pour
cela qu’une relation valide soit définie dans le Dataset. Tout contrôle supportant les liaisons simples peut
afficher une donnée relationnelle par utilisation de la relation (notée parfois chemin de navigation) dans le
DataMember de la liaison. Malheureusement, à la différence des saumons, ce chemin ne se parcourt qu'en
descendant, c'est-à-dire des tables parent vers les tables enfant. Or dans un formulaire à liaison simple, on
cherche généralement l'inverse, c'est à dire à récupérer les informations du parent de l'enfant courant. On doit
donc gérer alors un processus un peu plus lourd, utilisant plusieurs vues, la synchronisation. Pour cela, nous
allons nous servir des vues par défaut qui permettent de gérer simplement les vues d'un Dataset.
Dans mon formulaire 'détail commande', je souhaite afficher le nom du produit et le nom du fournisseur.
Dans ma table 'détail commande', je ne dispose que de la référence du produit. Celle-ci est la clé étrangère de
la relation avec la table 'Produits' qui est elle-même une table enfant de la table 'Fournisseurs'.
Private CM As CurrencyManager
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
'Module1.RempliDataset()
Me.dtaCategories.Fill(Me.DsNWind2.Catégories)
Me.dtaClients.Fill(Me.DsNWind2.Clients)
Me.dtaEmployes.Fill(Me.DsNWind2.Employés)
Me.dtaFournisseurs.Fill(Me.DsNWind2.Fournisseurs)
Me.dtaMessagers.Fill(Me.DsNWind2.Messagers)
Me.dtaProduits.Fill(Me.DsNWind2.Produits)
Me.dtaCommandes.Fill(Me.DsNWind2.Commandes)
Me.dtaDetailCommande.Fill(Me.DsNWind2.Détails_commandes)
'Gestion des dataview
Dim VueDetCommande As DataView =
Me.DsNWind2.Détails_commandes.DefaultView
Dim VueProduit As DataView = Me.DsNWind2.Produits.DefaultView
Dim VueFournisseur As DataView = Me.DsNWind2.Fournisseurs.DefaultView
'liaison des controles enfants
Me.txtNumCom.DataBindings.Add("Text", VueDetCommande, "N° commande")
Me.txtPrix.DataBindings.Add("Text", VueDetCommande, "Prix unitaire")
Me.nudQuantite.DataBindings.Add("Value", VueDetCommande, "Quantité")
Me.nudRemise.DataBindings.Add("Value", VueDetCommande, "Remise (%)")
'liaison du controle père
Me.txtNomProduit.DataBindings.Add("Text", VueProduit, "Nom du
Produit")
'liaison du grand père
Me.txtNomFournisseur.DataBindings.Add("Text", VueFournisseur,
"Société")
CM = CType(Me.BindingContext(VueDetCommande), CurrencyManager)
Call Synchronisation()
End Sub

35
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
CM.Position = CM.Position + 1
Call Synchronisation()
End Sub

Private Sub Synchronisation()


Dim MaLigDet As DataRowView = CType(CM.Current, DataRowView)
Me.DsNWind2.Produits.DefaultView.RowFilter = "[réf produit]=" &
MaLigDet.Item("réf produit")
Dim MaLigProd As DataRowView =
Me.DsNWind2.Produits.DefaultView.Item(0)
Me.DsNWind2.Fournisseurs.DefaultView.RowFilter = "[N° Fournisseur]="
& MaLigProd.Item("N° Fournisseur")
End Sub
Comme nous le voyons, la fonction de synchronisation est assez simple. Néanmoins nous n'utilisons pas
les relations. Une autre technique consistera alors à modifier la table enfant, en lui ajoutant les colonnes
désirées. Cependant si on veut atteindre plusieurs champs parent, cette technique n'est rapidement plus
viable.

Les liaisons complexes


Le nom de liaison complexe désigne les liaisons entre des contrôles et des groupes de données.
Généralement ces groupes de données sont des champs, mais dans certains cas il peut s'agir de liste. Ce
regroupement est orienté en deux familles. Les listes qui permettent une sélection dans un champ et les
grilles qui permettent un affichage champ / enregistrement.

Zone de liste (ListBox & ComboBox)


Nous allons commencer par regarder les contrôles listes. Ces contrôles exposent en plus de la collection
DataBindings une propriété DataSource et deux propriétés DisplayMember et ValueMember. Tel que nous
l'avons vu jusque là, les contrôles liés par l'intermédiaire du DataBinding ne sont pas acteurs, mais juste
consommateurs. De plus la notion d'enregistrement n'existe pas pour un contrôle à liaison simple. Les zones
de liste, elles, peuvent se comporter comme des contrôles à liaison simple mais aussi comme des contrôles de
gestion d'enregistrements. Vous noterez aussi qu'à la différence des contrôles à liaison simple, les zones de
liste exposent aussi leur propre BindingContext.
Pour utiliser la liste comme un contrôle à liaison complexe, on doit donc lui définir une source de
données dans sa propriété DataSource. Tout objet héritant de IList ou de IBindingList peut être cette source.
Toutefois les sources n'exposant pas IBindingList ne remettent pas à jour le contrôle si elles sont modifiées.
Regardons l'exemple suivant :
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
Dim MaListe As New ArrayList, cmpt As Long
For cmpt = 1 To 10
MaListe.Add("Objet" & cmpt.ToString)
Next
Me.ListBox1.DataSource = MaListe
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles Button1.Click
Dim MaListe As ArrayList
MaListe = CType(Me.ListBox1.DataSource, ArrayList)
MaListe.Add("Nouvel objet")
MaListe.RemoveAt(5)
Me.ListBox1.Refresh()

36
End Sub
Le clic sur le bouton ne modifie pas l'affichage de la liste malgré les modifications. Pour synchroniser la
liste avec la source, je dois appeler la méthode Refresh de son CurrencyManager, que j'obtiens en appelant
son BindingContext :
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
Dim MaListe As ArrayList, CurrManage As CurrencyManager
MaListe = CType(Me.ListBox1.DataSource, ArrayList)
MaListe.Add("Nouvel objet")
MaListe.RemoveAt(5)
CurrManage = CType(Me.ListBox1.BindingContext(MaListe),
CurrencyManager)
CurrManage.Refresh()
End Sub
Maintenant examinons les propriétés DisplayMember et ValueMember. Ces propriétés vont permettre
au contrôle d'être acteur. DisplayMember représente le champ qui va être affiché dans la source.
ValueMember représente le champ de la même source de données qui va être renvoyé par la propriété
SelectedValue. Pour comprendre la différence il suffit de tester le code suivant.
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
'Module1.RempliDataset()
……
With Me.ListBox1
.DataSource = Me.DsNWind2.Employés.DefaultView
.DisplayMember = "Nom"
.ValueMember = "Prénom"
End With
End Sub

Private Sub ListBox1_Click(ByVal sender As Object, ByVal e As


System.EventArgs) Handles ListBox1.Click
MessageBox.Show("Valeur sélectionnée : " & Me.ListBox1.SelectedValue)
End Sub
Et on obtient

Notez que si vous donnez la même propriété DataSource à deux listes, celles-ci sont synchronisées.
Regardons le cas ci-dessous.
With Me.ListBox1
.DataSource = Me.DsNWind2.Employés.DefaultView
.DisplayMember = "Nom"
.ValueMember = "Prénom"
End With
With Me.ListBox2

37
.DataSource = Me.DsNWind2.Employés.DefaultView
.DisplayMember = "Prénom"
.ValueMember = "N° employé"
End With
Dans ce cas un clic sur une des deux listes va faire changer l'élément sélectionné des deux listes en
même temps. Cette synchronisation n'est pas toujours souhaitable. Pour créer deux listes indépendantes d'une
même source, il faut dissocier leurs BindingContext ainsi :
With Me.ListBox1
.BindingContext = MonContexte
.DataSource = Me.DsNWind2.Employés.DefaultView
.DisplayMember = "Nom"
.ValueMember = "Prénom"
End With
MonContexte = New BindingContext
With Me.ListBox2
.BindingContext = MonContexte
.DataSource = Me.DsNWind2.Employés.DefaultView
.DisplayMember = "Prénom"
.ValueMember = "N° employé"
End With
Ici, mes deux zones de listes sont dissociées.
ListBox
Le contrôle de liste affiche les données sous forme d'une liste simple. La mise en forme de la liste
dépend des propriétés de la liste, mais aussi des propriétés de la source de données. Ainsi, une liste liée ne
trie pas les données qu'elle affiche, mais il est possible de trier la source. Les ListBox sont généralement
utilisées pour permettre la sélection d'un élément existant dans un choix limité. En fait, on l'utilise
généralement dans des scénarii standards que nous allons regarder maintenant.
Navigation sur une table
Dans le concept, la liste sert à sélectionner un enregistrement ce qui va faire réagir l'ensemble du
formulaire. C'est en quelque sorte une utilisation comme contrôle de navigation.
Je vais concevoir un formulaire affichant la liste des commandes, les dates de commande et d'envoi, le
transporteur et le destinataire de chaque commande. Mon code de liaison sera le suivant :
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
'Module1.RempliDataset()
Me.dtaCategories.Fill(Me.DsNWind2.Catégories)
Me.dtaClients.Fill(Me.DsNWind2.Clients)
Me.dtaEmployes.Fill(Me.DsNWind2.Employés)
Me.dtaFournisseurs.Fill(Me.DsNWind2.Fournisseurs)
Me.dtaMessagers.Fill(Me.DsNWind2.Messagers)
Me.dtaProduits.Fill(Me.DsNWind2.Produits)
Me.dtaCommandes.Fill(Me.DsNWind2.Commandes)
Me.dtaDetailCommande.Fill(Me.DsNWind2.Détails_commandes)
Me.DsNWind2.Commandes.Columns.Add("Messager",
System.Type.GetType("System.String"), "Parent(Messagers_Commandes).[nom
du messager]")
Dim VueCommande As DataView = Me.DsNWind2.Commandes.DefaultView
With Me.ListNumCommande
.DataSource = VueCommande
.DisplayMember = "N° commande"
.ValueMember = "N° commande"
End With

38
Me.txtDestinataire.DataBindings.Add("Text", VueCommande,
"Destinataire")
Me.txtAdresse.DataBindings.Add("Text", VueCommande, "Adresse
livraison")
Me.txtCodePostal.DataBindings.Add("Text", VueCommande, "Code postal
livraison")
Me.txtMessager.DataBindings.Add("Text", VueCommande, "Messager")
Me.txtPays.DataBindings.Add("Text", VueCommande, "pays livraison")
Me.txtVille.DataBindings.Add("Text", VueCommande, "Ville livraison")
With Me.dtpCommande
.Format = DateTimePickerFormat.Custom
.CustomFormat = "'Commandé le : 'dd MMM yy"
.DataBindings.Add("Value", VueCommande, "Date commande")
End With
With Me.dtpEnvoi
.Format = DateTimePickerFormat.Custom
.CustomFormat = "'Envoyé le : 'dd MMM yy"
.DataBindings.Add("Value", VueCommande, "Date envoi")
End With
End Sub
Vous noterez que ce code n'est pas nécessaire. Je l'utilise pour vous montrer le réglage des propriétés
mais tout ceci peut être fait en mode conception. J'obtiens alors un affichage dynamique lors de la sélection
d'un numéro de commande dans la liste. Ce qui nous donne un résultat de cette forme :

Maître / Détails
Les affichages maître / détails sont principalement un concept relationnel. Cela consiste à afficher les
enregistrements enfants d'un parent sélectionné. Ces affichages sont très faciles à réaliser si la source est un
Dataset ayant des relations définies. En effet, les contrôles à liaison complexe acceptent l'expression d'une
relation comme propriété DataMember. Dès lors, il suffit d'associer la clé de la table parent dans la propriété
ValueMember. Dans cet exemple, je vais utiliser un datagrid, que nous étudierons un peu plus loin, pour
afficher les enregistrements enfant. Dans mon formulaire, je veux afficher les produits d'un fournisseur par
simple sélection de celui-ci. Le code sera :
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
'Module1.RempliDataset()

39
Me.dtaCategories.Fill(Me.DsNWind2.Catégories)
Me.dtaClients.Fill(Me.DsNWind2.Clients)
Me.dtaEmployes.Fill(Me.DsNWind2.Employés)
Me.dtaFournisseurs.Fill(Me.DsNWind2.Fournisseurs)
Me.dtaMessagers.Fill(Me.DsNWind2.Messagers)
Me.dtaProduits.Fill(Me.DsNWind2.Produits)
Me.dtaCommandes.Fill(Me.DsNWind2.Commandes)
Me.dtaDetailCommande.Fill(Me.DsNWind2.Détails_commandes)
With Me.LstFournisseur
.DataSource = Me.DsNWind2.Fournisseurs
.DisplayMember = "Société"
.ValueMember = "N° fournisseur"
End With
With Me.dtgProduit
.DataSource = Me.DsNWind2.Fournisseurs
.DataMember = "Fournisseurs_Produits"
End With
End Sub
Chaque sélection d'un fournisseur dans la liste déclenchera l'affichage des produits de ce fournisseur
dans la grille.
Remarques
Lorsqu'on utilise une liste comme consommateur simple, elle expose prioritairement trois propriétés à la
liaison qu'il convient de ne pas confondre.
SelectedValue : La valeur sélectionnée s'il existe une définition valide pour la propriété ValueMember.
SelectedItem : Renvoie ou définit l'élément (object) sélectionné.
SelectedIndex : Renvoie ou définit l'index (position dans la liste) de l'élément sélectionné.
CheckedListBox
Ce contrôle est assez semblable au précédent, si ce n'est qu'il ajoute une case à cocher devant chaque
élément de la liste. Ceci permet la gestion de sélections multiples de façon plus ergonomique. La gestion des
éléments sélectionnés se fait par l'utilisation de la collection CheckedItems ou par l'appel de la méthode
GetItemChecked. Méfiez-vous des valeurs renvoyées par la collection SelectedItems car un élément
sélectionné n'est pas nécessairement coché.
Regroupement partiel
On trouve souvent ce cas sous la dénomination de recherche multicritères. Dans le principe, on utilise
une ou plusieurs listes pour limiter les choix possibles. Dans mon exemple, je vais limiter la sélection des
Produits par restriction sur les catégories de produits et sur la localisation du fournisseur.
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
'Module1.RempliDataset()
Me.ConnNWind.Open()
Me.dtaCategories.Fill(Me.DsNWind2.Catégories)
Me.dtaClients.Fill(Me.DsNWind2.Clients)
Me.dtaEmployes.Fill(Me.DsNWind2.Employés)
Me.dtaFournisseurs.Fill(Me.DsNWind2.Fournisseurs)
Me.dtaMessagers.Fill(Me.DsNWind2.Messagers)
Me.dtaProduits.Fill(Me.DsNWind2.Produits)
Me.dtaCommandes.Fill(Me.DsNWind2.Commandes)
Me.dtaDetailCommande.Fill(Me.DsNWind2.Détails_commandes)
With Me.clbCategorie
.DataSource = Me.DsNWind2.Catégories
.DisplayMember = "Nom de catégorie"
.ValueMember = "Code catégorie"

40
.SelectionMode = SelectionMode.One
End With
Dim MaComm As OleDb.OleDbCommand = New OleDb.OleDbCommand("SELECT
Ville FROM Fournisseurs GROUP BY Ville ORDER BY Ville", Me.ConnNWind)
Dim MonReader As OleDb.OleDbDataReader =
MaComm.ExecuteReader(CommandBehavior.CloseConnection)
While MonReader.Read
Me.clbVille.Items.Add(MonReader.GetString(0))
End While
Me.clbVille.SelectionMode = SelectionMode.One
Me.dtgFournisseur.DataSource = Me.DsNWind2.Produits.DefaultView
End Sub

Private Sub clbCategorie_ItemCheck(ByVal sender As Object, ByVal e As


System.Windows.Forms.ItemCheckEventArgs) Handles clbCategorie.ItemCheck
Dim StrRecup As String = Me.DsNWind2.Produits.DefaultView.RowFilter
Dim strFiltre As String = ""
If Me.clbVille.CheckedIndices.Count > 0 Then
StrRecup = Trim(Mid(StrRecup, InStr(1, StrRecup, "Parent",
CompareMethod.Binary)))
If Mid(StrRecup, 1, 1) = "(" Then StrRecup = Mid(StrRecup, 2)
If Mid(StrRecup, StrRecup.Length, 1) = ")" Then StrRecup =
Mid(StrRecup, 1, StrRecup.Length - 1)
Else
StrRecup = ""
End If
Dim Ind As Integer
For Each Ind In Me.clbCategorie.CheckedIndices
If Ind <> e.Index Then
Me.clbCategorie.SetSelected(Ind, True)
If strFiltre.Length > 0 Then strFiltre = strFiltre & " OR "
strFiltre = strFiltre & "[Code catégorie] = " &
Me.clbCategorie.SelectedValue
End If
Next
If e.NewValue = CheckState.Checked Then
Me.clbCategorie.SetSelected(e.Index, True)
If strFiltre.Length > 0 Then strFiltre = strFiltre & " OR "
strFiltre = strFiltre & "[Code catégorie] = " &
Me.clbCategorie.SelectedValue
End If
If StrRecup.Length > 0 Then
If strFiltre.Length > 0 Then strFiltre = "(" & strFiltre & ") AND
(" & StrRecup & ")" Else strFiltre = StrRecup
End If
Me.DsNWind2.Produits.DefaultView.RowFilter = strFiltre
End Sub

Private Sub clbVille_ItemCheck(ByVal sender As Object, ByVal e As


System.Windows.Forms.ItemCheckEventArgs) Handles clbVille.ItemCheck
Dim StrRecup As String = Me.DsNWind2.Produits.DefaultView.RowFilter
Dim strFiltre As String = ""
If Me.clbCategorie.CheckedIndices.Count > 0 Then
Dim PosAnd As Integer = InStr(1, StrRecup, " AND ",
CompareMethod.Binary)

41
If PosAnd > 0 Then Trim(StrRecup = Mid(StrRecup, 1, PosAnd))
If Mid(StrRecup, 1, 1) = "(" Then StrRecup = Mid(StrRecup, 2)
If Mid(StrRecup, StrRecup.Length, 1) = ")" Then StrRecup =
Mid(StrRecup, 1, StrRecup.Length - 1)
Else
StrRecup = ""
End If
Dim Ind As Integer
For Each Ind In Me.clbVille.CheckedIndices
If Ind <> e.Index Then
Me.clbVille.SetSelected(Ind, True)
If strFiltre.Length > 0 Then strFiltre = strFiltre & " OR "
strFiltre = strFiltre & "Parent(Fournisseurs_Produits).Ville
= " & QuotedStr(Me.clbVille.SelectedItem)
End If
Next
If e.NewValue = CheckState.Checked Then
Me.clbVille.SetSelected(e.Index, True)
If strFiltre.Length > 0 Then strFiltre = strFiltre & " OR "
strFiltre = strFiltre & "Parent(Fournisseurs_Produits).Ville = "
& QuotedStr(Me.clbVille.SelectedItem)
End If
If StrRecup.Length > 0 Then
If strFiltre.Length > 0 Then strFiltre = "(" & StrRecup & ") AND
(" & strFiltre & ")" Else strFiltre = StrRecup
End If
Me.DsNWind2.Produits.DefaultView.RowFilter = strFiltre
End Sub

Private Function QuotedStr(ByVal str2Quote) As String


QuotedStr = Chr(39) & str2Quote & Chr(39)
End Function
J'ai choisi cet exemple car il représente un cas intéressant, le regroupement sur une table. Comme vous
le voyez, je veux pouvoir faire une liste avec les villes des fournisseurs. Evidemment je souhaite que chaque
ville n’apparaisse qu’une fois dans ma liste. Je ne peux pas solutionner ce problème sans code car le
DataView ne sait pas faire intrinsèquement le regroupement. Dès lors je ne peux pas lier ma liste. J’utilise
donc un DataReader et une commande pour regrouper par le SQL et remplir ma liste.
Je perds par contre des fonctionnalités de la liste puisque dans mon cas, je ne peux plus utiliser la
propriété SelectedValue.
Notez enfin que pour la simple gestion d’un choix multiple dans une liste, le code s’est très rapidement
complexifié. Il faut donc bien se poser la question de l’intérêt avant de se lancer dans ce type de fonctions.
ComboBox
C’est le mariage d’une zone de liste et d’une zone de saisie. On l’appelle souvent « liste déroulante ».
Elle peut avoir les mêmes fonctionnalités qu’une zone de liste ou être assez différente, selon la valeur donnée
à la propriété ComboBoxStyle. Celle-ci peut prendre les valeurs suivantes :
Nom Description
DropDown La partie texte est modifiable.
DropDownList L'utilisateur ne peut pas directement modifier la partie texte.
Simple La partie texte est modifiable. La partie liste est toujours visible.
Attention à ne pas vous enfoncer dans une erreur commune. En gérant juste la liaison avec DataSource
et DataMember, si vous saisissez une valeur non présente dans la liste, celle-ci ne va pas s’ajouter dans la
liste. Il est obligatoire d’utiliser aussi le DataBinding comme nous allons le voir dans l’exemple suivant :

42
Liste de saisie
Imaginons que nous voulions faire un formulaire pour ajouter des catégories. Mon formulaire contient
un ComboBox pour les noms de catégories et une zone de texte pour la description. Il possède aussi un jeu
de bouton pour Ajouter et valider les enregistrements.
Mon code est :
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
………………………………………………..
With Me.cmbNomCat
.DataSource = Me.DsNWind2.Catégories.DefaultView
.DisplayMember = "Nom de catégorie"
.ValueMember = "Nom de catégorie"
End With
Me.txtDesc.DataBindings.Add("Text",
Me.DsNWind2.Catégories.DefaultView, "Description")
CM =
CType(Me.BindingContext.Item(Me.DsNWind2.Catégories.DefaultView),
CurrencyManager)
End Sub

Private Sub cmdAjout_Click(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles cmdAjout.Click
Dim MaLigne As DataRowView
MaLigne = Me.DsNWind2.Catégories.DefaultView.AddNew()
CM.Position = CM.Count - 1
MaLigne.BeginEdit()
End Sub

Private Sub cmdValid_Click(ByVal sender As System.Object, ByVal e As


System.EventArgs) Handles cmdValid.Click
Me.DsNWind2.Catégories.DefaultView.Item(CM.Position).EndEdit()
End Sub
Si j’utilise le bouton Ajouter, mes contrôles se mettent bien sur un nouvel enregistrement. Je procède
alors à ma saisie. Lorsque je clique sur le bouton Valider, ma saisie disparaît. Si je vais sur le dernier
enregistrement (celui que je viens d’ajouter), je récupère bien la valeur du champ Description, mais la valeur
du champ ‘Nom de catégorie’, c’est-à-dire de la liste déroulante est une ligne vide.
Pour que la liaison fonctionne correctement, je dois aussi lier la zone de texte du contrôle en passant par
le DataBinding du contrôle. Mon code doit être :
With Me.cmbNomCat
.DataSource = Me.DsNWind2.Catégories.DefaultView
.DisplayMember = "Nom de catégorie"
.ValueMember = "Nom de catégorie"
End With
Me.cmbNomCat.DataBindings.Add("Text", Me.DsNWind2.Catégories.DefaultView,
"Nom de catégorie")
Recherche partielle
Une autre utilisation des ComboBox consiste à faire de la saisie assistée. Cette technique n’est pas
directement accessible et doit être programmée. Il existe plusieurs techniques pour faire cela, je ne vous
donne que le code standard, car cela ne porte pas directement sur le sujet.
Private Sub cmbNomCat_TextChanged(ByVal sender As Object, ByVal e As
System.EventArgs) Handles cmbNomCat.TextChanged
With Me.cmbNomCat
Dim Pos As Integer = .Text.Length
.SelectedIndex = .FindString(.Text)

43
.SelectionStart = Pos
.SelectionLength = .Text.Length - Pos
End With
End Sub
N’oubliez pas que la sélection des éléments de ce code engendre un déplacement dans le jeu
d’enregistrements. Il est ainsi possible d’utiliser le ComboBox comme un contrôle de recherche.
Affichage dépendant
Le dernier exemple que nous allons voir va nous servir aussi dans le chapitre suivant. L’affichage
dépendant consiste à utiliser la zone de liste comme un affichage du parent lors du parcours des
enregistrements enfant. Cela est très souvent utilisé dans les modèles relationnels.
Dans le concept, on lie le contrôle à une table parent (DisplayMember sur le libellé et ValueMember sur
la clé). On lie ensuite par DataBinding la valeur à la clé étrangère de la table fille pour obtenir un affichage
plus clair.
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
'Module1.RempliDataset()
Me.ConnNWind.Open()
Me.dtaCategories.Fill(Me.DsNWind2.Catégories)
Me.dtaClients.Fill(Me.DsNWind2.Clients)
Me.dtaEmployes.Fill(Me.DsNWind2.Employés)
Me.dtaFournisseurs.Fill(Me.DsNWind2.Fournisseurs)
Me.dtaMessagers.Fill(Me.DsNWind2.Messagers)
Me.dtaProduits.Fill(Me.DsNWind2.Produits)
Me.dtaCommandes.Fill(Me.DsNWind2.Commandes)
Me.dtaDetailCommande.Fill(Me.DsNWind2.Détails_commandes)
Dim VueProduit, VueCategorie, VueFournisseur As DataView
VueProduit = Me.DsNWind2.Produits.DefaultView
VueProduit.Sort = "Nom du produit ASC"
VueCategorie = Me.DsNWind2.Catégories.DefaultView
VueCategorie.Sort = "Nom de catégorie ASC"
VueFournisseur = Me.DsNWind2.Fournisseurs.DefaultView
VueFournisseur.Sort = "Société ASC"
With Me.lstNomProduit
.DataSource = VueProduit
.DisplayMember = "Nom du produit"
.ValueMember = "Réf produit"
End With
Me.lblNomProduit.DataBindings.Add("Text", VueProduit, "Nom du
produit")
Me.txtQuant_U.DataBindings.Add("Text", VueProduit, "Quantité par
unité")
With Me.nudPrix
.DecimalPlaces = 2
.DataBindings.Add("Value", VueProduit, "Prix unitaire")
End With
Me.nudStock.DataBindings.Add("Value", VueProduit, "Unités en stock")
Me.nudCommande.DataBindings.Add("Value", VueProduit, "Unités
commandées")
Me.trbNiveau.DataBindings.Add("Value", VueProduit, "Niveau de
réapprovisionnement")
Me.lblIndispo.DataBindings.Add("Visible", VueProduit, "Indisponible")
With Me.cmbCategorie
.DataSource = VueCategorie
.DisplayMember = "Nom de catégorie"

44
.ValueMember = "Code catégorie"
.DataBindings.Add("SelectedValue", VueProduit, "Code catégorie")
End With
With Me.cmbFournisseur
.DataSource = VueFournisseur
.DisplayMember = "Société"
.ValueMember = "N° fournisseur"
.DataBindings.Add("SelectedValue", VueProduit, "N° fournisseur")
End With
End Sub
On obtient alors un formulaire de la forme

Nous avons donc utilisé les deux tables (mère et fille) dans la liaison du contrôle. La table mère
comme source de données, la table fille comme élément de sélection.

ErrorProvider
Nous allons découvrir ce contrôle car il s’agit d’un petit nouveau. Le contrôle ErrorProvider est une aide
à la validation du formulaire ou à la compréhension des erreurs des Dataset.
Quel que soit le langage utilisé, la validation du formulaire est un grand classique de la programmation
des bases de données. Cela pour deux raisons.
Les champs des bases de données sont des ‘réceptacles’ typés. Dès lors il convient d’être vigilant au
type des données saisies par l’utilisateur. Si vous travaillez avec un Dataset, l’erreur aura lieu lors de l’envoi
de la donnée au Dataset ce qui est un moindre mal. Mais si vous travaillez directement par des objets
Command, c’est le SGBD qui renverra les erreurs.
En validant les saisies, vous protégez votre formulaire contre une éventuelle tentative de détournement
de celui-ci, dans le cas d’une tentative d’intrusion.
Le contrôle ErrorProvider est utilisé en cas de validation des contrôles ‘à la volée’ c’est-à-dire sur
l’événement ‘Validated’ ou ‘Validating’ du contrôle, selon la gestion du focus que l’on souhaite obtenir.
Le contrôle génère une icône d’erreur à coté du contrôle en erreur. Cette icône clignote tant que le
curseur de la souris n’a pas été pointé sur elle. Lorsque cela a lieu, le message correspondant s’affiche.

45
Dans le principe c’est extrêmement simple, il suffit d’ajouter un contrôle ErrorProvider à la feuille puis
de gérer les événements des contrôles cibles.
Par exemple:
Private Sub nudPrix_Validating(ByVal sender As Object, ByVal e As
System.ComponentModel.CancelEventArgs) Handles nudPrix.Validating
If Me.nudPrix.Value = 0 Then
'génère l'erreur
Me.ErrorProvider1.SetError(Me.nudPrix, "Le prix ne peut être
nul")
e.Cancel = True
Else
'efface l'erreur
Me.ErrorProvider1.SetError(Me.nudPrix, "")
End If
End Sub
Jusque là je n'ai pas utilisé le contrôle comme un contrôle dépendant.
En fait, on peut se servir du même contrôle pour gérer les erreurs d'un Dataset. Pour cela le contrôle
ErrorProvider expose des propriétés DataSource et DataMember. Une fois que celles-ci sont attribuées, le
contrôle ErrorProvider affichera les erreurs sur les contrôles possédant les mêmes propriétés que lui. Cela
permet un affichage beaucoup plus clair des erreurs du Dataset.

46
Le Datagrid
A tout seigneur tout honneur, nous allons pour finir voir le plus complet et le plus complexe des
contrôles dépendants.

Présentation
Pour bien comprendre le fonctionnement du Datagrid, il faut comprendre les objets qui gravitent autour.

La source de données
Bien qu'il puisse en accepter plusieurs, la source de données est généralement une vue d'une table. Un
datagrid est sensiblement l'équivalent pour une table d'une zone de liste pour un champ. Le Datagrid ne gère
pas directement la notion de ligne, celles-ci ne sont donc accessibles qu'au travers de la source.

Le contrôle Datagrid
Pour schématiser, je dirais que c'est le squelette de la table. En fait le contrôle ne gère intrinsèquement
qu'un affichage 'simple' des données. Principalement, sa fonction est de gérer la navigation et l'édition dans
les cellules de la grille. Il gère aussi une mise en forme de base de la grille (principalement les couleurs).

La mise en forme
C'est le point le plus ardu à maîtriser. La mise en forme de la grille passe par des objets
DatagridTableStyle. Ceux-ci peuvent être vus comme une mise en forme évoluée de la grille. Ils sont
composés d'objet DatagridColumnStyle qui eux gèrent la mise en forme des colonnes. Cette mise en forme
peut être elle-même très complexe.
Comme nous allons maintenant le voir, tout cet ensemble interagit pour donner un contrôle très complet.
Pour ne pas être troublé par les raccourcis engendrés par l'utilisation des Dataset fortement typés, je vais
travailler de façon 'classique'.

Le contrôle Datagrid
Le contrôle Datagrid attend comme source de données un DataViewManager ou un DataView. Vous
pouvez évidemment passer un Dataset ou une DataTable comme source du Datagrid, mais celui-ci utilisera
l'objet vue par défaut correspondant. La première bonne habitude consiste donc à passer explicitement un
objet Dataview (ou DataViewManager) à votre Datagrid.
Nous y reviendrons plus loin, mais sachez d'ores et déjà que le Mappage correct de vos tables va vous
être utile ; je vais donc faire un petit rappel.

Mappage
Lorsqu'on remplit un Dataset en partant d'un SGBD, les tables et les champs doivent être mappées sur le
Dataset. Ce comportement est défini par la propriété MissingMappingAction qui peut prendre les valeurs
suivantes :
Nom Description
Une exception InvalidOperationException est générée si le mappage des colonnes
Error
spécifiées est manquant.
Toute colonne ou table ne possédant pas de mappage est ignorée. Retourne une
Ignore
référence Null (Nothing dans Visual Basic).
La table ou colonne source est créée et ajoutée au Dataset à l'aide de son nom
Passthrough
d'origine.
Comme la propriété à pour valeur par défaut ‘Passthrough’, les DataColumn du Dataset se voient
attribuer les noms des colonnes du SGBD si vous omettez de les préciser.

47
Le mappage consiste généralement à donner explicitement des noms à vos colonnes de Dataset et à
établir une liaison de noms entre les champs de la table du SGBD et les DataColumn de la DataTable. Cela
présente l’avantage d’utiliser des noms plus parlant, mais aussi de pouvoir ignorer des colonnes lors de la
récupération.
Quelle que soit la stratégie suivie, le nom des colonnes est important car c’est celui qui va apparaître
dans les en-têtes de colonnes du datagrid, et c’est par ce nom que les styles vont pouvoir être appliqués.
Le code suivant est un mappage de la table ‘produits’.
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
Dim MaConn As New System.Data.OleDb.OleDbConnection("Provider =
Microsoft.Jet.OLEDB.4.0;User ID=Admin;Data Source=D:\User\nwind.mdb;")
Dim strSQL As String = "SELECT [réf produit],[nom du produit],[N°
Fournisseur],[code catégorie],[Quantité par unité],[Prix
unitaire],[Unités en stock],[Unités commandées],[Niveau de
réapprovisionnement], Indisponible FROM Produits"
Dim dtaProduit As New OleDb.OleDbDataAdapter(strSQL, MaConn)
Dim MapTable As Common.DataTableMapping =
dtaProduit.TableMappings.Add("Table", "Produits")
With MapTable
.ColumnMappings.Add("réf produit", "Reference")
.ColumnMappings.Add("nom du produit", "Nom")
.ColumnMappings.Add("N° Fournisseur", "Fournisseur")
.ColumnMappings.Add("code catégorie", "Categorie")
.ColumnMappings.Add("Quantité par unité", "Unite")
.ColumnMappings.Add("Prix unitaire", "Prix")
.ColumnMappings.Add("Unités en stock", "Stock")
.ColumnMappings.Add("Unités commandées", "Commande")
.ColumnMappings.Add("Niveau de réapprovisionnement", "Reappro")
.ColumnMappings.Add("Indisponible", "Indisponible")
End With
Dim dsProduit As New DataSet
dtaProduit.Fill(dsProduit)
Me.DataGrid1.DataSource = dsProduit.Tables("Produits").DefaultView
End Sub

Notions de ligne
Le contrôle Datagrid ne gère pas les lignes qu’il affiche autrement que pour la navigation. Chaque fois
que vous avez besoin de travailler sur les lignes, il vous faudra interroger / manipuler la source du datagrid, à
une exception près ; le nombre de lignes visibles peut être obtenu en interrogeant la propriété
VisibleRowCount.
Lignes affichées
Cela veut dire que pour maîtriser les lignes qui sont affichées par la grille vous n’avez que trois
possibilités.
Filtrage
La plus simple, et la seule sans risque d’ailleurs consiste à filtrer, si c’est possible, le Dataview source
par l’intermédiaire de sa propriété ‘RowFilter’.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
Dim MaVue As DataView = CType(Me.DataGrid1.DataSource, DataView)
MaVue.RowFilter = "Prix>100"
End Sub
Il est possible de créer des chaînes de filtre assez complexe et je vous conseille fortement d’utiliser cette
méthode de préférence à toutes autres.

48
Changement d’état
Un datagrid n’affiche que les lignes dont l’état est spécifié dans la propriété ‘RowStateFilter’. En
changeant l’état de la DataRowView, vous pouvez donc la supprimer de l’affichage. Par exemple
Private Sub DataGrid1_CurrentCellChanged(ByVal sender As Object, ByVal e
As System.EventArgs) Handles DataGrid1.CurrentCellChanged
Dim MaVue As DataView = CType(Me.DataGrid1.DataSource, DataView)
Dim MaLigne As DataRowView = MaVue.Item(Me.DataGrid1.CurrentRowIndex)
MaLigne.Delete()
End Sub
Comme mon Dataset n’est pas un objet connecté, cette manipulation ne se répercute pas sur ma source
de données. Néanmoins cette technique me condamne à ne pas faire d’Update pour ne pas transmettre des
modifications non souhaitées vers le SGBD.
Suppression
Le Dataview source est une collection de DataRowView qui est une représentation de la collection
DataRows de la table source. En français, cela veut dire que je peux retirer la ligne de la collection. Elle
n’apparaîtra plus dans la grille, ni nulle part d’ailleurs puisque je l’aurai supprimé. Cette technique peut
poser des problèmes en cas de rafraîchissement de la source. De plus, dans un schéma relationnel, cette
suppression peut être interdite pour le respect des contraintes d’intégrité.
Le tri
Le tri des lignes se fait toujours sur la source. Si la propriété ‘AllowSorting’ du datagrid est vraie, il est
possible de trier en cliquant sur un en-tête de colonne. Néanmoins ne perdez pas de vu que cela modifie la
propriété ‘Sort’ de la source du datagrid. Pour vous en assurer, testez le code suivant.
Private Sub DataGrid1_MouseUp(ByVal sender As Object, ByVal e As
System.Windows.Forms.MouseEventArgs) Handles DataGrid1.MouseUp
Dim hti As System.Windows.Forms.DataGrid.HitTestInfo
hti = Me.DataGrid1.HitTest(e.X, e.Y)
If hti.Type = DataGrid.HitTestType.ColumnHeader Then
MessageBox.Show(Me.DataGrid1.DataSource.sort)
End If
End Sub

Nombre de lignes
Pour connaître le nombre de lignes, il faut compter le nombre de lignes du DataView sous-jacent
Dim MaVue As DataView = CType(Me.DataGrid1.DataSource, DataView)
MessageBox.Show(MaVue.Count.ToString)

Parcourir les lignes


Je peux évidemment choisir de parcourir les lignes de la vue
Dim MaVue As DataView = CType(Me.DataGrid1.DataSource, DataView)
Dim MonEnum As IEnumerator = MaVue.GetEnumerator
Dim MaLigne As DataRow
While MonEnum.MoveNext And Not (MonEnum.Current Is Nothing)
MaLigne = MonEnum.Current.Row
End While
Mais je peux aussi utiliser le CurrencyManager issu de la grille et attaquer les cellules de la grille par
leurs coordonnées.
Dim CurrMan As CurrencyManager =
CType(Me.BindingContext(Me.DataGrid1.DataSource), CurrencyManager)
Dim cmpt As Integer
For cmpt = 0 To CurrMan.Count - 1
MessageBox.Show(Me.DataGrid1(cmpt, 1))
Next

49
Notions de colonne
Là le sujet est un peu plus complexe. Normalement le Datagrid affiche toutes les colonnes et
uniquement les colonnes de la source. Mais nous verrons plus loin, qu’il est possible de modifier cet
affichage. Les colonnes d’un Datagrid sont directement les objets DataColumn de la source de données. En
fait, sauf pour accéder au données (ou aux cellules) on manipule que très rarement directement les colonnes
du datagrid. Généralement tout le travail se fait sur la mise en forme des colonnes que nous verrons plus loin.

Notions de cellule
La grille affiche donc des cellules qui contiennent les données. Globalement, on n’utilise pas la notion
de cellules dans le datagrid, sauf pour la cellule courante. Toutefois, il est tout à fait possible de parcourir
l’ensemble de la grille cellule par cellule. En effet, le datagrid expose une propriété Item qui permet
d’atteindre une cellule à l’aide de ses coordonnées (numéro de ligne, numéro de colonne).

Navigation
C’est le premier aspect fonctionnel du contrôle. Le datagrid gère la navigation dans les cellules.
Lorsqu’il est lié à un DataViewManager, il gère aussi la navigation entre les tables et les jeux
d’enregistrements parent/enfant.
Le contrôle accepte les touches suivantes pour la navigation.
Action Raccourci
Terminer une entrée de cellule et descendre à la cellule suivante. ENTRÉE
En mode d'édition de cellule, annuler la modification de la cellule. ÉCHAP
Supprimer le caractère situé avant le point d'insertion lors de la modification
RET.ARR
d'une cellule.
Supprimer le caractère situé après le point d'insertion lors de la modification
SUPPR
d'une cellule.
Vous déplacer sur la première cellule de la ligne en cours. DÉBUT
Vous déplacer sur la dernière cellule de la ligne en cours. FIN
Sélectionner les caractères de la cellule en cours et placer le point d'insertion à
F2
la fin de la ligne. Equivaut au double click
Si le focus est placé sur une cellule, vous déplacer sur la cellule suivante de la
ligne.
TAB
Si le focus est placé sur la dernière cellule d'une ligne, vous déplacer jusqu'au
premier lien de la table enfant de la ligne et le développer.
Si le focus est placé sur une cellule, vous déplacer sur la cellule précédente de
la ligne.
Si le focus est placé sur la première cellule d'une ligne, vous déplacer jusqu'au MAJ+TAB
dernier lien développé de la table enfant ou sur la dernière cellule de la ligne
précédente.
Vous déplacer sur le contrôle suivant dans l'ordre de tabulation. CTRL+TAB
Vous déplacer sur le contrôle précédent dans l'ordre de tabulation. CTRL+MAJ+TAB
Si vous êtes dans une table enfant, vous déplacer jusqu'à la table parent. ALT+GAUCHE
ALT+BAS
Développer les liens de la table enfant. ALT+BAS développe tous les liens ou
CTRL+SIGNE PLUS
ALT+HAUT
Réduire les liens de la table enfant. ALT+HAUT réduit tous les liens. ou
CTRL+SIGNE MOINS
Vous déplacer sur la cellule non vide la plus éloignée, dans la direction de la
CTRL+FLÈCHE
flèche.
Étendre la sélection d'une ligne dans la direction de la flèche. MAJ+HAUT/BAS
Étendre la sélection jusqu'à la ligne non vide la plus éloignée dans la direction
CRTL+MAJ+HAUT/BAS
de la flèche.

50
Vous déplacer sur la première cellule en haut à gauche. CTRL+ORIGINE
Vous déplacer sur la dernière cellule située en bas à droite. CTRL+FIN
Étendre la sélection jusqu'à la première ligne. CTRL+MAJ+ORIGINE
Étendre la sélection jusqu'à la dernière ligne. CTRL+MAJ+FIN
Sélectionner la ligne en cours MAJ+ESPACE
Sélectionner toute la grille CTRL+A
Dans une table enfant, afficher la ligne parente. CTRL+PG.SUIV
Dans une table enfant, masquer la ligne parente. CTRL+PG.PRÉC
Étendre la sélection d'une page vers le bas MAJ+PG.SUIV
Étendre la sélection d'une page vers le haut. MAJ+PG.PRÉC
Appeler la méthode DataGrid.EndEdit pour la ligne en cours. CTRL+ENTRÉE
En mode édition, entrer une valeur dbnull dans une cellule. CTRL+0
La navigation est principalement le fait de l’utilisateur mais il est tout à fait possible de la gérer par le
code.
Private Sub DataGrid1_CurrentCellChanged(ByVal sender As Object, ByVal e
As System.EventArgs) Handles DataGrid1.CurrentCellChanged
Me.DataGrid1.Select(Me.DataGrid1.CurrentRowIndex)
End Sub
Ce code force la sélection complète de la ligne lors de la sélection d'une cellule.

Edition
La gestion de l’édition est assez lourde car elle peut être tantôt partie de la gestion directe de contrôle,
tantôt de la gestion de la mise en forme des colonnes. Mais commençons par le début.
Pour pouvoir faire de l'édition (ajout, suppression ou modification) il faut paramétrer la source de
données et non le contrôle. Le code suivant autorise la modification mais interdit l'ajout et la suppression.
Dim MaVue As DataView = dsProduit.Tables("Produits").DefaultView
MaVue.AllowEdit = True
MaVue.AllowDelete = False
MaVue.AllowNew = False
Me.DataGrid1.DataSource = MaVue
N.B : Si vous désirez interdire l'édition, il est plus simple de mettre la propriété ReadOnly de la grille à
Vraie.
La gestion de l'édition par la suite demande plus ou moins de travail. Du fait que le datagrid expose
directement les DataColumn, il y a contrôle du type de la donnée saisie. Si le type ne correspond pas, la grille
annule la modification sans générer d'erreur (bien qu'il soit possible de lui en faire générer une).
Néanmoins les fautes de types ne sont pas les seules possibles, et il faut bien pouvoir faire des contrôles
supplémentaires. Ainsi pour l'instant, rien ne m'empêche d'entrer un prix négatif dans ma cellule. Nous
verrons plus loin comment faire de la validation.
Le contrôle d'édition est assez complexe à réaliser en ne travaillant que sur le contrôle ou sur la source,
tel que nous allons le voir dans l'exemple suivant.
Surveiller les suppressions de ligne
Lorsque le Dataview autorise la suppression, un appui sur la touche 'Suppr' supprime la ligne (En fait la
marque comme supprimée). Pour rajouter une validation, deux approches sont possibles.
Surveillance du clavier
Dans ce cas on surveille l'utilisation de la touche de suppression.
Un code simple comme celui ci-dessous ne fonctionnera pas car l'événement ne sera pas intercepté
Private Sub DataGrid1_KeyDown(ByVal sender As Object, ByVal e As
System.Windows.Forms.KeyEventArgs) Handles DataGrid1.KeyDown
If e.KeyCode = Keys.Delete Then
… … … …
End If

51
End Sub
Il faut alors sous-classer pour surveiller le clavier, comme nous allons le voir un peu plus loin.
Surveillance de la source
On crée un gestionnaire d'événements sur ListChanged du Dataview
AddHandler MaVue.ListChanged, New
System.ComponentModel.ListChangedEventHandler(AddressOf OnListChanged)
Et on gère l'événement
Private Shared Sub OnListChanged(ByVal sender As Object, ByVal args As
System.ComponentModel.ListChangedEventArgs)
If args.ListChangedType =
System.ComponentModel.ListChangedType.ItemDeleted Then
If MsgBox("Vous avez supprimé la ligne - Confirmez",
"Suppression", MessageBoxButtons.YesNo) = MsgBoxResult.No Then
Dim MaVue As DataView = sender
MaVue.RowStateFilter = DataViewRowState.CurrentRows And
DataViewRowState.Deleted
MaVue.Item(args.NewIndex + 1).Row.RejectChanges()
MaVue.RowStateFilter = DataViewRowState.CurrentRows
End If
End If
End Sub

Evénements
Le contrôle Datagrid gère de nombreux événements, mais la plupart sont liés à la modification d'une
propriété et ne sont utilisables que dans les cas où la surveillance de cette propriété est importante. Un
événement qui est tout de même très utile est CurrentCellChange. Attention, celui-ci se déclenche après que
le changement a eu lieu. Il n'est pas possible de l'utiliser directement pour faire de la validation.
Pour pouvoir manipuler les événements plus à leur guise, de nombreux développeurs préfèrent dériver
leur propre classe Datagrid.
Dériver la grille
Dériver un contrôle se fait de la façon suivante.
Dans l’explorateur de solution choisissez ‘Ajouter une classe’
Donnez un nom à votre classe, et faites-là hériter de Datagrid (System.Windows.Forms.DataGrid de son
vrai nom)
A partir de là vous pouvez
¾ Ajouter des propriétés
¾ Ajouter des méthodes
¾ Substituer des méthodes
La substitution de méthode consiste à détourner une méthode pour utiliser votre code à la place. Il faut
évidemment pour cela que la méthode soit substituable (Overridable)
La liste de ces méthodes apparaît dans le sélecteur « Nom de la classe » de VS.NET.
Une fois votre classe complète, allez dans la feuille qui va utiliser la grille, et ajoutez un contrôle
Datagrid. Dans le code généré par le concepteur remplacez
Friend WithEvents Datagrid1 As System.Windows.Forms.DataGrid
Par
Private WithEvents Datagrid1 As NomDeVotreClasse
Et dans la routine InitializeComponent remplacez
Me.Datagrid1 = New System.Windows.Forms.DataGrid
Par
Me.Datagrid1 = New NomDeVotreClasse
Vous pouvez alors utiliser votre propre contrôle Datagrid.

52
Je crée la classe suivante qui va me permettre de gérer la suppression des lignes comme dans le cas
précédent :
Imports Microsoft.VisualBasic
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Namespace DataGridPerso
Public Class MaGrille
Inherits DataGrid
Private Const WM_KEYDOWN = &H100
Private AncienneLigne As Integer

Public Sub New()

End Sub

Protected Overrides Function ProcessDialogKey(ByVal keyData As


System.Windows.Forms.Keys) As Boolean
Dim hti As DataGrid.HitTestInfo
Dim pt As Point = Me.PointToClient(Cursor.Position)
hti = Me.HitTest(pt)
If keyData = Keys.Delete Then
If hti.Type = Me.HitTestType.RowHeader Then
If MsgBox("Vous avez supprimé la ligne - Confirmez",
"Suppression", MessageBoxButtons.YesNo) = MsgBoxResult.No Then
Return True
End If
End If
End If
End Function

End Class
End Namespace
Le sous-classement des contrôles est souvent très pratique pour ajouter des fonctionnalités qui n’existent
pas à un contrôle. Les possibilités sont très nombreuses.

Mise en forme
Bien que nous entrions dans le chapitre de la mise en forme de la grille, ce que nous allons voir dans
cette partie va dépasser de très loin ce seul aspect pour entrer véritablement dans la personnalisation du
comportement.

Au niveau du contrôle
La mise en forme au niveau du contrôle est assez sommaire. Je ne vais pas énumérer ici les possibilités
du Datagrid car vous pourrez trouver tout cela dans l’aide. Le code suivant modifie la police et la couleur de
fond des en-têtes, la couleur des lignes de séparation et colorie le fond d’une ligne sur deux.
With Me.Datagrid1
.AlternatingBackColor = Color.Brown
.GridLineColor = Color.BlueViolet
.HeaderBackColor = Color.Cyan
.HeaderFont = New Font(FontFamily.GenericSansSerif, 10,
FontStyle.Bold)
.SelectionForeColor = Color.DarkBlue
End With

53
C’est assez immonde comme résultat, mais cela vous donne un exemple. Rappelez-vous que l’on utilise
le formatage au niveau du contrôle que pour les cas simples. En effet, pour obtenir une mise en forme
avancée, nous allons utiliser les objets DatagridTableStyle et DatagridColumnStyle.

Au niveau de la table
Le contrôle Datagrid possède une collection TableStyles qui renvoie tous les objets DatagridTableStyle
de la grille. L’objet DatagridTableStyle est en fait une mise en forme de la grille. Un datagrid gère un
DatagridTableStyle par table de sa source. La liaison entre un DataTable et son DatagridTableStyle
correspondant se fait par identité des noms (nom mappé de la table est identique à la propriété
MappingName du DatagridTableStyle).
Me.Datagrid1.DataSource = dsProduit.Tables("Produits").DefaultView
Dim monGridTableStyle As DataGridTableStyle = New DataGridTableStyle
monGridTableStyle.MappingName = CType(Me.Datagrid1.DataSource,
DataView).Table.TableName
With monGridTableStyle
.AlternatingBackColor = Color.Brown
.GridLineColor = Color.BlueViolet
.HeaderBackColor = Color.Cyan
.HeaderFont = New Font(FontFamily.GenericSansSerif, 10,
FontStyle.Bold)
.SelectionForeColor = Color.DarkBlue
End With
Datagrid1.TableStyles.Add(monGridTableStyle)
Ce code fait la même chose que le précédent. En soit, il y a une parfaite identité entre les propriétés de
mise en forme du contrôle et celle du DatagridTableStyle. Ceci est normal puisque le contrôle utilise un
DatagridTableStyle par défaut. C’est celui-ci que nous avons paramétré dans l’exemple précédent. Donc ce
n’est pas à ce niveau que nous allons faire la mise en forme mais au niveau du dessous, qui est le
DatagridColumnStyle.

Au niveau des colonnes


Chaque DatagridTableStyle contient une collection ColumnStyles qui renvoie vers les objets
DatagridColumnStyle. A l’origine, un objet DatagridTableStyle contient autant d’objet DatagridColumnStyle
qu’il y a de DataColumn dans DataTable. En fait, la grille est composée de deux types de colonne qui
dérivent de DatagridColumnStyle, DataGridTextBoxColumn et DataGridBoolColumn.
Dans le cas de DataGridTextBoxColumn, chaque cellule est considérée comme un contrôle TextBox,
dans l’autre comme une case à cocher. Grâce à ces objets, nous allons pouvoir manipuler les cellules comme
des contrôles.
Formatage
Commençons par un exemple de base :
Pour créer correctement un objet DatagridColumnStyle on utilise le CurrencyManager pour récupérer
une description de propriété (PropertyDescriptor).
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
Dim MaConn As New System.Data.OleDb.OleDbConnection("Provider =
Microsoft.Jet.OLEDB.4.0;User ID=Admin;Data
Source=C:\tutoriel\nwind.mdb;")
Dim strSQL As String = "SELECT [réf produit],[nom du produit],[N°
Fournisseur],[code catégorie],[Quantité par unité],[Prix
unitaire],[Unités en stock],[Unités commandées],[Niveau de
réapprovisionnement], Indisponible FROM Produits"
Dim dtaProduit As New OleDb.OleDbDataAdapter(strSQL, MaConn)
Dim MapTable As Common.DataTableMapping =
dtaProduit.TableMappings.Add("Table", "Produits")

54
With MapTable
.ColumnMappings.Add("réf produit", "Reference")
.ColumnMappings.Add("nom du produit", "Nom")
.ColumnMappings.Add("N° Fournisseur", "Fournisseur")
.ColumnMappings.Add("code catégorie", "Categorie")
.ColumnMappings.Add("Quantité par unité", "Unite")
.ColumnMappings.Add("Prix unitaire", "Prix")
.ColumnMappings.Add("Unités en stock", "Stock")
.ColumnMappings.Add("Unités commandées", "Commande")
.ColumnMappings.Add("Niveau de réapprovisionnement", "Reappro")
.ColumnMappings.Add("Indisponible", "Indisponible")
End With
Dim dsProduit As New DataSet
dtaProduit.Fill(dsProduit)
'crée la vue source
Dim MaVue As DataView = dsProduit.Tables("Produits").DefaultView
Me.Datagrid1.DataSource = MaVue
Dim CM As CurrencyManager = CType(Me.BindingContext(MaVue),
CurrencyManager)
'création et mise en forme du TableStyle
Dim monGridTableStyle As DataGridTableStyle = New DataGridTableStyle
monGridTableStyle.MappingName = MaVue.Table.TableName
With monGridTableStyle
.AlternatingBackColor = Color.Brown
.GridLineColor = Color.BlueViolet
.HeaderBackColor = Color.Cyan
.HeaderFont = New Font(FontFamily.GenericSansSerif, 10,
FontStyle.Bold)
.SelectionForeColor = Color.DarkBlue
End With
'création et mise en forme du columnStyle du premier champ
Dim pd As PropertyDescriptor = CM.GetItemProperties()(0)
Dim MaColText As New DataGridTextBoxColumn(pd)
With MaColText
.MappingName = MaVue.Table.Columns(0).ColumnName
.Alignment = HorizontalAlignment.Center
.NullText = ""
End With
monGridTableStyle.GridColumnStyles.Add(MaColText)
Datagrid1.TableStyles.Add(monGridTableStyle)

End Sub
Si vous utilisez ce code, vous verrez que les données de la colonne référence sont bien centrées dans la
colonne. Mais vous remarquerez aussi que seule cette colonne apparaît. En effet, des lors qu'on définit les
colonnes du DatagridTableStyle, seules les colonnes ajoutées à celui-ci apparaissent.

Vous devez impérativement ajouter les objets DatagridColumnStyle à l'objet DatagridTableStyle


avant d'ajouter celui-ci à la collection TablesStyles du contrôle.
Cela nous ouvre des possibilités très intéressantes. Nous savons qu'un Dataview reprend toujours toutes
les colonnes d'un DataTable, mais nous pouvons manipuler nos objets DatagridColumnStyle pour choisir les
colonnes à afficher. De plus, les colonnes vont apparaître dans la grille dans l'ordre d'ajout des
DatagridColumnStyle dans DatagridTableStyle. Dès lors, on peut modifier aussi l'ordre des colonnes.
Regardons l'exemple suivant :
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load

55
… … … …
Dim dsProduit As New DataSet
dtaProduit.Fill(dsProduit)
'crée la vue source
dsProduit.Tables("Produits").Columns.Add("Prix_Commande",
System.Type.GetType("System.Decimal"), "Prix*Commande")
Dim MaVue As DataView = dsProduit.Tables("Produits").DefaultView
Me.Datagrid1.DataSource = MaVue
Dim CM As CurrencyManager = CType(Me.BindingContext(MaVue),
CurrencyManager)
'création et mise en forme du TableStyle
Dim monGridTableStyle As DataGridTableStyle = New DataGridTableStyle
monGridTableStyle.MappingName = MaVue.Table.TableName
With monGridTableStyle
.AlternatingBackColor = Color.Brown
.GridLineColor = Color.BlueViolet
.HeaderBackColor = Color.Cyan
.HeaderFont = New Font(FontFamily.GenericSansSerif, 10,
FontStyle.Bold)
.SelectionForeColor = Color.DarkBlue
End With
'création et mise en forme du columnStyle des champs à afficher
Dim pd As PropertyDescriptor = CM.GetItemProperties()(MesChamps.Nom)
Dim MaColText As New DataGridTextBoxColumn(pd)
With MaColText
.MappingName = MaVue.Table.Columns(MesChamps.Nom).ColumnName
.Alignment = HorizontalAlignment.Center
.HeaderText = .MappingName
End With
monGridTableStyle.GridColumnStyles.Add(MaColText)
pd = CM.GetItemProperties()(MesChamps.Commande)
MaColText = New DataGridTextBoxColumn(pd)
With MaColText
.MappingName = MaVue.Table.Columns(MesChamps.Commande).ColumnName
.Alignment = HorizontalAlignment.Right
.HeaderText = "Unités en commande"
End With
monGridTableStyle.GridColumnStyles.Add(MaColText)
pd = CM.GetItemProperties()(MesChamps.Prix)
MaColText = New DataGridTextBoxColumn(pd)
With MaColText
.MappingName = MaVue.Table.Columns(MesChamps.Prix).ColumnName
.Alignment = HorizontalAlignment.Right
.HeaderText = "Prix unitaire"
End With
monGridTableStyle.GridColumnStyles.Add(MaColText)
pd = CM.GetItemProperties()(MesChamps.Prix_Commande)
MaColText = New DataGridTextBoxColumn(pd)
With MaColText
.MappingName =
MaVue.Table.Columns(MesChamps.Prix_Commande).ColumnName
.Alignment = HorizontalAlignment.Center
.HeaderText = "Prix total"
End With
monGridTableStyle.GridColumnStyles.Add(MaColText)

56
Datagrid1.TableStyles.Add(monGridTableStyle)
MaVue.RowFilter = "Prix_Commande >0"

End Sub
J'utilise l'énumération suivante pour gérer mes colonnes
Private Enum MesChamps As Integer
Reference = 0
Nom = 1
Fournisseur = 2
Categorie = 3
Unite = 4
Prix = 5
Stock = 6
Commande = 7
Reappro = 8
Indisponible = 9
Prix_Commande = 10
End Enum
Nous voyons bien alors que le formatage de la grille est facile à réaliser.
Je vous ai dit qu'il existait un autre type de colonne pour les champs booléens. Je vais donc m'en servir
pour ajouter ma colonne 'Indisponible' à la table.
pd = CM.GetItemProperties()(MesChamps.Indisponible)
Dim MaColBool As New DataGridBoolColumn(pd)
With MaColBool
.MappingName = MaVue.Table.Columns(MesChamps.Indisponible).ColumnName
.HeaderText = .MappingName
End With
monGridTableStyle.GridColumnStyles.Add(MaColBool)
Nous allons maintenant ajuster une colonne à la largeur du plus long membre
Dim NbLigne As Int32 = CType(Me.Datagrid1.DataSource, DataView).Count
Dim MonGraphics As Graphics = Graphics.FromHwnd(Me.Datagrid1.Handle)
Dim fChaine As StringFormat = New
StringFormat(StringFormat.GenericTypographic)
Dim Largeur As Decimal, Taille As SizeF, cmpt As Integer
For cmpt = 0 To NbLigne - 1
Taille = MonGraphics.MeasureString(Me.Datagrid1(cmpt, 0).ToString,
Me.Datagrid1.Font, 100, fChaine)
If Taille.Width > Largeur Then Largeur = Taille.Width
Next
Me.Datagrid1.TableStyles(0).GridColumnStyles(0).Width = Largeur

Manipulation de cellule
Avec les objets DataGridTextBoxColumn il est donc possible de manipuler une cellule comme un objet
TextBox. Ceci permet d'aller assez loin dans les fonctionnalités.
Je reprends donc ma colonne de prix. Comme je ne souhaite pas que l'on puisse saisir des valeurs
négatives, je vais limiter le KeyPress des cellules de la colonne prix.
Dans ma définition de la colonne je rajoute un gestionnaire d’événement
pd = CM.GetItemProperties()(MesChamps.Prix)
MaColText = New DataGridTextBoxColumn(pd)
With MaColText
.MappingName = MaVue.Table.Columns(MesChamps.Prix).ColumnName
.Alignment = HorizontalAlignment.Right
.HeaderText = "Prix unitaire"
End With

57
AddHandler MaColText.TextBox.KeyPress, AddressOf MaCellKeyPress
monGridTableStyle.GridColumnStyles.Add(MaColText)
Et j’écris mon gestionnaire classique
Private Sub MaCellKeyPress(ByVal sender As Object, ByVal e As
KeyPressEventArgs)
Dim c As Char
c = e.KeyChar
If Not (Char.IsDigit(c) Or Char.IsControl(c)) Then
e.Handled = True
End If
End Sub
Il s’agit bien d’une collection de TextBox. Il est donc parfaitement possible de récupérer une cellule
comme un contrôle. En fait, on récupère des objets dérivés DataGridTextBox. Mais seule la cellule active
peut être récupérée. Ceci implique qu’une modification des propriétés de DataGridTextBox implique une
modification des propriétés de la cellule active.
Regardons l’exemple suivant :
Dim MaColText As DataGridTextBoxColumn =
Me.Datagrid1.TableStyles(0).GridColumnStyles(0)
Me.Datagrid1.CurrentCell = New DataGridCell(3, 0)
Dim MaCellText As DataGridTextBox = MaColText.TextBox
MaCellText.BackColor = Color.Green
La cellule active (troisième ligne, première colonne) change sa couleur de fond. Si vous sélectionnez
une autre cellule de la première colonne, c’est le fond de cette nouvelle cellule qui va changer de couleur. Ce
qu'il faut donc bien retenir, c'est que l'on ne programme pas une cellule par ce biais, mais le comportement
de la cellule active quand elle est dans la colonne concernée.
Ceci n’est pas vrai dans le cas des DataGridBoolColumn qui elles ne renvoient pas d'objet particulier.

Contrôles contenus
Pour favoriser la saisie, nous allons intégrer des contrôles en lieu et place de nos cellules 'TextBox'.
Comme nous avons des colonnes numériques à saisir, nous allons dériver le DataGridTextBoxColumn.
Namespace DataGridPerso
Public Class MaNumColonne
Inherits DataGridColumnStyle
Private MonNumUpDown As New NumericUpDown
Private EstEditable As Boolean

Public Sub New(ByVal Min As Decimal, ByVal Max As Decimal, ByVal


DecPos As Integer, ByVal Increment As Decimal)
MonNumUpDown.Visible = False
MonNumUpDown.DecimalPlaces = DecPos
MonNumUpDown.Increment = Increment
MonNumUpDown.Maximum = Max
MonNumUpDown.Minimum = Min
End Sub

Protected Overrides Sub Abort(ByVal rowNum As Integer)


EstEditable = False
RemoveHandler MonNumUpDown.ValueChanged, AddressOf
MonNumUpDownValueChanged
End Sub

Protected Overrides Function Commit(ByVal dataSource As


System.Windows.Forms.CurrencyManager, ByVal rowNum As Integer) As Boolean
MonNumUpDown.Bounds = Rectangle.Empty

58
AddHandler MonNumUpDown.ValueChanged, AddressOf
MonNumUpDownValueChanged
If Not Me.EstEditable Then
Return True
End If
Me.EstEditable = False
Try
Dim value As Decimal = MonNumUpDown.Value
SetColumnValueAtRow(dataSource, rowNum, value)
Catch
End Try
Invalidate()
Return True
End Function

Protected Overloads Overrides Sub Edit(ByVal source As


System.Windows.Forms.CurrencyManager, ByVal rowNum As Integer, ByVal
bounds As System.Drawing.Rectangle, ByVal [readOnly] As Boolean, ByVal
instantText As String, ByVal cellIsVisible As Boolean)
Dim Valeur As Decimal = CType(GetColumnValueAtRow([source],
rowNum), Decimal)
If cellIsVisible Then
MonNumUpDown.Bounds = New Rectangle(bounds.X + 2,
bounds.Y + 2, bounds.Width - 4, bounds.Height - 4)
MonNumUpDown.Value = Valeur
MonNumUpDown.Visible = True
AddHandler MonNumUpDown.ValueChanged, AddressOf
MonNumUpDownValueChanged
Else
MonNumUpDown.Value = Valeur
MonNumUpDown.Visible = False
End If
If MonNumUpDown.Visible Then
DataGridTableStyle.DataGrid.Invalidate(bounds)
End If
End Sub

Protected Overrides Function GetMinimumHeight() As Integer


Return MonNumUpDown.PreferredHeight + 4
End Function

Protected Overrides Function GetPreferredHeight(ByVal g As


System.Drawing.Graphics, ByVal value As Object) As Integer
Return MonNumUpDown.PreferredHeight + 4
End Function

Protected Overrides Function GetPreferredSize(ByVal g As


System.Drawing.Graphics, ByVal value As Object) As System.Drawing.Size
Return New Size(100, MonNumUpDown.PreferredHeight + 4)
End Function

Protected Overloads Overrides Sub Paint(ByVal g As


System.Drawing.Graphics, ByVal bounds As System.Drawing.Rectangle, ByVal
source As System.Windows.Forms.CurrencyManager, ByVal rowNum As Integer)
Paint(g, bounds, [source], rowNum, False)

59
End Sub

Protected Overloads Overrides Sub Paint(ByVal g As


System.Drawing.Graphics, ByVal bounds As System.Drawing.Rectangle, ByVal
source As System.Windows.Forms.CurrencyManager, ByVal rowNum As Integer,
ByVal backBrush As Brush, ByVal foreBrush As Brush, ByVal alignToRight As
Boolean)
Dim Valeur As Decimal = CType(GetColumnValueAtRow([source],
rowNum), Decimal)
Dim rect As Rectangle = bounds
g.FillRectangle(backBrush, rect)
rect.Offset(0, 2)
rect.Height -= 2
g.DrawString(Valeur.ToString,
Me.DataGridTableStyle.DataGrid.Font, foreBrush,
RectangleF.FromLTRB(rect.X, rect.Y, rect.Right, rect.Bottom))

End Sub

Protected Overloads Overrides Sub Paint(ByVal g As


System.Drawing.Graphics, ByVal bounds As System.Drawing.Rectangle, ByVal
source As System.Windows.Forms.CurrencyManager, ByVal rowNum As Integer,
ByVal alignToRight As Boolean)
Paint(g, bounds, [source], rowNum, False)
End Sub

Protected Overrides Sub SetDataGridInColumn(ByVal value As


System.Windows.Forms.DataGrid)
If Not (MonNumUpDown.Parent Is Nothing) Then
MonNumUpDown.Parent.Controls.Remove(MonNumUpDown)
End If
If Not (value Is Nothing) Then
value.Controls.Add(MonNumUpDown)
End If
End Sub

Private Sub MonNumUpDownValueChanged(ByVal sender As Object,


ByVal e As EventArgs)
Me.EstEditable = True
MyBase.ColumnStartedEditing(MonNumUpDown)
End Sub

End Class
Et l'appel dans le code se résume à
Dim MaNumCol As New DataGridPerso.MaNumColonne(0, 500, 0, 1)
With MaNumCol
.MappingName = MaVue.Table.Columns(MesChamps.Commande).ColumnName
.HeaderText = "Unités en commande"
End With
monGridTableStyle.GridColumnStyles.Add(MaNumCol)
Notez que de nombreux contrôles peuvent être intégrés avec sensiblement le même code.

Conclusion

60
Nous avons donc étudié la base de la liaison de données. Celle-ci suffit au traitement de nombreux cas,
même s'il peut être préférable parfois de travailler entièrement par le code. La forte présence de
ResumeBinding et de SuspendBinding est généralement un signe que le mode choisi n'est pas bon.
On peut donc dans un même formulaire utiliser des contrôles à liaisons simples et d'autres à liaisons
complexes, et par le jeu des relations réaliser des affichages parent / enfant.
Rappelez vous les points suivants :
o Une liaison simple met toujours en jeu une propriété et un champ. La combinaison DataSource /
DataMember permet de désigner ce champ.
o Dans les objets ADO.NET, c'est toujours un Dataview qui est le fournisseur de données.
o Le CurrencyManager permet la navigation dans les enregistrements.
Bonne programmation

61

Vous aimerez peut-être aussi