Classe M.E.L.A. (C.R.U.D.) avec Microsoft ACCESS

Création d'une classe M.E.L.A. : Création et mise en œuvre d'une classe de Modification, Effacement, Lecture, Ajout.

Fabrice Constans

Certaines opérations sont redondantes dans une IHM. La gestion des fiches en est une. Au travers de ce tutoriel, nous allons créer et mettre en œuvre une classe qui prendra en compte un système de navigation et de manipulation des fiches.

Commentez cet article : Commentez

- Titre : Classe M.E.L.A. (C.R.U.D.) avec Microsoft ACCESS

- Auteur : Fabrice Constans

- Parution : 27 septembre 2011

- Mise à jour : 29 septembre 2012

- Licence : Copyright ® 2011 Fabrice Constans. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- I-A. Mise en garde

- II. Analyse du besoin

- II-A. Modification

- II-B. Effacement

- II-C. Lecture

- II-D. Ajout

- II-E. Copie

- III. Formulaire et boutons

- III-A. Le formulaire

- IV. La classe

- IV-A. Créer la classe

- IV-B. Déclaration

- IV-C. Propriété Form

- IV-D. La fonction d'instanciation

- V. Référencer la bibliothèque

- VI. Le formulaire

- VII. Les méthodes de la classe

- VII-A. Verrouiller les contrôles

- VII-B. Créer

- VII-C. Modifier

- VII-D. Enregistrer

- VII-E. Annuler

- VII-F. Supprimer

- VII-F-1. Cas spécial la procédure TraitementApresSuppression

- VII-G. Copier

- VIII. La navigation

- VIII-A. Vers la fiche précédente

- VIII-B. Vers la fiche suivante

- VIII-C. Rechercher

- VIII-D. Fermeture du formulaire

- IX. Piloter un autre formulaire

- X. Conclusion

- XI. Incompatibilité

- XII. Liens utiles

I - Introduction

M.E.L.A., ou C.R.U.D. en anglais est l'acronyme de Modification, Effacement, Lecture, Ajout (Create, Read, Update, Delete). Il s'agit des actions de création, suppression, modification et enregistrement des données d'un formulaire. Toutes les applications qui gèrent de l'information sont équipées d'un système M.E.L.A.

La plupart des applications ACCESS possèdent un M.E.L.A. par formulaire. Qu'il ait été dupliqué ou créé pour chacun d'eux, il représente, en phase de conception, une charge importante.

Dans ce tutoriel, nous allons créer un M.E.L.A. unique et générique qui s'adaptera à tous les formulaires d'une application via une simple déclaration. Pour certains formulaires le M.E.L.A. devra être complété au niveau du formulaire par des procédures spécifiques, nous montrerons aussi comment les mettre en œuvre.

I-A - Mise en garde

Ce tutoriel nécessite une bonne connaissance de VBA et du fonctionnement des formulaires en général.

Il a été constaté que certains types de champs spécifiques comme les Multivalués (Pièces jointes) provoquent des dysfonctionnements de la classe, notamment lors de l'enregistrement.

II - Analyse du besoin

Le besoin est décrit en partie par l'acronyme.

II-A - Modification

Dans les formulaires ACCESS, on retrouve souvent des contrôles déverrouillés. C'est la méthode basique qui ne nécessite pas de programmation, mais qui pose de réels problèmes lors de fausses manipulations. Cette méthode étant insuffisante pour une application professionnelle, nous utiliserons un système de verrouillage/déverrouillage des contrôles avec la propriété Enabled ouLocked.

II-B - Effacement

L'effacement d'un enregistrement se fait par la commande Runcommand acCmdDeleteRecord.

II-C - Lecture

La lecture ne comprend que le déplacement entre enregistrements.

Deux boutons permettent de le faire : le bouton précédent et le bouton suivant. Bien qu'il existe les méthodes MovePrevious et MoveNext dans l'objet Recordset, il est conseillé de passer par la commande GotoRecord habituelle.

En effet, les méthodes Move ne sont pas opérationnelles lorsque le curseur est positionné sur le NewRecord.

Vous pouvez également créer des boutons pour aller à la première et la dernière fiche.

II-D - Ajout

Pour ajouter une fiche, il faut se placer sur le nouvel enregistrement avec la méthode AddNew.

II-E - Copie

Pour copier une fiche, on utilise le RecordsetClone comme source dont on duplique le contenu dans le Recordset.

III - Formulaire et boutons

Maintenant que nous avons déterminé les grandes fonctionnalités nécessaires pour bâtir notre M.E.L.A., nous allons réaliser un formulaire type dans une base annexe.

Ouvrons une nouvelle base et ajoutons une table et un formulaire basé sur celle-ci.

III-A - Le formulaire

La propriété du formulaire Avec Module doit être réglée sur Oui.

Nous allons positionner les nouveaux boutons dans son en-tête.

Les boutons :

Nom

Libellé

btnPrecedent

&Précédent

btnSuivant

Sui&vant

btnCreer

&Créer

btnEnregistrer

&Enregistrer

btnModifer

&Modifier

btnCopier

&Modifier

btnSupprimer

&Supprimer

btnAnnuler

&Annuler

btnFermer

&Fermer

On ajoute la liste déroulante LmRechercher qui permettra de faire des recherches rapides. Cette liste est composée d'une colonne cachée contenant l'ID unique que toute table doit posséder et d'une ou plusieurs autres colonnes contenant les données utilisateur d'identification.
Dans la zone de détail, il est possible que vous souhaitiez garder des contrôles actifs. C'est le cas dans l'exemple fourni en téléchargement du bouton Envoyer Email. Dans ce cas vous devez mettre le mot NOLOCK dans la propriété Remarque de ces contrôles.
Le nom des contrôles (boutons, zone de liste) doit absolument être respecté.

Voici le résultat.

Le bouton Enregistrer est positionné au-dessus du bouton Modifier.

IV - La classe

Pour réaliser notre M.E.L.A. générique, nous allons créer une classe. La classe a plusieurs avantages, son installation est rapide sur les formulaires, et les évolutions touchent l'ensemble de l'application sans autre modification qu'elle-même.

L'interaction entre une classe et un objet doté d'événements comporte des règles précises qu'il vaut mieux bien connaitre avant de se lancer.

Pour que la classe gère les événements d'un objet, il faut :

- qu'il dispose d'événements ;

- que les événements utilisés soient activés en réglant leur propriété sur [Event Procedure] ou [Procédure événementielle] ;

- que la variable qui le représente soit déclarée avec la clause WithEvents.

Les propriétés événementielles qui seront utilisées peuvent être réglées directement dans la classe comme nous le verrons plus tard.

La procédure événementielle de l'objet n'a pas forcément besoin d'exister explicitement, cependant si elle est présente, son code sera exécuté avant celui de la classe.

Par exemple, si du code existe sur l'événement Sur Clic à la fois dans l'objet et dans la classe, c'est toujours celui de l'objet qui sera exécuté en premier.

IV-A - Créer la classe

Créons un nouveau fichier nommé MELA.accdb.

Pour créer un module de classe, il faut aller dans le ruban Créer, Autre, Macro, Module de classe. L'éditeur VBE s'ouvre.

Les propriétés du module de classe sont réglées ainsi :

PublicNotCreatable permet d'utiliser cette classe à l'extérieur de sa base moyennant un petit artifice.

Une classe publique placée en bibliothèque est visible dans toutes les bases depuis laquelle elle est référencée. Cependant son instanciation est impossible.

Schématiquement, cela veut dire que la classe se trouvant dans une base bibliothèque dûment référencée est visible de la base principale, mais ne peut être instanciée. En effet, le périmètre d'action de l'instruction de déclaration New est limité à la base dans laquelle elle est exécutée.

Une base A voit une classe de la base B référencée comme bibliothèque, mais est incapable de l'utiliser.

Un artifice consiste à appeler une fonction de la bibliothèque qui se chargera de déclarer la classe et d'en renvoyer l'instance.

IV-B - Déclaration

Les variables d'objets de la classe sont déclarées privées et déclencheuses d'événements (WithEvents).

'---------------------------------------------------------------------------------------
' Module    : cBoutonNav
' Author    : fabrice CONSTANS MVP - 2008 - 2011
'             Developpez.com (pseudo : Loufab)
' Date      : 23/09/2011,29/09/2012
' Purpose   : Classe MELA (CRUD)
'             gestion de Modification, Effacement, Lecture, Ajout dans un formulaire
'
' Copyright : Vous pouvez utiliser cette classe et la modifier dans le cadre privé,
'             professionnel et associatif. Vous devez laisser le nom de l'auteur.
'             Vous ne pouvez pas la distribuer sous quelque forme que ce soit
'                  sans l'autorisation écrite de l'auteur.
'            fabrice.constans@wanadoo.fr
' Merci à Micniv et Robyseb (developpez.com) pour les corrections apportées au code
'---------------------------------------------------------------------------------------

Option Compare Database
Option Explicit

Private WithEvents oForm As Form

Private WithEvents btnCreer As CommandButton
Private WithEvents btnModifier As CommandButton
Private WithEvents btnEnregistrer As CommandButton
Private WithEvents btnCopier As CommandButton
Private WithEvents btnAnnuler As CommandButton
Private WithEvents btnSupprimer As CommandButton
Private WithEvents btnPrecedent As CommandButton
Private WithEvents btnSuivant As CommandButton
Private WithEvents btnFermer As CommandButton
Private WithEvents LmRechercher As ComboBox
Private vDefaultControlName As String
Private vIdCurrentRecord As Long

La variable vDefaultControlName permet de stocker le nom d'un contrôle. Elle sera utilisée ultérieurement pour positionner le focus sur un contrôle lors de certaines actions.

La variable vIdCurrentRecord permet de stocker l'ID unique de l'enregistrement courant. Nous verrons lors de la suppression comment elle peut être utilisée.

IV-C - Propriété Form

La première propriété à créer initialise le formulaire et les boutons du M.E.L.A.

Cette propriété publique sera appelée depuis chaque formulaire bénéficiant du M.E.L.A.

Public Property Set Form(objForm As Form)
' initialisation du formulaire à gérer
On Error GoTo errSub

Dim ctrl As Control
Set oForm = objForm
' parcourt l'entete de formulaire
For Each ctrl In oForm.Section(acHeader).Controls
If TypeOf ctrl Is CommandButton Then
' on active l'événement
ctrl.OnClick = "[Event Procedure]"
Select Case ctrl.Name
Case "btnCreer"
Set btnCreer = ctrl
Case "btnModifier"
Set btnModifier = ctrl
Case "btnEnregistrer"
Set btnEnregistrer = ctrl
Case "btnCopier"
Set btnCopier = ctrl
Case "btnSupprimer"
Set btnSupprimer = ctrl
Case "btnPrecedent"
Set btnPrecedent = ctrl
Case "btnSuivant"
Set btnSuivant = ctrl
Case "btnFermer"
Set btnFermer = ctrl
Case "btnAnnuler"
Set btnAnnuler = ctrl
Case Else
' non géré
End Select
End If
'Cas spécial pour la liste de recherche
If ctrl.Name = "LmRechercher" Then
Set LmRechercher = ctrl
ctrl.AfterUpdate = "[Event Procedure]"
End If
Next
...

L'objet formulaire est stocké dans la variable objet oForm tandis qu'un premier For Each parcourt la section d'en-tête de formulaire (acHeader) pour déclarer chaque bouton de commande (CommandButton). Pour chaque bouton de commande, la propriété événementielle OnClick est activée.

Chaque bouton de commande possède sa propre variable.

Le deuxième For Each parcourt la section de détail (acDetail) à la recherche du contrôle dont l'index de tabulation (TabIndex) est égal à 0. Ce contrôle capturé recevra le focus au moment du passage en saisie.

' fixe le contrôle par défaut
For Each ctrl In oForm.Section(acDetail).Controls
' c'est un contrôle avec une propriété TabIndex
If Not (TypeOf ctrl Is Label Or TypeOf ctrl Is Line Or TypeOf ctrl Is Image Or _
TypeOf ctrl Is CustomControl Or TypeOf ctrl Is Page Or _
TypeOf ctrl Is PageBreak Or TypeOf ctrl Is Rectangle) Then

If ctrl.TabIndex = 0 Then
DefaultControlName = ctrl.Name
Exit For
End If

End If
Next

oForm.OnCurrent = "[Event Procedure]"
Set ctrl = Nothing
Exit Property
errSub:
Debug.Print Err.Number & " -- " & Err.Description
Resume Next
End Property

La propriété événementielle OnCurrent du formulaire est également activée.

Chaque fois qu'on doit ajouter un bouton de commande ou autre dans le M.E.L.A., on le fait depuis cette procédure.

Le Get sur la propriété Form permet d'accéder à l'objet formulaire. Nous pourrions nous passer de cette propriété et y accéder en utilisant l'objet oForm.

Private Property Get Form() As Form
' renvoie l'objet formulaire
Set Form = oForm
End Property

Le nom des procédures événementielles de l'objet Form est toujours formé avec le nom de la variable (oForm), jamais avec le Get (Form).

Public Sub Form_Current()

Ceci ne fonctionne pas dans la classe.

Alors que :

Public Sub oForm_Current()

Cette syntaxe fonctionne dans la classe.

Il ne faut pas oublier de déclarer le « Getter » et « Setter » de la propriété DefaultControlName.

Public Property Get DefaultControlName() As String
' renvoie le contrôle par défaut
Let DefaultControlName = vDefaultControlName
End Property
Public Property Let DefaultControlName(vControlName As String)
' définit le contrôle par défaut
vDefaultControlName = vControlName
End Property

Ces deux procédures ont pour but d'écrire et de lire la propriété DefaultControlName. Elle est volontairement déclarée publique pour pouvoir la définir depuis l'extérieur. En effet, il est possible de vouloir définir un autre contrôle à activer.

De la même manière, nous déclarons le «Getter» et «Setter» de la propriété IdCurrentRecord.

Public Property Get IdCurrentRecord() As Long
' pour les opérations postméthode
IdCurrentRecord = vIdCurrentRecord
End Property
Public Property Let IdCurrentRecord(vlIdCurrentRecord As Long)
vIdCurrentRecord = vlIdCurrentRecord
End Property

IV-D - La fonction d'instanciation

Comme précisé dans un des chapitres précédents, les classes d'une bibliothèque ne peuvent être utilisées directement. Il faut créer une fonction.

Ouvrons un nouveau module normal pour y ajouter cette fonction.

Public Function CreerMELA() As cBoutonNav
' Pour créer une instance de la classe
' obligatoire dans le cas d'une bibliothèque
Set CreerMELA = New cBoutonNav
End Function

La base bibliothèque peut être enregistrée.

V - Référencer la bibliothèque

Repassons sur l'application pour référencer la bibliothèque.

Dans le menu Outils/Références, il faut cliquer sur Parcourir, choisir Tous les fichiers (*.*) dans la liste déroulante Fichiers de type, puis aller chercher le fichier MELA.accdb dans son répertoire.

Nous validons notre choix avec le bouton Ouvrir.

La bibliothèque apparait maintenant dans la liste.

VI - Le formulaire

Pour que le formulaire utilise la classe cBoutonNav, nous devons instancier cette dernière.

Ouvrons le formulaire en mode création et activons l'événement Sur Ouverture.

Dans l'en-tête du module, il faut déclarer la variable de classe.

Option Compare Database
Option Explicit
Dim mButtonNav As cBoutonNav

Il ne reste qu'à instancier la classe au chargement du formulaire.

Private Sub Form_Open(Cancel As Integer)

' mise en fonction de la classe
Set mButtonNav = MELA.CreerMELA()

' paramétrage de la classe avec le formulaire
Set mButtonNav.Form = Me
' Dans le cas où l'on veut fixer un contrôle par défaut différent
' de celui défini par la classe.
'mButtonNav.DefaultControlName = "Prenom"
End Sub

La propriété Form de la classe est renseignée avec l'objet formulaire (Me).

Toute variable de type objet doit être libérée, nous ajoutons cette libération dans l'événement Sur Fermeture du formulaire.

Private Sub Form_Close()
Set mButtonNav = Nothing  'libère la variable
End Sub

À la fermeture du formulaire, la classe sera libérée de la mémoire.

VII - Les méthodes de la classe

À présent, rédigeons les méthodes et propriétés de la classe.

La modification d'une bibliothèque ne peut se faire au travers de la base qui la référence. Bien que son code soit visible, la bibliothèque est, à ce moment, en lecture seule. Pour modifier une bibliothèque, il faut fermer les bases qui la référencent et la rouvrir.

VII-A - Verrouiller les contrôles

Le verrouillage/déverrouillage des contrôles se fait grâce à une méthode privée de notre classe. Lors de l'appel, une valeur booléenne peut être passée :

- True verrouille les contrôles ;

- False déverrouille ;

- lorsque rien n'est passé, il y a inversion du verrouillage des contrôles.

La visibilité des boutons Enregistrer et Modifier est inversée. Le focus est passé soit au contrôle par défaut (DefaultControlName), soit au bouton Modifier afin d'éviter les erreurs liées à la désactivation d'un contrôle actif.

Private Sub ActiverControleDetail(Optional Activer As Variant = True)
' active/desactive les contrôles
On Error Resume Next
Dim ctrl As Control

' Activer n'est pas fourni on fait le contraire
'If IsMissing(Activer) Then Activer = Not oForm.Controls(Me.DefaultControlName).Enabled
If IsMissing(Activer) Then Activer = oForm.Controls(Me.DefaultControlName).Locked

' Evol : 14/7/2012
' Suppression, Ajout, modif sont gérés dans les formulaires continus et liés.
'oForm.AllowDeletions = Activer
'oForm.AllowAdditions = Activer
'oForm.AllowEdits = Activer

'on ne parcourt que le détail (le header contient les boutons de navigation
For Each ctrl In oForm.Section(acDetail).Controls
If Not (ctrl.Tag Like "*NOLOCK*") Then ctrl.Locked = Not Activer

'Evol 13/07/2012 gestion des sous-oForm
If TypeOf ctrl Is SubForm Then
ctrl.oForm.AllowDeletions = Activer
ctrl.oForm.AllowAdditions = Activer
ctrl.oForm.AllowEdits = Activer
End If
Next
' gestion de l'enregistrement
'If oForm.Controls(Me.DefaultControlName).Enabled Then    ' enregistrer
If Not oForm.Controls(Me.DefaultControlName).Locked Then
oForm.btnEnregistrer.Visible = True
oForm.Controls(Me.DefaultControlName).SetFocus
oForm.btnModifier.Visible = False
Else
oForm.btnModifier.Visible = True
oForm.btnModifier.SetFocus
oForm.btnEnregistrer.Visible = False
End If
End Sub

Seuls les contrôles de la section Détail sont désactivés.

Le grisage provoqué par la propriété Enabled peut poser des problèmes de lecture avec certaines résolutions d'écran. On utilisera Locked à la place, qui a l'avantage et l'inconvénient de laisser les boutons de commande actifs.

Si vous souhaitez utiliser la propriété Enabled, la variante est disponible dans le code.

Pour que cette fonctionnalité soit opérationnelle, on y fait appel lors d'événements bien précis, comme lors du changement d'enregistrement qui déclenche l'événement Sur Activation. On en profite pour capter l'ID unique s’il existe.

Private Sub oForm_Current()
' verrouille l'enregistrement dès son affichage sauf si c'est un nouvel enregistrement.
ActiverControleDetail oForm.NewRecord
' Capte l'id de l'enregistrement
If Not oForm.NewRecord Then
Me.IdCurrentRecord = vIdCurrentRecord
End If
End Sub

Ici le choix de l'activation ou non des contrôles est conditionné par la valeur renvoyée par la propriété NewRecord du formulaire. Ceci permet d'être immédiatement en situation de saisie si l'ouverture du formulaire est réglée sur Ajout de données.

Docmd.OpenForm "fAdherent",,,,acAdd

On continue le verrouillage/déverrouillage tout au long des actions qui le nécessitent.

VII-B - Créer

Pour la procédure Créer, nous commençons par effectuer un test de la propriété Dirty qui détermine si l'enregistrement est modifié. Dans ce cas, on déclenche la procédure d'enregistrement.

On se positionne systématiquement sur le nouvel enregistrement. Enfin on déverrouille les contrôles.

Private Sub btnCreer_Click()
' se positionne sur le nouvel enregistrement
If oForm.Dirty Then  ' si une modification est en cours
btnEnregistrer_Click
End If

oForm.Recordset.AddNew  ' positionnement sur le Nouvel enregistrement
ActiverControleDetail True  ' déverrouille les contrôles.
End Sub

Là encore, vous pouvez remplacer la méthode Addnew par la commande classique.

Docmd.GotoRecord acActiveDataObject,,acNewRec

Dirty passe à True lorsqu'un contrôle lié est modifié, aussi bien par l'utilisateur que par code. Il sera donc incompatible avec les formulaires préremplis à moins d'utiliser la propriété Valeur par défaut pour faire le préremplissage.

VII-C - Modifier

Le bouton Modifier ne fait que déverrouiller les contrôles.

Private Sub btnModifier_Click()
' déverrouille les contrôles
ActiverControleDetail True
End Sub

VII-D - Enregistrer

Pour l'enregistrement, la propriété Dirty est utilisée pour déterminer l'état de l'enregistrement. La méthode du même nom permet de faire la sauvegarde. Dans tous les cas, les contrôles sont verrouillés.

Private Sub btnEnregistrer_Click()
' enregistre la fiche courante
If oForm.Dirty Then
oForm.Dirty = False
End If
ActiverControleDetail False
End Sub

Dirty a la particularité d'être à la fois une propriété et une méthode. Vous pouvez consulter l'aide Microsoft ACCESS pour en savoir plus.

Vous pouvez employer la commande classique à la place de Dirty = False.

Docmd.RunCommand acCmdSaveRecord

VII-E - Annuler

Pour l'annulation, nous testons toujours si l'enregistrement est modifié, puis nous utilisons la méthode Undo de l'objet Form.

Private Sub btnAnnuler_Click()
' annule la dernière saisie
If oForm.Dirty Then oForm.Undo
End Sub

Vous pouvez utiliser la commande classique d'annulation à la place de la méthode Undo.

docmd.RunCommand acCmdUndo

VII-F - Supprimer

La suppression utilise la commande classique Runcommand acCmdDeleteRecord plutôt que la méthode Delete du Recordset. En effet cette dernière pose quelque problème lors de la suppression du dernier enregistrement ou la confirmation de suppression est requise par l'utilisateur. En revanche si l'enregistrement est nouveau, la modification est annulée.

Private Sub btnSupprimer_Click()
' suppression de l'enregistrement courant
On Error GoTo errSub

If oForm.NewRecord Then  ' c'est un nouveau record, message et annule
MsgBox "Vous êtes sur la fiche réservée à la saisie, elle ne peut être supprimée.", vbInformation + vbOKOnly, fVersionProduit()
oForm.Undo
Exit Sub
End If

'demande de confirmation
If MsgBox("Voulez-vous supprimer définitivement la fiche " & oForm.Caption & " ?", _
vbYesNo + vbDefaultButton2, fVersionProduit()) = vbYes Then

' Corr 18 / 07 / 2012 : Méthode Delete échoue sur le dernier record
DoCmd.RunCommand acCmdDeleteRecord
'oForm.Recordset.Delete

TraitementApresSuppression  ' lance le traitement postsuppression

On Error Resume Next  ' si pas de liste de recherche
oForm.LmRechercher.Requery

End If

Exit Sub

errSub:
' après suppression du dernier enregistrement
If Err.Number = 3021 Then
btnCreer_Click
Exit Sub
End If
If Err.Number = 2010 Then
oForm.btnCreer.SetFocus
Resume
End If
MsgBox "erreur : " & Err.Number & "---" & Err.Description
Exit Sub
Resume
End Sub

VII-F-1 - Cas spécial la procédure TraitementApresSuppression

Cette procédure va nous permettre de supprimer des enregistrements dans des tables filles lorsque la relation d'intégrité n'est pas active. En effet, volontairement ou non, beaucoup d'applications fonctionnent sans relation d'intégrité.
Les méthodes de la classe MELA surcharge les méthodes du formulaire, elles sont donc exécutées après le code de l'événement du formulaire. Il en découle que des événements qui suivent peuvent ne pas être déclenchés. C'est le cas lors de la suppression. L'événement AfterDelete ne se déclenche pas.

Schéma de la suppression

Pour contourner ce problème, on utilise une nouvelle procédure de la classe MELA (TraitementApresSuppression) pour lancer le traitement situé dans le formulaire (piAfterDelete).

Private Sub TraitementApresSuppression()
'---------------------------------------------------------------------------------------
' Procedure : TraitementApresSuppression
' Author    : Fabrice CONSTANS (MVP)
' Date      : 13 / 07 / 2012
' Purpose   : Lance des traitements après suppression, utile pour supprimer des enreg.
'               connexes sur des tables sans relation d'intégrité référentielle.
'             Cause : Après le delete de la classe (btnSupprimer_Click) les événements
'                     (AfterDelete) de la pile dans le formulaire ne sont pas joués.
' Parametres: N/A
' Return    : N/A
'---------------------------------------------------------------------------------------
'
On Error Resume Next  ' s’il n'y a pas de piAfterDelete dans le formulaire on passe.

' Fonction qui supprime les enregistrements connexes
Call oForm.piAfterDelete   ' attention à bien faire une gestion d'erreur
' dans piAfterDelete.
End Sub

Le fait d'isoler l'appel dans une procédure de la classe et de ne pas appeler directement piAfterDelete dans la méthode btnSupprimer_Click permet de traiter l'erreur remontée si le formulaire ne nécessite pas ce type de traitement.
Dans le formulaire livré avec la bibliothèque, voici comment se présente la procédure.

Public Sub piAfterDelete()
'---------------------------------------------------------------------------------------
' Procedure : piAfterDelete (à placer dans le module du formulaire)
' Purpose   : Après la suppression de la fiche par la classe Mela CBoutonNav celle-ci
'              fait un appel à cette procédure publique pour faire des traitements Post.
'              En effet il n'y a pas de déclenchement d'événement après.
' Parametres: vlId l'id de la fiche supprimée
' Return    :
'---------------------------------------------------------------------------------------
'

Dim db As DAO.Database
Dim i As Integer

Set db = CurrentDb

db.Execute "DELETE * FROM tInscription WHERE Id_Adherent=" & mButtonNav.IdCurrentRecord & ";", dbFailOnError

Set db = Nothing
On Error GoTo 0
Exit Sub
End Sub

On exécute une ou plusieurs requêtes de suppression en utilisant la propriété IdCurrentRecord qui conserve l'ID jusqu'au changement d'enregistrement.

Si vous avez mis en place la relation d'intégrité référentielle, vous n'avez pas besoin de mettre en place piAfterDelete. Vous pouvez cependant laisser l'appel dans la classe.

VII-G - Copier

Cette méthode copie les données de l'enregistrement courant vers un nouvel enregistrement.
Elle utilise l'objet RecordsetClone comme source et évite la copie de valeur des champs NumeroAuto.

Private Sub btnCopier_Click()
' copier l'enregistrement courant
Dim rst As DAO.Recordset
Dim fld As DAO.Field
Dim bkm As Variant

If oForm.NewRecord And Not oForm.Dirty Then Exit Sub  'pas de copie d'un enregistrement vide
btnEnregistrer_Click ' on enregistre

bkm = oForm.Recordset.Bookmark  'repère l'enregistrement courant

Set rst = oForm.RecordsetClone  'ouvre le clone

rst.Bookmark = bkm              'positionne le curseur du clone sur le record repéré

btnCreer_Click  'mode création

oForm.Recordset.AddNew

For Each fld In oForm.Recordset.Fields
If Not fld.Attributes And dbAutoIncrField Then 'sauf NumeroAuto
fld.Value = rst.Fields(fld.Name)
End If
Next

oForm.Recordset.Update

btnEnregistrer_Click 'enregistrement
On Error Resume Next  ' si pas de liste de recherche
oForm.LmRechercher.Requery
End Sub

Les champs sont parcourus un à un pour être copiés dans le RecordSet de Form.

VIII - La navigation

VIII-A - Vers la fiche précédente

En tout premier lieu, il faut vérifier que l'enregistrement courant n'est pas en cours de modification. Si c'est le cas, un message demande à l'utilisateur s'il souhaite l'annulation.

Ensuite, la position dans la table est vérifiée. Si le curseur n'est pas au début, il y a déplacement sur l'enregistrement précédent. Sinon un message d'avertissement est émis.

Private Sub btnPrecedent_Click()
' vers la fiche suivante
On Error GoTo errSub

If oForm.Dirty Then
If MsgBox("Souhaitez-vous abandonner les modifications ?", _
vbInformation + vbYesNo, fVersionProduit()) = vbYes Then
btnAnnuler_Click        ' oui Annuler
Else
Exit Sub
End If
End If

If oForm.CurrentRecord > 1 Then                 ' pas sur le 1er record
DoCmd.GoToRecord acActiveDataObject, oForm.Name, acPrevious
Else
MsgBox "Vous avez atteint la première fiche !", vbInformation + vbOKOnly, fVersionProduit()
End If

Exit Sub
errSub:
MsgBox Err.Number & " -- " & Err.Description
End Sub

VIII-B - Vers la fiche suivante

Pour le déplacement vers la fiche suivante, c'est le même déroulement que précédemment à la différence que l'on teste si le curseur n'est pas à la fin.

Private Sub btnSuivant_Click()
' vers la fiche précédente
On Error GoTo errSub

If oForm.Dirty Then
If MsgBox("Souhaitez-vous abandonner les modifications ?", _
vbInformation + vbYesNo, fVersionProduit()) = vbYes Then
btnAnnuler_Click        ' oui Annuler
Else
Exit Sub
End If
End If

If oForm.CurrentRecord < oForm.RecordsetClone.RecordCount Then  ' pas sur le dernier record
DoCmd.GoToRecord acActiveDataObject, oForm.Name, acNext
Else
MsgBox "Vous avez atteint la dernière fiche !", vbInformation + vbOKOnly, fVersionProduit()
End If

Exit Sub
errSub:
MsgBox Err.Number & " -- " & Err.Description
End Sub

VIII-C - Rechercher

Pour cette méthode vous devez avoir au préalable paramétré la liste modifiable LmRechercher comme ceci :

- la source est basée sur la même table que le formulaire ;

- la 1re colonne de la source est l'ID, le numéro unique de l'enregistrement ;

- la propriété Colonne liée est 1.

Ainsi

Private Sub LmRechercher_AfterUpdate()

On Error GoTo errSub

If Not IsNull(oForm.LmRechercher) Then
oForm.Recordset.FindFirst oForm.LmRechercher.Recordset.Fields(0).Name & "=" & oForm.LmRechercher
End If
Exit Sub
errSub:
MsgBox Err.Number & " -- " & Err.Description
End Sub

VIII-D - Fermeture du formulaire

La fermeture ne se résume pas à un simple Close du formulaire, il faut veiller à bien libérer toutes les variables de la classe.

Private Sub btnFermer_Click()
' ferme le formulaire
On Error GoTo errSub

If oForm.Dirty Then  ' si enreg. modifier
If MsgBox("Voulez-vous enregistrer la fiche avant de fermer ?", _
vbInformation + vbYesNo, fVersionProduit()) = vbYes Then
oForm.Dirty = False   ' enregistre
Else
oForm.Undo            ' annule
End If
End If

DoCmd.Close acForm, oForm.Name  ' ferme le formulaire
Exit Sub
errSub:
MsgBox Err.Number & " -- " & Err.Description
End Sub

Dans la classe, il existe une méthode privée qui est toujours exécutée à la fin. Il s'agit de Class_Terminate qui peut servir à libérer toutes les variables objet.

Private Sub Class_Terminate()
' libère les variables
On Error Resume Next
Set btnCreer = Nothing
Set btnModifier = Nothing
Set btnEnregistrer = Nothing
Set btnCopier = Nothing
Set btnAnnuler = Nothing
Set btnSupprimer = Nothing
Set btnPrecedent = Nothing
Set btnSuivant = Nothing
Set btnFermer = Nothing
Set LmRechercher = Nothing
Set oForm = Nothing
End Sub

La classe est libérée lors de l'événement OnClose du formulaire.

IX - Piloter un autre formulaire

Rien de plus simple que d'implémenter la classe dans un autre formulaire.

Copier le code du formulaire fourni avec la bibliothèque.

Private Sub Form_Open(Cancel As Integer)

' mise en fonction de la classe
Set mButtonNav = MELA.CreerMELA()

' paramétrage de la classe avec le formulaire
Set mButtonNav.Form = Me
' Dans le cas où l'on veut fixer un contrôle par défaut différent
' de celui défini par la classe.
'mButtonNav.DefaultControlName = "Prenom"
End Sub

Ensuite copier les boutons.

Exécutez ! Ça fonctionne !

X - Conclusion

L'avantage d'une classe M.E.L.A. c'est son extrême portabilité, sa rapidité de déploiement ainsi que sa maintenance aisée.

Imaginez !

Le fonctionnement de l'annulation ne vous plait pas. Vous n'avez qu'à modifier la classe et l'ensemble de votre application bénéficie instantanément de ce changement.

Vous souhaitez ajouter une fonctionnalité dans certains formulaires ? Qu'à cela ne tienne, modifiez la classe et rajoutez le ou les boutons dans les formulaires concernés. Seuls ceux-ci seront impactés.

Une classe n'est pas très complexe à mettre en œuvre. Cependant il faut de la rigueur et être familiarisé avec le concept objet. Notez que lorsque vous manipulez du code dans un formulaire ou un état, vous manipulez un module de classe de formulaire ou d'état.

La bibliothèque M.E.L.A. est disponible en téléchargement ; si vous avez une version de Microsoft ACCESS 97 vous pouvez télécharger le code au format texte ici. https://loufab.developpez.com/tutoriels/access/classe-mela/CodeMelaTexte.zip ou bien si vous êtes équipé de 2007 et supérieur la bibliothèque est téléchargeable ici : https://loufab.developpez.com/tutoriels/access/classe-mela/MELA.zip

XI - Incompatibilité

Il a été constaté que certains types de champs spécifiques comme les Multivalués (Pièces jointes) provoquent des dysfonctionnements de la classe. Notamment lors de l'enregistrement.

XII - Liens utiles

Pour en savoir plus sur les modules de classes dans ACCESS, je vous conseille quelques lectures.

https://access.developpez.com/cours/?page=langagevba#vbaclasses