0. 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ède 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 certain formulaire 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 oeuvre.

0-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 disfonctionnements de la classe, notamment lors de l'enregistrement.

I. Analyse du besoin

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

I-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.

I-B. Effacement

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

I-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.

I-D. Ajout

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

I-E. Copie

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

II. 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.

II-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 utilisateurs 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.

image

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

III. 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.

III-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 :

image

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.

III-B. Déclaration

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

 
Sélectionnez

'---------------------------------------------------------------------------------------
' 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 quelques 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.

III-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.

 
Sélectionnez

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

    Dim ctrl As Control
    
    Set oForm = objForm
    ' parcours 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.

 
Sélectionnez

    ' fixe le controle par défaut
    For Each ctrl In oForm.Section(acDetail).Controls
        ' c'est un controle 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.

 
Sélectionnez

Private Property Get Form() As Form
' renvoi 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).

 
Sélectionnez

Public Sub Form_Current()

Ceci ne fonctionne pas dans la classe.

Alors que :

 
Sélectionnez

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.

 
Sélectionnez

Public Property Get DefaultControlName() As String
' renvoi le controle par défaut
    Let DefaultControlName = vDefaultControlName
End Property

Public Property Let DefaultControlName(vControlName As String)
' défini le controle par defaut
    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.

 
Sélectionnez

Public Property Get IdCurrentRecord() As Long
' pour les opérations post méthode
    IdCurrentRecord = vIdCurrentRecord
End Property

Public Property Let IdCurrentRecord(vlIdCurrentRecord As Long)
    vIdCurrentRecord = vlIdCurrentRecord
End Property      

III-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.

 
Sélectionnez

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.

IV. 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.

image

La bibliothèque apparait maintenant dans la liste.

V. 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.

 
Sélectionnez

Option Compare Database
Option Explicit

Dim mButtonNav As cBoutonNav

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

 
Sélectionnez

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 ou l'on veut fixer un controle 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.

 
Sélectionnez

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.

VI. 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.

VI-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.

 
Sélectionnez

Private Sub ActiverControleDetail(Optional Activer As Variant = True)
' active/desactive les controles
    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 parcours 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'enregistrer
    '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 si il existe.

 
Sélectionnez

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.

 
Sélectionnez

Docmd.OpenForm "fAdherent",,,,acAdd

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

VI-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.

 
Sélectionnez

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évérouille les controles.
    
End Sub

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

 
Sélectionnez

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.

VI-C. Modifier

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

 
Sélectionnez

Private Sub btnModifier_Click()
' deverouille les controles
    ActiverControleDetail True
End Sub

VI-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.

 
Sélectionnez

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.

 
Sélectionnez

Docmd.RunCommand acCmdSaveRecord

VI-E. Annuler

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

 
Sélectionnez

Private Sub btnAnnuler_Click()
' annule la derniere saisi
    If oForm.Dirty Then oForm.Undo
End Sub

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

 
Sélectionnez

docmd.RunCommand acCmdUndo

VI-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 enregsitrement ou la confirmation de suppression est requise par l'utilisateur. En revanche si l'enregistrement est nouveau, la modification est annulée.

 
Sélectionnez

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é à 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 post suppression

        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

VI-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.

Image non disponible
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).

 
Sélectionnez

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  ' si 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.

 
Sélectionnez

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érencielle, vous n'avez pas besoin d'utiliser de mettre en place piAfterDelete. Vous pouvez cependant laisser l'appel dans la classe.

VI-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.

 
Sélectionnez

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.

VII. La navigation

VII-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.

 
Sélectionnez

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

VII-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.

 
Sélectionnez

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

VII-D. 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 1ère colonne de la source est l'ID, le numéro unique de l'enregistrement.
  • La propriété Colonne liée est 1.

Ainsi

 
Sélectionnez

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

VII-E. 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.

 
Sélectionnez

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 objets.

 
Sélectionnez

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.

VIII. 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.

 
Sélectionnez

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 ou l'on veut fixer un controle par défaut différent
' de celui défini par la classe.
'mButtonNav.DefaultControlName = "Prenom"

End Sub

Ensuite copier les boutons.

image

Exécutez ! Ça fonctionne !

IX. 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 oeuvre. 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. http://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 : http://loufab.developpez.com/tutoriels/access/classe-mela/MELA.zip

X. Incompatibilité

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

XI. Liens utiles

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

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