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 SubC'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 SubIV. 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.











