I. Avant-propos▲
Le principe des formulaires « père/fils » :
- le fils est un sous-formulaire du « père » ;
- la source du « père » et celle du « fils » ont des colonnes dont les valeurs sont communes ;
- lorsque le « père » affiche un enregistrement, le « fils » affiche uniquement les enregistrements qui correspondent aux valeurs communes.
Un exemple :
- le « père » décrit la recette ;
- le « fils » liste les ingrédients pour réaliser cette recette.
Dans le cas qui nous occupe, nous allons un peu ruser.
Au lieu de coordonner l'affichage du « père » et du « fils » d'après leur source, nous allons coordonner le « fils » sur la base de champs indépendants logés dans le formulaire « père ». (Ce dernier n'aura d'ailleurs pas de source !)
II. Prérequis▲
Avoir bien compris le mécanisme des formulaires « père/fils ». À ce sujet, vous pouvez consulter ce tutoriel Comment classer les données dans des tables liées et construire un formulaire père/fils et singulièrement son chapitre VII.
III. Comment s'y prendre▲
III-A. Le point de départ▲
Imaginez que vous disposez de ce formulaire :
Voici comment procéder pour l'utiliser comme formulaire de recherche multicritère en réutilisant, quasi telles quelles, les quelques lignes de code que nous détaillerons plus loin.
III-B. Construire le « père »▲
On incorpore autant de contrôles indépendants (des contrôles qui n'ont pas de propriété Source contrôle) que de critères de sélection souhaités.
Peu importe le type de ces contrôles : une zone de texte, une zone de liste (modifiable ou non), une case à cocher…
La seule contrainte, pour pouvoir utiliser telle quelle la proposition, c'est que le nom de ces contrôles soit composé du mot « Filtre » accolé au nom correspondant du Source contrôle du « fils » :
III-C. Incorporer le « fils »▲
Il suffit de glisser/déposer l'objet « fils » (le formulaire fVins) dans le « père » en mode construction (le formulaire fRecherche) :
À ce stade, parler de « père » et « fils » est un abus de langage, d'ailleurs si on regarde les propriétés du conteneur de fVins, on constate qu'Access n'a pas « spontanément » reconnu la filiation, comme il l'aurait fait si les sources avaient été deux tables liées.
III-D. Établir la filiation▲
Instinctivement, on se dit qu'il suffit de compléter soi-même les propriétés Champs fils et Champs pères comme ceci :
Pas si simple !
Si on procédait de la sorte, les deux formulaires seraient effectivement coordonnés et le « fils » afficherait ses enregistrements lorsque nous aurions simultanément :
[FiltreAppellation] = [Appellation] |
[FiltreCategorie] = [Categorie] |
[FiltreMillesime] = [Millesime] |
[FiltreRegion] = [Region] |
C'est le dur moment, où il faut se rappeler que si on compare Null à quoi que ce soit, la réponse est toujours « Null ». Dès lors, si un des filtres a la valeur Null, le « fils » n'affichera rien !
En fait, nous ce que nous voulons c'est :
- si un filtre est complété, il faut comparer sa valeur à celle de la source du « fils » ;
- si le filtre n'est pas complété, il faut ignorer la comparaison.
Pas moyen de communiquer cette règle lors de la création du formulaire.
Qu'à cela ne tienne, nous allons construire, à la volée, le comportement ad hoc.
L'algorithme est simple :
- au départ, les propriétés Champs fils et Champs pères sont vierges ;
- chaque fois que l'utilisateur modifiera la valeur d'un FiltreXxxx, on repèrera tous les contrôles FiltreXxxx non Null et on garnira les propriétés Champs fils et Champs pères en conséquence.
Voici le code qui se déclenchera après la mise à jour d'un de ces contrôles :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
Public
Sub
Actu
(
)
On
Error
GoTo
GestionErreurs
Dim
ctl As
Control
Me
(
sConteneur).LinkMasterFields
=
""
Me
(
sConteneur).LinkChildFields
=
""
For
Each
ctl In
Me.Controls
If
Left
(
ctl.Name
, 6
) =
"filtre"
And
Not
IsNull
(
Me
(
ctl.Name
)) Then
Me
(
sConteneur).LinkChildFields
=
Me
(
sConteneur).LinkChildFields
_
&
"["
&
Right
(
ctl.Name
, Len
(
ctl.Name
) -
6
) &
"];"
Me
(
sConteneur).LinkMasterFields
=
Me
(
sConteneur).LinkMasterFields
_
&
"["
&
ctl.Name
&
"];"
End
If
Next
ctl
Exit
Sub
GestionErreurs
:
Select
Case
Err
.Number
Case
2335
'survient à partir de la 2e affectation d'un champ fils (sans conséquence)
Resume
Next
Case
Else
MsgBox
"Erreur dans Sub Actu : "
&
Err
.Number
&
" "
&
Err
.Description
End
Select
End
Sub
Explication du code :
4-5 : on vide les propriétés Champs fils (LinkChildFields) et Champs pères (LinkMasterFields) de leur contenu précédent éventuel.
6 : on lance une boucle pour rechercher tous les contrôles du « père ».
7 -11 : pour ceux dont le nom commence par « Filtre » et qui n'ont pas la valeur Null, on complète les propriétés Champs fils et Champs pères.
Lorsque Champs pèresest encore vide, c'est-à-dire lors du 1er remplissage de Champs fils Access effectue, sans problème, le remplissage des deux propriétés.
Par contre lors de l'itération suivante, lorsqu'on veut par exemple ajouter [Categorie]; a Champs fils alors que Champs pères contient déjà [FiltreAppellation]; Access génère cette erreur :
C'est cette « fausse » erreur que l'on neutralise en 17-18.
Si vous êtes attentif, vous avez remarqué dans le code ci-dessus, que nous faisons référence au conteneur du sous-formulaire avec la syntaxe : Me(sConteneur).
Ceci est une astuce pour rendre le code plus générique : le code proposé fonctionnera quel que soit le nom que vous avez donné au conteneur (dans l'exemple, il s'appelle cntrVins)
Dans l'événement Sur ouverture du formulaire, nous avons ce code qui recherche le nom du conteneur :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
Option
Compare Database
Option
Explicit
Dim
sConteneur As
String
Private
Sub
Form_Open
(
Cancel As
Integer
)
Dim
ctl As
Control
'Recherche du nom du conteneur
For
Each
ctl In
Me.Controls
If
ctl.ControlType
=
acSubform Then
sConteneur =
ctl.Name
Exit
Sub
End
If
Next
ctl
End
Sub
On déclenche une boucle sur la collection des contrôles du formulaire jusqu'à trouver celui qui est un conteneur (code type = acSubform), on mémorise alors son nom dans la variable sConteneur définie en tête du module de classe, donc visible tant que le formulaire reste ouvert.
III-E. Encore quelques bricoles et c'est fini !▲
À l'événement Après mise à jour de chaque contrôle FiltreXxxx, nous aurons ce code qui va déclencher la Sub Actu() présentée au paragraphe précédent :
Private
Sub
FiltreAppellation_AfterUpdate
(
)
Call
Actu
End
Sub
Private
Sub
FiltreCategorie_AfterUpdate
(
)
Call
Actu
End
Sub
Private
Sub
FiltreMillesime_AfterUpdate
(
)
Call
Actu
End
Sub
Private
Sub
FiltreRegion_AfterUpdate
(
)
Call
Actu
End
Sub
C'est le seul bout de code que vous devez adapter vous-même après avoir copié/collé le code fourni dans ce tutoriel.
Et, pour le confort, on ajoute un bouton dont le clic provoque la réinitialisation des contrôles FiltreXxxx :
Private
Sub
btTout_Click
(
)
Dim
ctl As
Control
For
Each
ctl In
Me.Controls
If
Left
(
ctl.Name
, 6
) =
"filtre"
Then
Me
(
ctl.Name
) =
Null
End
If
Next
ctl
Call
Actu
End
Sub
IV. Autres tutoriels traitant du sujet sur DVP▲
RECHERCHE MULTI-CRITERE de cafeine ;
Création d'un formulaire de recherche multicritère de jeannot45 ;
Formulaire de recherche prêt à l'emploi de loufab ;
Formulaire de recherche sur la base d'une requête de votre serviteur.
V. Téléchargement▲
La base de données (Access2000) qui a servi à la mise au point de ce tutoriel est ici.
VI. Remerciements▲
À Christophe Warin (Tofalu) et à Philippe JOCHMANS qui ont corrigé et amélioré mon code.
À Arkham46 pour l'outil qui a facilité la rédaction et la mise en ligne de cet article.
À bipbip56 pour m'avoir signalé une erreur et à tee_grandbois pour l'avoir localisée.
À Philippe Duval (Phanloga) pour la correction orthographique.