Une méthode élégante de sélection

Dans une base Access, j’ai une table de clients. Je souhaiterais que l’utilisateur puisse consulter la liste des clients, et en sélectionner certains un par un (par des cases à cocher). Ceci afin d’enchaîner sur un traitement spécifique derrière… Comment faire ?

Première approche

Le premier réflexe consiste à ajouter, dans la table tbl Clients, un champ Oui/Non (que vous appelleriez « Sélectionné« , par exemple). De cette manière, il suffit d’afficher la table (ou une requête dérivée) pour obtenir les cases à cocher… C’est fini !

Consultez notamment cet autre article du blog pour un exemple.

Oui mais cette approche a plusieurs inconvénients :

  • Vous modifiez la structure de la table Clients pour ajouter un champ qui n’est pas informatif (le champ Sélectionné ne décrit pas le client, il sert seulement à ajouter une fonctionnalité de sélection).
  • Toutes les tables « sélectionnables » doivent être modifiées de cette manière : dans la base Vidéoclub, si vous devez permettre la sélection des acteurs, ou des réalisateurs, etc., vous allez ajouter un champ Sélectionné à toutes ces tables.
  • Qui dit que vous avez le droit d’ajouter un champ dans tbl Clients ? Notamment si c’est une table liée, appartenant à une base dorsale en réseau…
  • Et puis surtout : si la base est utilisée en réseau, que se passe-t-il si plusieurs utilisateurs effectuent des sélections en même temps ? C’est simple : le dernier arrivé écrase les sélections des autres. Effets imprévisibles garantis !

On va donc faire un tout petit peu plus compliqué, de façon à régler tous ces problèmes et obtenir une solution réutilisable…

Étape 1 : la table de sélection

Dans un premier temps, nous allons construire une table qui gèrera les sélections proprement dites.

  1. Créez une table dans votre base de données.
    Important : si votre base scindée en deux (frontale / dorsale), c’est dans la partie frontale que vous créez cette table. L’avantage étant que chaque utilisateur dispose d’une base frontale différente, ce qui lui permettra de faire ses propres sélections de manière indépendante.
  2. Ajoutez un premier champ que vous nommerez Numéro, et qui sera clef primaire de la table.
    Ce champ doit être de même type que la clef primaire de la table Clients. Dans notre exemple, la table Clients a pour clef primaire un champ Numéro Client (NuméroAuto). Notre champ Numéro sera donc de type Numérique/Entier long.
  3. Ajoutez un 2ème champ que vous nommerez Sélectionné, de type Oui/Non, de valeur par défaut Non.
  4. Enregistrez votre table, par exemple sous le nom tbl Sélections.

Complément

La table tbl Sélections pourra servir pour la sélection dans d’autres tables à condition :

  • que ces autres tables aient également une clef primaire de type NuméroAuto ou Numérique/Entier long.
  • que les sélections sur ces autres tables ne se font pas simultanément (sinon, prévoyez autant de tbl Sélections spécialisées).

Étape 2 : la requête de sélection

Il faut maintenant relier la table des clients à celle des sélections que vous venez de créer. Donc il nous faut une requête !

  1. Créez une nouvelle requête à partir des tables tbl Clients et tbl Sélections.
  2. En haut de la requête, glissez le champ Numéro Client sur son équivalent Numéro. Vous reliez ainsi les 2 tables.
  3. Placez sur la grille de requête tous les champs qui vous intéressent. Le champ Sélectionné de la table tbl Sélections doit bien sûr en faire partie.
  4. Enregistrez cette requête (par exemple : rqt Sélection Clients).


Pour l’instant, si vous passez en mode Feuille de données, vous n’obtenez aucune ligne de résultat. Tout simplement parce que vous avez tracé une jointure interne entre les 2 tables, et qu’il n’y a aucune correspondance strictes entre les 2 tables (la table tbl Sélections est encore vide). C’est normal !

Étape 3 : le formulaire de sélection

Construisez un formulaire tabulaire à partir de la requête rqt Sélection Clients. Ici, vous designez comme vous voulez !

Étape 4 : le code VB d’initialisation

Pour l’instant, tant que tbl Sélections ne contient aucun enregistrement, la requête et le formulaire n’affichent rien. Commencez par recopier tout ce qui suit dans un module standard de la base de données :

Ensuite, ouvrez votre formulaire en mode Création, et programmez l’événement Sur ouverture comme ceci :

Les arguments de la procédure InitialiserSelection sont :

  • le nom de la table dont on souhaite sélectionner les lignes
  • le nom du champ qui fait office de clef primaire dans cette table (on ne gère pas les clefs primaires composées).
  • le nom de la table qui gère les sélections (ça permet de faire évoluer le système, si vous aviez besoin de plusieurs de ces tables). Ce paramètre peut être omis si la table que vous avez créée à l’étape 1 s’appelle tbl Sélections.
  • une clause Where optionnelle qui permet de filtrer les enregistrements à sélectionner (voir plus loin).

Étape 5 : les finitions

Il ne reste plus qu’à programmer les boutons du formulaire pour que le tout soit facile à utiliser. Voici les 3 événements Sur clic des boutons Fermer, Tout sélectionner et Tout désélectionner :

Note
Si votre table de sélection ne s’appelle pas tbl Sélections, vous devez passer son nom aux commandes ToutSelectionner et ToutDeselectionner, ci-dessus. Ça donnerait : ToutSelectionner "Nom de mon autre table de sélection"

Étape 6 : quelques optimisations

Imaginez que vous devez sélectionner quelques clients dans une liste de 10000. Notre méthode va recopier à chaque fois les 10000 numéros de clients dans la table tbl Sélections. On peut faire un peu mieux, par exemple pour permettre la sélection des clients de Paris seulement…  C’est ici qu’intervient le 4ème argument de la procédure InitialiserSelection : placez-y un filtre SQL (sans le mot WHERE) qui va limiter la liste. Par exemple :

Si vous êtes débutant, vous pouvez laisser ce 4ème argument de côté, ça marchera quand même !

Exploiter les lignes sélectionnées

A ce stade, tout le mécanisme de sélection est en place. Mais comment exploiter les enregistrements sélectionnés ? Ben… il y a plusieurs solutions… (rappelez-vous que les clients sélectionnés sont identifiables dans la table tbl Sélections, ou dans la requête rqt Sélection Clients).

Si vous êtes plutôt « visuel » :

…vous pouvez construire une requête à partir de la 1ère requête (celle que vous avez appelée plus haut rqt Sélection Clients), et appliquer au champ Sélectionné le critère Vrai. De cette manière, vous récupérez les clients sélectionnés pour un autre traitement.

Si vous êtes plutôt « code » :

…vous pouvez directement exploiter les clients sélectionnés. Voici par exemple le détail du bouton OK, qui ouvre une planche d’étiquettes. Cette planche d’étiquettes est un état construit sur la requête rqt Sélection Clients. Dans cet exemple, les étiquettes ne sont pas filtrées au départ, mais c’est le DoCmd.OpenReport qui applique le filtre, à l’ouverture de l’état :

La vidéo associée à cet article vous montre le résultat final. A vous de jouer !

Vous aimerez aussi...

40 réponses

  1. PatShaker dit :

    Bonjour Hervé,

    J’utilise ton code et ça fonctionne A1. Je voudrais que les lignes sélectionnées soient ajoutées à une table « TblArchive2015 ». En fait j’aimerais que ça se fasse automatiquement via le bouton que tu as codé « état étiquette ».

    Comme tu peux le constater je ne suis pas très calé en VBA ou même en access.

    Merci à l’avance!

    • Hervé Inisan dit :

      Sans trop de VBA, il doit être possible de créer une requête pour n’afficher que les lignes sélectionnées (voir le paragraphe « Si vous êtes plutôt visuel »).
      Et de transformer ensuite cette requête en requête Ajout (pour ajouter certains champs de la requête à tblArchive2015).

  2. KIKAI dit :

    Bonjour,
    J’aimerai savoir s’il est possible d’affecter une valeur décimale à une option car lorsque je rentre la valeur 0.5 ou 0,5 il me met 0 automatiquement.

  3. Daniel dit :

    Bonjour,
    Il y a bien une relation 1 contact -> n projets
    C’est bien le contact qui est l’objet de ma sélection
    J’ai bien une requêt de regroupement avec le statut et j’arrive bien à créer une liste de mes contact sans doublons de cette manière, sauf que… dès que j’ai une requête de regroupement dans ma requête finale je ne peux plus sélectionner individuellement les contacts.

    • Hervé Inisan dit :

      C’est normal effectivement : une requête de regroupement est une requête de synthèse qui « perd » les lignes d’origine. Elle ne peut pas être mise à jour.
      Il faut faire en sorte que la requête de contacts intègre la valeur de regroupement qui va bien, sans faire elle-même de regroupement.

  4. Sabrina dit :

    Bonjour,
    Merci pour ce tuto.
    Concernant les boutons tout sélectionner/déselectionner, comment faire pour que si l’on filtre sur un champ le code agisse uniquement sur ce qui est affiché dans le formulaire et non sur toute la table.
    Pat avance merci.

    • Hervé Inisan dit :

      Si c’est un filtre par formulaire, on peut le récupérer par Me.Filter. A vue de nez, il faudrait aménager la phrase SQL du bouton « Tout sélectionner » pour intégrer ce Me.Filter dans une condition WHERE. Du genre :

      A adapter parce que Me.Filter ne marchera pas dans un module standard.

      Du coup, ça implique aussi de changer la table strTableSelection du UPDATE, puisqu’actuellement celle-ci n’a pas les champs à filtrer. Je n’ai pas eu le temps de tester, mais sur le principe, en remplaçant strTableSelection par le nom de la requête Sélection (dans l’ex. : rqt Sélection Clients), ça pourrait fonctionner.

      • Sabrina dit :

        Merci Hervé pour ta réponse et surtout ta réactivité.
        J’ai testé ta proposition mais j’ai un message d’erreur : utilisation incorrecte du mot clé Me

        Mon code :
        Sub ToutSelectionner1( _
        Optional ByVal strTableSelection As String = « T_suivi_reparation »)

        CurrentDb.Execute « UPDATE [ » & T_suivi_reparation & « ] SET [Réparé ?] = True WHERE  » & Me.Filter
        End Sub

        • Hervé Inisan dit :

          Oui : comme je le disais, Me ne fonctionne pas dans un module standard. Il faudrait le remplacer par ceci :

          Mais le problème de ces rustines est qu’elles rendent le code du module standard très dépendant du formulaire. Donc ça gênera une réutilisation du même code plus tard. Ça peut dépanner dans un premier temps, mais il faudrait penser plus générique à terme. 😉

          • Daniel dit :

            Bonjour,
            J’ai le même problème que Sabrina, et l’utilisation de Forms![Nom du formulaire].Filter me donne le message : « erreur d’execution 3061 trop peu de paramètres. 1 attendu » Faut-il passer par des filtres prédéfinis pour l’utilisateur pour créer un nouveau .Form.RecordSource?
            Je vais chercher de ce côté, mais avant, je dois résoudre un autre problème dont je ne trouve pas la solution : la liste qui va servir pour la sélection doit déjà être une filtrée sur le critère suivant : Tous les contacts ayant une sous-fiche Projet avec un statut « X » le tout sans doublon de contact.
            La requête regroupement me permet de faire le filtrage, mais je ne peux pas l’utiliser pour la sélection (on ne peut que tout sélectionner ou déselectionner).

          • Hervé Inisan dit :

            Je n’ai pas la structure des tables (Contacts/Projets…), mais j’imagine qu’il y a une relation 1:n entre Contacts et Projets ?
            Et c’est la table Contacts qui sert pour la sélection ?

            Dans ce cas, il faudrait sans doute, d’une manière ou d’une autre, que le statut figure dans la requête qui sert de source au formulaire. Peut-être lier la requête de regroupement à cette requête source (mais difficile de se prononcer sans connaître le projet).

  5. Hervé Inisan dit :

    mel > Le bouton peut faire quelque chose comme ça (avec ma requête d’exemple) :

  6. mel dit :

    Merci herve, apres selection comment supprimer a travers un bouton supprimer ?

  7. Hervé Inisan dit :

    daniel87 > Les champs Date d'envoi et Suivi figurent dans la même requête ? A savoir : sur le formulaire de sélection ?

  8. daniel87 dit :

    Bravo et un grand merci pour le code concernant ‘’une méthode élégante de sélection’’.
    Je l’ai adaptée dans un formulaire servant de base à un publipostage dans word, piloté depuis Access 2010.
    Depuis le bouton ‘’tout sélectionner’’, je souhaiterais alimenter un sous formulaire ‘’suivi’’, ayant un champ ‘’Date’’ et un champ ‘’Suivi’’, ou seraient renseignés la date d’envoi et le suivi, par exemple ‘’Courrier envoyé’’, pour chaque enregistrement sélectionné du formulaire.
    J’y arrive un par un pour chaque enregistrement du formulaire, mais pour 100 enregistrements sélectionnés ou plus, c’est fastidieux. Et là je bloque.
    Vos lumières me seraient bien utiles pour résoudre ce problème.
    Merci par avance et encore bravo pour vos explications claires, concises et surtout qui fonctionnent à chaque fois.

  9. christophe vdw dit :

    bonsoir
    oui bien sure!!!!!!!
    une zone de liste est plus adaptée, tous les enregistrement restent visible.
    j’avais trouver une « bidouille »:
    sur événement click de la case a cocher
    je lance une requette mise a jour qui désélectionne toute les case a cocher
    puis je dit que que la case =vrai (Me![nom_du_champ=-1)
    MERCI ENCORE

  10. Hervé Inisan dit :

    christophe vdw > Pour sélectionner un seul enregistrement à la fois, je ferais plus simple : une liste déroulante de recherche devrait faire l’affaire. Quel est l’objectif ?

  11. christophe vdw dit :

    bonjour
    methode tout a fait simple et efficace!!!!merci
    sauf que je veut pouvoir séléctionner qu’un seul enregistrement a la fois?
    comment faire pour remplacer la « case a cocher » par un « bouton a bascule »?
    merci

  12. Hervé Inisan dit :

    Arnaud > Pas forcément, mais je conseillerais. La table de sélection peut être réutilisée à condition :

    1. Que le type de données de la clef (Numérique dans mon exemple) soit compatible avec la table à rattacher.
    2. Que plusieurs sélections (sur plusieurs tables) ne se fassent pas en simultané (sinon, effectivement, ça ne marchera pas).
  13. Arnaud dit :

    Question bête :
    doit on créer autant de table de sélections que de tables avec lesquels on travaille ?

    En fait j’utilise cette technique pour faire des validations par lot :
    J’archive dans un premier temps ma table produits vers une table archive
    je dois penser aussi à désarchiver
    et enfin je souhaite valider certains enregistrements par lot également…

  14. Hervé Inisan dit :

    BL > Je pense visualiser effectivement. C’est un problème malheureusement dû au formulaire continu et au déclenchement de l’événement Current. Il n’y a pas de solution à ce comportement (si ce n’est déplacer les 3 objets en en-tête ou pied de formulaire, 3 objets uniquement en tout, au lieu de 3 par ligne).

  15. BL dit :

    Bonjour,

    J’ai un problème, sur base d’une méthode de sélection similaire en plusieurs points.

    Je précise que l’objectif est de créer une sorte de webshop (enfin, dans l’idée), mais sur Access, avec un peu de VBA.

    Je m’explique : j’ai un formulaire continu, avec des cases à cocher (contrôles nommés Part_Selection) pour chaque enregistrement, et le champ de table pour la case (nommé Part_Selection aussi) qui va bien avec. Jusque là, tout va bien.

    Toutefois, j’ai aussi 3 contrôles (2 boutons : + et -, et un champ Quantity relié à la même table, qui apparaît donc en zone de texte nommée Quantity sur le formulaire) qui doivent s’afficher uniquement si la case de l’enregistrement correspondant est cochée, et ce, pour chaque enregistrement.

    En somme, je souhaiterais que ces 3 contrôles (placés visuellement juste à côté de la case) soient visibles uniquement pour les enregistrements pour lesquels la case est cochée : 1 case cochée parmi toutes = l’apparition des 3 champs juste à côté, la case décochée = masquage des 3 champs.

    Seulement voilà, pour le moment, je n’ai réussi qu’une chose : sur coche/décoche d’une seule case parmi toutes (quelle et ou qu’elle soit dans le formulaire, d’ailleurs), il m’affiche/me masque l’ensemble des 3 contrôles (= pour tous les enregistrements), et c’est donc là mon problème.
    Visualisez-vous la chose, et auriez-vous une idée qui pourrait m’aider ?
    Par avance grand merci 🙂

  16. Hervé Inisan dit :

    jeromehej > Pour la 2ème question : il ne peut pas y avoir un module qui porterait le nom d’un bloc VBA, et qui du coup créerait un conflit ?

  17. Hervé Inisan dit :

    jeromehej > L’ajout d’un champ dans la table de sélections, pour différencier les tables, peut être une bonne idée. Il faudrait vérifier s’il n’y a pas d’effet de bord sur la jointure (dans la requête), et aménager la requête pour tenir compte du nom de champ. Il faudrait du coup également adapter le code VBA pour que les sélections soient paramétrables sur le nom de table.

    A noter aussi que le champ Numéro ne peut plus être clef primaire dans ce cas (puisqu’il pourrait contenir plusieurs valeurs similaires provenant de tables différentes). Ça veut dire que la clef deviendrait Numéro + Nom de table. D’où ma première remarque sur la jointure…

  18. jeromehej dit :

    Bonsoir,
    Je crois avoir suivi correctement les instructions ci-dessous et j’obtiens le message suivant (sous Access 2010) :
    « Erreur de compilation: Variable ou procédure attendue, et non un module »
    Puis quand je clique sur « ok » du message d’erreur, il affiche surligné en jaune « Private Sub Form_Open(Cancel As Integer) ».
    Auriez vous un conseil pour que je trouve d’où vient l’erreur svp ?

  19. jeromehej dit :

    Bonjour,

    Je viens de découvrir votre site et je vous dis tout d’abord un grand bravo pour le partage de vos connaissances et expériences. Je vais certainement reprendre certaines de vos astuces pour refaire une base existante que je reprends à zéro.

    Pour revenir à cet article, vous sites « que les sélections sur ces autres tables ne se font pas simultanément (sinon, prévoyez autant de tbl Sélections spécialisées). ». Ne pourriez vous pas ajouter un champs « table » dans la table de sélection, champs dans lequel on précise la table qu’on a indiqué dans l’instruction  » InitialiserSelection « tbl Clients », « Numéro Client », « tbl Sélections » « . Ensuite pour le retraitement des enregistrements sélectionnés on met une condition « si champs « table » = bidule alors ….. Qu’en dites vous ? Y voyez vous des inconvénients ?

    Je propose cela car j’aime simplifié la maintenance d’une base de données en créant des codes qui servent à plusieurs endroits de la base (comme par exemple votre numérotation personnalisée des clés de tables). S’il pouvait n’exister qu’une table de sélection pour l’ensemble de la base, çà serait bien.

    Je pense à cela car dans la vision que j’ai dans ma nouvelle base refaite je souhaite par exemple ouvrir un formulaire « factures » dans lequel j’ouvrirais le formulaire « fournisseurs » afin de sélectionner celui qui m’a envoyé la facture ; mais si le fournisseur n’existe pas je le crée directement de ce dernier formulaire ouvert en ouvrant lors de la création le formulaire « adresse » afin de sélectionner l’adresse correspondant au fournisseur à créer. Du coup, dans la table sélection je peux avoir de sélectionné en même temps des fournisseurs mais aussi des adresses. Et ceci n’est qu’un exemple car il peut y avoir plus.

    Bien à vous.

  20. Hervé Inisan dit :

    Nico > Il faudrait que l’événement « Sur activation » (Form_Current) réactive les champs masqués.

  21. Nico dit :

    Bonjour,
    Je ne savais pas où poster ceci mais ça concerne mon formulaire. J’ai plusieurs champs auxquels sont affectés des macros rendant certains autres champs invisibles sous conditions. Mon problème est que, quand je passe à l’enregistrement suivant, les éléments rendus invisibles lors de l’enregistrement précédent ne réapparaissent pas ! Je suis obligé de fermer puis rouvrir le formulaire pour que tous mes champs soient accessibles, et pour la saisie, c’est pas génial.
    En somme, je souhaiterais que mon formulaire se « ré-actualise » à chaque enregistrement.

    Merci d’avance pour votre aide !

  22. Hervé Inisan dit :

    Jean Bernard > Le « surlignage » de la ligne sélectionnée se fait avec la mise en forme conditionnelle. Je tâcherai de publier un article là-dessus quand j’aurai un moment.

  23. Jean Bernard dit :

    Bonjour,
    Merci beaucoup pour la méthode, c’est vraiment très efficace. Je souhaiterai savoir comment faire pour que la ligne selectionnée soit allumée?

    Merci d’avance.

  24. Hervé Inisan dit :

    David > Normalement, la concaténation par & fonctionne bien en VBA. Quel est ton bout de code qui ne fonctionne pas ?

  25. DAVID dit :

    Bonjour Hervé,
    je trouve la méthode de sélection sur formulaire plutôt pratique et j’aimerais m’en inspirer pour faire du mailing sélectif, je m’explique :
    j’ai un formulaire principal dans lequel j’ai un champs texte destiné à recevoir la liste des mails correspondant aux enregistrements sélectionnés dans un sous-formulaire ; à chaque fois que je sélectionne un contact (par ex par double clic)le mail associé vient se concaténer avec un point virgule dans le champs du formulaire principal et lorsque j’ai fini je copie la liste concaténée dans le champs destinataire du message electronique (Outlook par automation).
    La concaténation classique & en vba ne semble pas fonctionner et je suis un faux débutant en vba.
    Si tu pouvais m’éclairer un peu ça serait gentil.

    Merci

  26. Hervé Inisan dit :

    jmorsay > Je vois un peu le problème : une fois le champ calculé ajouté, j’imagine que la requête de base (celle que j’appelle rqt Sélection Clients dans mon exemple) n’est plus accessible en écriture ? Ça peut se produire de plusieurs manières : selon le type de jointure effectué, ou si la requête a été transformée en requête de regroupement, par exemple.

    D’une manière générale, il faut (pour ce cas d’exploitation) que la requête puisse être mise à jour.

  27. jmorsay dit :

    Bonjour,
    J’utilise cette élégante méthode de sélection de produits pour la gestion d’une épicerie sociale. Mais je n’arrive pas à afficher le stock disponible en même temps que les produits dispos. Dès que je rajoute cette info (calculée par une requête)je ne peux plus cocher de case, le formulaire part en traitement et rien (je pense qu’il reboucle sur le calcul du stock). Mon formulaire est alimenté par une requête qui contient les produits choisis et disponibles (ce fameux stock dispo) mais pas le droit de l’afficher !!
    Merci de votre aide, et de tous ces précieux conseils que vous nous apportez.
    Cordialement. Jmorsay

  28. kadomino dit :

    Milles excuses. J’avais mis dans ma qry le champ « numero » en lieu et place de « Selectionne »
    Tout fonctionne impecablement.

  29. kadomino dit :

    Bonjour,
    J’ai testé votre application mais lors de la commande du bnt « ToutSelectionner » j’ai l’erreur .3114′ : Erreur de syntaxe dans l’instruction UPDATE.

    CurrentDb.Execute « UPDATE [ » & strtblSelections & « ] SET [Selectionne] = True; »

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *