Il est de bon ton de se souhaiter la bonne année. Mais vous, et vous seul, pourrez faire en sorte que cette année soit bonne, meilleure que celle qui vient de s'écouler. Apprenez à ne compter que sur vous, car personne n'est plus qualifié que vous-même pour bâtir, réparer ou améliorer votre propre vie. Personne ne fera les choses à votre place. D'ailleurs, tout ce que les autres peuvent faire, c'est souhaiter que vous le fassiez. Et ne croyez pas que tout ceux qui vous entourent vous apporteront des solutions : certains font juste partie de vos problèmes. Transformez vos résolutions en actes, et dans douze mois, retournez-vous et souriez-vous fièrement : C'était long. C'était difficile. Mais ça y est : 2017 était une bonne année, merci Moi.

Access

Les macros de données - Partie 1 : bases

Dans la première partie de ce didacticiel, nous allons aborder les macros de données (Nouveauté d'Access 2010, également appelées "Macros de table" ou "Triggers").

Bien qu'il ne faut pas les confondre avec les macros traditionnelles (qui existent toujours), j'utiliserai parfois le terme Macro à la place de Macro de table dans ce didacticiel.

Cette leçon nécessite une bonne connaissance d'Access (Tables, champs, relations, macros, requêtes, et même un peu de VBA dans les formulaires)

Nous aborderons sommairement la possibilité de créer une historisation des changements effectuées dans nos tables, puis, dans la 2ème partie, nous créerons un véritable système d'historisation.

 
Sommaire

Base de données de base

Commencez par télécharger cette base de données. Elle ressemble à ceci :

 

Elle contient deux tables, reliées entre elles par IDArticle.

Nous disposons d'une part de la liste des articles disponibles à la vente ( T_Article) et, d'autre part, des différentes commandes passées par les clients (T_Commande.)

Il n'y a pas de date de commande, ni de possibilité de commander plusieurs articles dans la même commande, ni même de liste déroulante pour choisir l'article dans T_Commande, car je voulais une situation simplifiée à l'extrême pour illustrer le fonctionnement de ces fameuses macros de table..

Requêtes et calculs

Avant d'évoquer les macros de table, comparons-les à d'autres techniques.

Concentrons-nous sur T_Commande : il y a la quantité, mais pas le prix - et donc pas le prix total de la commande (Qte X Prix).

Pour obtenir ces informations,
une requête s'avère nécessaire.

Créez une requête basée sur les deux tables T_Commande et T_Article, et disposez les champs comme ceci :

Inscrivez le calcul de la
commande :

Appuyez sur ENTER - Ca corrige la syntaxe :

Remplacez Expr1 par PrixTotal.

Exécutez la requête :

Trop de mises à jour dans les calculs de requête

Enregistrez cette requête sous R_CalculPrixCommande, et fermez-là (la requête ).

Bien.

Le temps passe, les choses évoluent. Augmentons le prix des crayons de couleur !

Dans la table T_Article, majorez le prix des crayons de 1.80 à 2.10 :

 

 

Peu après cette mise à l'index, un certain Daniel Dumoulin désire commander 50 crayons de couleur rouge : rendez-vous dans la table T_Commande, et créez cette nouvelle commande :

 

Une fois cette commande enregistrée,
fermez les tables, et rouvrez votre requête R_CalculPrixCommande :

Historique perdu

Cette nouvelle commande est absolument parfaite ... mais ... les précédentes commandes de crayons sont devenues fausses, tout à coup : elles ont été ajustées avec le nouveau prix majoré ! Nous ne disposons plus d'aucune manière de connaître l'ancien prix qui avait été appliqué !

Cette histoire de calcul dans la requête ne convient donc pas dans ce cas !

Ce qu'il faudrait, c'est que dans T_Commande, nous conservions une copie du prix actuel de T_Article.

Faisons-le !

Retournez dans la table T_Commande, et insérez le champ Prix entre IDArticle et Qte :

 

 

 

Définissez-le en format Monétaire, et précisez le format Standard
(Pour éviter de voir le symbole monétaire), 2 décimales :

Ce champ est donc un champ tout simple, qui n'effectue aucun calcul automatique.

Les macros de table vont nous permettre d'aller automatiquement rechercher le prix de l'article correspondant dans T_Article, et de l'importer dans ce prix de T_Commande.

Comment faisait-on dans les versions précédentes d'Access, quand les macros de données n'existaient pas ?

On programmait en VBA, via des formulaires !

Programmation VBA dans les formulaires

Regardez l'exemple suivant : j'ai créé un formulaire basé sur T_Commande, et j'ai demandé que lorsqu'on met à jour le N° d'article :

  1. VBA ouvre (OpenRecordSet) la table T_Article
  2. Recherche (FindFirst) le N° d'article du formulaire
  3. Copie (Prix = T_Article("Prix")) le prix ainsi trouvé dans le champ Prix du formulaire

Comme ceci :

Je ne l'explicite pas plus en détail, car ce n'est pas le sujet de ce didacticiel, mais si vous voulez le tester, téléchargez  cette base de données, et lancez le formulaire F_ExempleVBA. Ensuite, allez dans n'importe quelle commande - ou créez-en une nouvelle - et inscrivez un N° d'article (existant dans T_Article) dans IDArticle.

A l'instant ou vous quitterez le champ, le prix de l'article va s'importer et il ne changera pas si, par la suite, vous changez le prix dans T_Article.

On se débrouillait très bien comme ça.

Il est même possible qu'on puisse créer une macro à la place du VBA pour effectuer la même tâche, mais je n'ai simplement jamais essayé (je trouve plus simple, en tant que programmeur, d'utiliser VBA)

Du coup, quel est l'avantage d'utiliser une macro de table pour effectuer une tâche similaire ?

Avantages des macros de table

Les macros de table se trouvant ... dans les tables, on pourra alors effectuer des changements dans la table-même, et le prix sera correctement importé (nous n'aurons plus besoin d'un formulaire juste pour ça). Vous me direz qu'une base de données Access sans formulaire, c'est quand même fort rare, je vous le concède !

Ceci dit, il faut savoir qu'on peut malgré tout construire des formulaire basé sur T_Commande, les macros de table s'exécuteront aussi en arrière plan du formulaire, ce qui évite de le charger de programmation.

De plus, si on exporte - ou copie - ces deux tables dans une autre base de données, les macros de table vont s'exporter avec, et fonctionneront également dans la nouvelle base (sauf évidemment s'il s'agit d'un export dans l'ancien format de base de données MDB, ou si on exécute la base de données avec Access 2007 puisque les macros de table sont une nouveauté Access 2010).

Macro Avant modification

Maintenant que vous cernez le domaine d'utilisation des macros de table, créons notre première macro. Elle va être un peu simplette, mais c'est juste pour montrer le principe : Elle va juste écrire "Ca marche" dans le champ commentaire, dès que vous enreistrerez une modification dans un quelconque enregistrement.

Ouvrez T_Commande en mode saisie de données. Cliquez sur le ruban Table, et sur
Avant Modification.

 

 

 

Choisissez DéfinirChamp
dans la liste :

Dans Nom, écrivez Remarque, et comme Valeur  : "Ca marche"
(entre "guillemets", sinon, ça ne marche pas):

 

Fermez l'action de macro en utilisant l'une de ces deux croix,
et répondez Oui à la demande de confirmation :

Testez : par exemple, ajoutez un s à "Abro", dans la première commande, puis cliquez sur un autre enregistement pour valider, et regardez : La remarque contient bien Ca marche.

 

Fonction Si

Maintenant, admettons que nous désirions écrire le mot "Quatre" dans la remarque, seulement si nous modifions la commande numéro 4.

Pour ce faire, retournez dans la macro de données
(cliquez sur , et glissez l'instruction Si au dessus de votre instruction existante, comme ceci :

Ensuite, faites glisser l'instruction de modification du champ dans la zone Si, de cette façon :

Cliquez entre Si et Alors : , et écrivez [IDCommande]=4 : .

Cliquez ensuite sur la valeur : , et écrivez "Quatre", puis fermez : .

A partir de maintenant, changez ce que vous voulez dans n'importe quel enregistrement de T_Commande, rien ne se passe, sauf si vous changez quelque chose dans la commande N° 4 : il écrira Quatre dans la remarque à l'instant ou vous validerez vos modifications (soit en cliquant sur la petite disquette, soit quand vous appuierez sur CTRL-S, ou que vous changez simplement d'enregistrement).

Variables locales

Etudions ce qu'est une variable locale. C'est une manière de mémoriser une valeur.

Effacez le contenu de votre macro de table, grâce aux petites croix :

 

Ajoutez, en faisant cliquer-glisser les instructions comme avant, l'instruction DéfinirVarLocale (TrucMachin, 4000), et DéfinirChamp (Prix, TrucMachin - qui vaut donc 4000), comme ceci :

Testez : modifiez n'importe quel enregistrement, et constatez que le prix devient CHF 4'000.00.

Remarquez que je n'ai pas encadré la variable locale ni le champs de [crochets]. Plus précisément, les "guillemets" sont obligatoires, comme on l'a vu plus haut, mais les [crochets] ne le sont que dans le cas où il y a des espaces dans les noms des champs (Si le champ s'était appelé Le prix (Espace entre Le et prix), alors, les [crochets] auraient été indispensables.

Vous allez me dire que dans ce cas présent, la variable locale TrucMachin ne sert à rien. Vous avez raison. C'était juste pour vous montrer comment l'utiliser, car nous allons en avoir besoin ultérieurement.

Fixation des prix dans les commandes

Passons aux choses sérieuses ! Récapitulons notre demande initiale :

Lorsque nous modifions un enregistrement dans T_Commande, nous devons :

  1. Ouvrir la table T_Article
  2. Chercher le numéro d'article correspondant à notre numéro d'article courant dans T_Commande
  3. Copier le prix ainsi trouvé dans T_Article, et le coller dans Prix de T_Commande

Commencez par effacer toutes les actions de macros existantes, comme tout à l'heure avec les croix, puis remplissez les actions comme suit :

 

Vous avez compris le système ?

  1. On commence par mémoriser l'IDarticle dans la variable VARIDArticle
  2. On ouvre la table T_Article, et on y recherche l'enregistrement dont l'IDArticle correspond à l'IDArticle de T_Commande (C'est la condition Where)
  3. Alors que nous sommes encore dans T_Article (D'où l'importance glisser-déposer DéfinirVarLocale à l'intérieur du bloc Rechercher un enregistrement, et pas en dessous), nous stockons le prix trouvé dans une variable VARPrix
  4. Nous allons replacer ce prix dans notre prix de T_Commande, et donc, cette fois nous devons disposer notre DéfinirChamp en dessous du bloc Rechercher un enregistrement

Testez : Modifiez n'importe quel champ de n'importe quel enregistrement de T_Commande, et constatez son prix qui vient s'installer.

Je vous rappelle qu'initialiement, nous avons entrepris tout ce mic-mac uniquement pour que les nouvelles commandes soient pourvues du nouveau prix, sans altérer les prix des anciennes commandes.

Effectuons un test complet pour prouver que ça fonctionne :

  1. Ajoutez une nouvelle commande, comme ceci :
  2. Enregistrez-là avec CTRL-S : le prix est bien importé :
  3. Fermez T_Commande
  4. Ouvrez T_Article, et majorez le prix de l'article N°2 à 33.45 :
  5. Fermez T_Article (ce qui enregistrera automatiquement le nouveau prix dans T_Article)
  6. Rouvrez T_Commande, et constatez que le prix de l'ancienne commande est restée à 28.90, ce qui est très bien
  7. Créez une nouvelle commande, pour Eugène Ermenons (Avec un "s" a la fin de Ermenon), aussi pour l'article N°2
  8. Enregistrez avec CTRL-S : Le nouveau prix actualisé de 33.45 a bien été importé sur cette commande, mais pas pour les autres commandes antérieures de l'article N°2, c'est bien ce que nous désirions.

Eviter les mises à jour excessives

Si on reprend l'exemple en VBA dans le formulaire que j'ai montré plus haut, c'est différent : la macro ne se déclenche que lorsque je change l'IDArticle : c'est plus souple. Ici, dans la macro de table, on n'a pas cette possibilité : elle s'exécute lors de n'importe quelle mise à jour de n'importe quel champ

Par contre, comme vous l'avez constaté, nous avons commis une petite faute d'orthographe à M.Ermenons. Il s'écrit vraiment avec un "s", et pas un "t".

Vous me voyez venir ?

Si vous corrigez l'orthographe de Ermenont à la commande 7, la macro va s'exécuter, et transformer 28.90 en 33.45, et ça, c'est pas bien !

Ce qui serait bien, ce serait que cette macro ne s'exécute que si le prix de l'article n'est pas encore rempli : ainsi, lorsqu'on effectue une modification dans une commande existante, le nouveau prix n'est pas importé, mais lorsque nous nous trouvons sur une nouvelle commande - dans laquelle le prix n'existe pas encore - alors, il est importé.

Rajoutez un Si, comme ceci, au début de la macro :

Ecrivez-y  : EstNull([Prix])=Faux, et dans ce cas (le prix est nul, ou vide), on arrête simplement la macro.

Ca va vous permettre de changer Ermenont en Ermenons sans toucher au prix.

Ajustement des mises à jour des prix

Par contre, il demeure encore une faiblesse :

Imaginez : vous avez rempli la commande N°7. Elle est enregistrée, le prix est importé, tout va bien. Puis vous constatez que vous vous êtes trompé d'article ! Ce n'est pas l'article N°2 qu'il voulait, mais le 4 !

Si vous changez le N°2 en 4, comme le prix était déjà indiqué, il ne va pas s'actualiserAïe !

Nous allons changer un tout petit peu de stratégie : occupons-nous seulement de l'IDArticle, et laissons tomber le prix.

Il faudrait qu'on puisse lui dire de s'arrêter immédiatement dans ces deux cas :

  1. Dans le cas où IDArticle est vide (ce qui évitera un message d'erreur en même temps)
  2. Dans le cas où la valeur précédente d'IDArticle est la même que la nouvelle valeur (C'est à dire que quand on va changer Ermenont en Ermenons, la valeur précédente de l'IDArticle (2) est la même que la "nouvelle" valeur (2 aussi puisqu'on ne s'est pas occupé de ce champ)

Modifiez votre Si comme suit : EstNull([IDArticle]) Ou [IDArticle]=[Ancien].[IDArticle]

Mise à jour sélective

Voilà ! Effectuons un test complet. Vous êtes dans T_Commande :

  1. Créez une nouvelle commande pour Francine Fermond (Ecrivez juste Francine Fermond dans une nouvelle ligne, rien d'autre)
  2. Appuyez sur CTRL  S pour enregistrer : aucune erreur ne survient
    Comme IDArticle est Null, notre Si initial arrête la macro immédiatement
  3. Complétez la commande de Francine Fermond : écrivez 2 dans son IDArticle, et enregistrez.
    Le prix de l'article 2 (33.45) est bien importé
  4. Quittez T_Commande, et allez dans T_Article.
    Augmentez le prix de la poupée à 35.80
  5. Quittez T_Article, et revenez dans T_Commande
    Le prix de la poupée est bien toujours à 33.45 (ce qui est normal)
  6. Créez une nouvelle commande pour Francine Fermont qui veut vous acheter encore une poupée. Cette fois, elle est bien logiquement facturée avec le nouveau prix !
  7. Aïe ! Du coup, on constate qu'on a commis une faute d'orthographe dans son nom.
    Rectifiez : ajoutez un "t" à la fin de Fermon dans la commande 10 !
        Enregistrez.
    Suspense ... YES ! Sa commande 10 n'a pas changé de prix !
  8. Bon, madame Fermont nous rappelle pour nous informer qu'elle s'est trompée dans sa commande 10 : elle ne voulait pas commander l'article 2, mais l'article 3.
    On accepte de changer 2 en 3 dans la commande 10, et on presse sur CTRL-S : Parfait : comme vous le voyez à droite, la macro vient d'importer le prix de l'IDarticle 3.
Il reste encore le cas exceptionnel ou l'utilisateur efface carrément l'IDArticle d'une commande : dans ce cas, le prix reste affiché. Mais on va s'arrêter là, c'est assez compliqué comme ça.

Interactions entre les relations et les Macros de Mise à jour

 

 

 

 

Pour comprendre le chapitre qui vient, il vous faudra casser la relation entre T_Article et T_Commande (sinon, il sera tout bonnement impossible d'entrer un numéro d'article non-correspondant)

L'autre bug survient dans le cas où l'utilisateur entre un IDArticle qui n'est pas référencé dans la table T_Article : vous serez gratifié d'un message d'erreur :

Celui-ci n'est pas gênant pour un sou : lorsque la base de données sera terminée, il est bien clair qu'on ne va jamais s'amuser à inscrire des numéros d'article : on aura évidemment pris soin de créer une liste déroulante pour choisir les articles, ce qui exclut cette erreur de facto.

Ignorer les erreurs

Si ce message d'erreur vous ennuie, vous pouvez toujours lui demander de l'ignorer (plus exactement : d'ignorer la ligne de macro qui pose problème).

Voici l'astuce : ajoutez Sur Erreur tout en haut de votre macro, et choisissez Suivant :

A présent, vous pouvez tenter d'écrire un IDarticle fantaisiste dans IDArticle de T_Commande, il n'y aura plus de message d'erreur.

Par contre, restez conscient du danger : si un ancien IDarticle correct était inscrit, le fait de le changer en numéro incorrect ne changera plus le prix :

Je ne saurais donc que trop vous recommander de redéfinir la relation comme elle était, avec l'intégrité référentielle. Voyez cette page si vous avez besoin d'aide sur l'utilité de l'intégrité référentielle.

Historisation des suppressions

Lorsque vous supprimez un enregistrement, il peut être intéressant de garder la trace de celui-ci.

Les macros de données vont nous permettre cette fonctionnalité assez facilement. Je vais commencer à vous montrer comment ça se passe dans cette page, mais la deuxième partie de ce didacticiel y sera complètement consacré.

A chaque supression d'un  - ou plusieurs - enregistrement(s) de T_Commande, il faudrait que toutes les données de cette commande se copient dans une nouvelle table que nous appellerons T_CommandeHistorique.

Nous allons commencer par créer cette table :

  1. Cliquez sur T_Commande dans le volet de navigation, et copiez-collez-là :
  2. Allez dans T_CommandeHistorique en mode création, et définissez IDCommande en Numérique :

Macro Après suppression

Nous disposons maintenant d'une table vide avec une structure équivalente à T_Commande. On a dû remplacer le NuméroAuto par du Numérique simplement parce que dans cette table, les numéros ne vont plus se suivre séquentiellement (vous allez comprendre un peu plus tard).

Fermez T_CommandeHistorique et retournez dans T_Commande.

Cliquez sur le bouton

Nous allons récupérer toutes les anciennes valeurs de chaque champ (Oui : car comme le bouton l'indique - Après suppression - les données sont déjà supprimées - depuis une petite fraction de seconde) dans des variables locales.

Une fois que nous aurons tout stocké dans ces variables locales, nous ouvrirons la table T_CommandeHistorique, et nous utiliserons CréerEnregistrement pour y intégrer le contenu de nos variables locales.

Z'êtes prêt ? C'est par là :

Une fois que vous avez bien tout recopié, testez : effacez un enregistrement :

Et allez ensuite vérifier que cet enregistrement a bel et bien été recopié dans T_CommandeHistorique.

 

 

 

Tiens ! Il n'a rien copié ! Et il n'a pas renvoyé d'erreur !

Bug dans les champs mémos

 

C'est tout bête, mais faut le savoir :

Ce bug est dû à la Remarque qui est en type de données Mémo !

Il suffit de supprimer ces deux instructions :

Essayez maintenant de supprimer l'enregistrement 5 par exemple :

Et voilà ! Ca marche ! (A part la remarque, évidemment). A part vous demander de changer le type de la remarque de Mémo en Texte, je n'ai pas d'alternative viable à vous proposer pour pouvoir journaliser cette remarque.

Interaction historisation/valeurs par défaut

Petite amélioration : Ajoutons la date et l'heure de cette suppression.

Rendez-vous dans la table T_CommandeHistorique, et ajoutez un champ Moment en Date/Heure, et dans les options du champ, définissez la valeur par défaut à Maintenant(), comme ceci :

Eh, vous savez quoi ? ... C'est déjà fini !

Nul besoin de modifier la macro de table, puisque la valeur par défaut est Maintenant(). Dès que T_CommandeHistorique va s'enrichir d'une ligne, la date et l'heure seront déjà présents.

Essayez : Supprimez la commande N° 8 de T_Commande, et allez ensuite dans T_CommandeHistorique :

La première ligne ne contient pas de moment de suppression, puisqu'elle avait été supprimée avant notre ajout de champ.

La ligne 8 est bien présente, avec sa date et heure de suppression (la nouvelle ligne contient la valeur par défaut - la date et l'heure à laquelle j'étais lorsque j'ai ouvert la table) mais il suffit de fermer et de rouvrir cette table pour qu'elle s'actualise automatiquement.

Suivi des modifications

Ce qui pourrait être encore plus intéressant, ce serait d'historiser également les changements effectués dans la table T_Commande. Ce serait bien de savoir quelle valeur de quel champ a été modifiée en quelle autre valeur.

Nous allons utiliser le mot-clé Ancien que nous avons vu plus haut.

Nous avons besoin de doubler tous les champs de la table T_CommandeHistorique afin de stocker les anciennes et nouvelles valeurs, comme ceci :

Surtout, retirez la clé primaire de IDCommande, car à partir de maintenant, nous pourrons avoir plusieurs enregistrements avec le même IDCommande dans T_CommandeHistorique, puisqu'il sera possible de modifier la même commande à plusieurs reprises, ce qui cumulera autant de lignes d'historique.

Quittez T_CommandeHistorique, et retournez dans T_Commande.

Cette fois, cliquez sur l'événement de Macro Après MAJ (Mise A Jour)

Format XML

Je pense que vous avez déjà la puce à l'oreille : ce que nous allons faire ressemble à ce que nous avons fait pour la suppression :

  1. On sauvegarde les données des valeurs actuelles et anciennes dans des variables locales
  2. On crée un nouvel enregistrement dans T_CommandeHistorique
  3. On y injecte les contenus des données

Commençons avec le client.

Vous allez devoir recopier la macro suivante, mais attendez :

Les macros sont stockées en XML. Je ne vais pas vous donner un cours d'XML, mais tout ce que vous devez savoir, c'est qu'il s'agit d'un format de texte puissant et souple qui est utilisé dans beaucoup de circonstances.

Grâce à ce XML, justement, je vais vous éviter la recopie fastidieuse de cette macro !

Voici comment j'ai procédé :

  1. J'ai appuyé sur CTRL A pour sélectionner toute la macro
  2. puis sur CTRL C pour la copier
  3. Puis, je suis allé dans le bloc notes Windows (Ou dans word)
  4. et j'ai appuyé sur CTRL V (pour coller), et voici ce que j'ai obtenu :

Exemple XML

Je peux donc vous proposer cette macro sous cette forme :

<?xml version="1.0" encoding="UTF-16" standalone="no"?>
<DataMacros xmlns="http://schemas.microsoft.com/office/accessservices/2009/11/application"><DataMacro Event="AfterUpdate"><Statements><Action Name="SetLocalVar"><Argument Name="Name">VARClient</Argument><Argument Name="Value">[Client]</Argument></Action><Action Name="SetLocalVar"><Argument Name="Name">VARClientAncien</Argument><Argument Name="Value">[Old].[Client]</Argument></Action><CreateRecord><Data><Reference>T_CommandeHistorique</Reference></Data><Statements><Action Name="SetField"><Argument Name="Field">Client</Argument><Argument Name="Value">[VARClient]</Argument></Action><Action Name="SetField"><Argument Name="Field">ClientAncien</Argument><Argument Name="Value">[VARClientAncien]</Argument></Action></Statements></CreateRecord></Statements></DataMacro></DataMacros>

Sélectionnez le texte XML ci-dessus, et copiez collez-le directement dans votre macro vide : miracle, vous avez votre macro sans avoir à la recopier. Elle est pas belle, la vie ?

Il n'y a plus qu'à tester ! Dans la commande N°4, corrigez l'orthographe de
Charles Cottet
en Charles Cottey (Avec un "y" à la fin). Enregistrez, et allez voir ce qui s'est passé dans T_CommandeHistorique :

 

 

Constatez que l'IDCommande de T_CommandeHistorique ne s'est pas rempli, et les autres champs restent également vides.

Il faut donc tout faire à la main :

 

 

 

Mais comme je suis un gars bon, je vous propose d'effacer votre macro actuelle, et de la remplacer par cette version complète juste prête à être copiée-collée :

<?xml version="1.0" encoding="UTF-16" standalone="no"?>
<DataMacros xmlns="http://schemas.microsoft.com/office/accessservices/2009/11/application"><DataMacro Event="AfterUpdate"><Statements><Action Name="SetLocalVar"><Argument Name="Name">VARIDCommande</Argument><Argument Name="Value">[IDCommande]</Argument></Action><Action Name="SetLocalVar"><Argument Name="Name">VARClient</Argument><Argument Name="Value">[Client]</Argument></Action><Action Name="SetLocalVar"><Argument Name="Name">VARClientAncien</Argument><Argument Name="Value">[Old].[Client]</Argument></Action><Action Name="SetLocalVar"><Argument Name="Name">VARIDArticle</Argument><Argument Name="Value">[IDArticle]</Argument></Action><Action Name="SetLocalVar"><Argument Name="Name">VARIDArticleAncien</Argument><Argument Name="Value">[Old].[IDArticle]</Argument></Action><Action Name="SetLocalVar"><Argument Name="Name">VARPrix</Argument><Argument Name="Value">[Prix]</Argument></Action><Action Name="SetLocalVar"><Argument Name="Name">VARPrixAncien</Argument><Argument Name="Value">[Old].[Prix]</Argument></Action><Action Name="SetLocalVar"><Argument Name="Name">VARQte</Argument><Argument Name="Value">[Qte]</Argument></Action><Action Name="SetLocalVar"><Argument Name="Name">VARQteAncien</Argument><Argument Name="Value">[Old].[Qte]</Argument></Action><CreateRecord><Data><Reference>T_CommandeHistorique</Reference></Data><Statements><Action Name="SetField"><Argument Name="Field">IDCommande</Argument><Argument Name="Value">[VARIDCommande]</Argument></Action><Action Name="SetField"><Argument Name="Field">Client</Argument><Argument Name="Value">[VARClient]</Argument></Action><Action Name="SetField"><Argument Name="Field">ClientAncien</Argument><Argument Name="Value">[VARClientAncien]</Argument></Action><Action Name="SetField"><Argument Name="Field">IDArticle</Argument><Argument Name="Value">[VARIDArticle]</Argument></Action><Action Name="SetField"><Argument Name="Field">IDArticleAncien</Argument><Argument Name="Value">[VARIDArticleAncien]</Argument></Action><Action Name="SetField"><Argument Name="Field">Prix</Argument><Argument Name="Value">[VARPrix]</Argument></Action><Action Name="SetField"><Argument Name="Field">PrixAncien</Argument><Argument Name="Value">[VARPrixAncien]</Argument></Action><Action Name="SetField"><Argument Name="Field">Qte</Argument><Argument Name="Value">[VARQte]</Argument></Action><Action Name="SetField"><Argument Name="Field">QteAncien</Argument><Argument Name="Value">[VARQteAncien]</Argument></Action></Statements></CreateRecord></Statements></DataMacro></DataMacros>

Testez : Faites plein de changements dans l'enregistrement N° 3, comme ci-dessous, et allez vérifier ensuite T_CommandeHistorique :

Je vous rappelle que la remarque n'est pas journalisée à cause de son type de données Mémo.

Délais d'enregistrements

J'ai constaté parfois un certain délai nécessaire pour la mise à jour.

En d'autres mots, si vous fermez super-vite T_Commande, et que vous ouvrez T_CommandeHistorique tel Speedy Gonzales, il est possible que vous ne voyiez pas les changements. Fermez tranquillement T_CommandeHistorique et rouvrez-la touuuuuuut lentement, avant de conclure que vous avez peut-être fait une faute d'erreur quelque part.

Les suppressions sont mélangées avec les modifications.

Ce serait sympathique d'avoir un champ GenreEvenement qui contiendrait soit Suppression, Soit Modification, selon les cas, non ?

Je vous propose d'essayer de réaliser cette petite prouesse sans consulter la solution juste ici plus bas, à titre d'exercice !

 

 

 

Solution :

  1. Créez un champ GenreEvenement, en texte, dans T_CommandeHistorique :
  2. Allez dans T_Commande, dans la macro
  3. Ajoutez cette ligne tout à la fin :
  4. Quittez cette macro, et allez dans
  5. Ajoutez cette ligne :

Maintenant, testons !

  1. Créez un nouvel enregistrement dans T_Commande :
  2. Enregistrez-le :
  3. Modifiez-le :
  4. Supprimez-le.

Ouvrez T_CommandeHistorique.
la modification et la suppression ont bien été journalisées :

Macro Après insertion

La seule chose qui n'a pas été journalisée, c'est la création de l'enregistrement.

Seriez-vous capable de le faire sans regarder la solution ?

 

 

 

 

Solution :

Il n'y a rien à faire de spécial dans T_CommandeHistorique, cette fois. Allez dans T_Commande, dans la Macro

Comme nous allons faire exactement la même chose qu'Après MAJ, on ne va pas se priver : rendez-vous dans la macro , appuyez sur CTRL A pour tout sélectionner, et CTRL C pour tout copier.

Quittez cette macro, et allez dans .

Appuyez sur CTRL-V pour tout coller.

Voilà ! Maintenant, retirez toutes les instructions qui concernent les valeurs "Ancien", qui ne servent à rien dans le cas actuel :

Pensez à ajouter en dernier lieu :

Testez ! Ajoutez une nouvelle commande :

Admirez le résultat dans T_CommandeHistorique :

Journalisation des erreurs

Si vous commettez la moindre petite faute (un champ qui n'est pas parfaitement orthographié par exemple), la macro ne fonctionnera simplement pas, mais elle ne vous affichera pas toujours un message d'erreur.

USYSApplicationLog

Dans certains cas, un message d'erreur vous sera affiché,mais dans d'autres, les erreurs seront consignées dans une table spéciale, nommée USYSAplicationLog (USYS = User SYStem).

Si vous n'avez pas fait la moindre faute dans la base de données, USYSApplicationLog n'existe pas (pas encore). Si vous avez fait des fautes qui ont provoqué des erreurs de macro, la table est créée, mais elle n'est pas visible de toute façon.

Pour savoir si USYSApplicationLog existe, cliquez sur le menu Fichier : vous aurez un bouton en plus :

Dans mon cas, je n'ai pas manqué de commettre quelques erreurs lors de l'élaboration de de didactiel, et voici le contenu de mon USYSApplicationLog, que je visualise en cliquant sur le bouton :

On peut effacer certaines lignes, ou carrément toutes les lignes de cette table, à la main.

Même si vous videz complètement la table, le bouton continuera d'être affiché.

Affichage des objets système

 

 

Si on veut s'en débarasser, il faut carrément supprimer USYSApplicationLog.

 

Mais comment peut-on la supprimer si on ne la voit même pas ? Il faut afficher les objets système, comme ceci :

Si vous nommez l'une de vos tables en la commençant pas USYS, elle sera automatiquement masquée, et vous ne pourrez la faire apparaître qu'en cochant la case qu'on vient de voir.

Si vous la voyez - c'est donc que vous avez commis des erreurs - supprimez-là.

Si vous ne la voyez pas, c'est que jamais aucune erreur n'a été générée dans une macro de table de cette base. (Ne vous préoccupez surtout pas des autres tables en gris clair, ce sont des tables nécessaire à la cuisine interne d'Access).

Afin d'illustrer le fonctionnement de cette journalisation des erreurs, créez deux tables : T_1 et T_2, comme ceci :

Elles sont complètement vides.

Allez dans la table T_2, Avant Modification : , et demandez à modifier le champ NBAnimal en lui attribuant le texte "Tralala". Une erreur va survenir, puisque le champ est défini en numérique : . Essayez de saisir une ligne (Georges, chat), et observez ce qui se passe au moment de l'enregistrement : .

Un message d'erreur survient effectivement.

Cliquez sur OK, et constatez qu'il est impossible de sauvegarder cet enregistrement. Votre seul salut est d'annuler votre saisie avec la
touche.

Du coup, dans ce cas, cet avertissement a pour effet d'annuler l'erreur et USYSApplicationLog ne s'est pas créée.

Que se passerait-il si nous demandions à ignorer l'erreur, comme nous l'avons vu plus haut, ainsi : ?

Essayez : cette fois l'enregistrement est créé, et la ligne fautive est simplement ignorée (NBAnimal restera vide) ...

Mais USYSApplicationLog ne sera toujours pas créée !

Par contre :

  1. Supprimez cette macro
  2. Fermez T_2
  3. Allez dans T_1 - Après insertion : . Recopiez ceci :
                      Il s'agit de la même erreur, sauf que cette fois, nous sommes dans une autre table.
  4. Entrez les données que je vous propose sur la droite, sauvegardez avec CTRL-S, et regardez en même temps dans le volet de navigation :

                             la table USYSApplicationLog vient d'être créée automatiquement :

Regardez dans le menu Fichier : la mise en garde est explicite :

 

Vous pouvez - au choix - cliquer sur le bouton , ou double-cliquer sur la table USYSApplicationLog pour l'ouvrir.

Si vous utilisez le bouton , USYSApplicationLog s'affiche, et le message dans le menu fichier s'est transformé:

 

Ouvrez USYSApplicationLog : elle contient la seule et unique ligne décrivant l'action qui a posé problème :

Je souligne que cette erreur est silencieusement générée, et discrètement journalisée.

Que se passerait-il si, comme dans l'exemple précédent nous demandons explicitement à passer par dessus l'erreur, comme ceci :

 

 

Eh bien, tout comme à l'exemple d'avant, l'erreur est simplement... ignorée, et USYSApplicationLog ne sera pas complété d'une ligne de plus.

Parcours de tous les enregistrements

Revenons à notre duo T_Article et T_Commande. Imaginons que lorsqu'on modifie la remarque d'un article, celle-ci doive s'ajouter aux remarques de tous les articles correspondants dans T_Commande.

Je m'explique :

Admettons que nous changions la remarque du crayon de couleur dans T_Article : j'aimerais que cette nouvelle remarque s'ajoute à la fin des remarques existantes de toutes les commandes de cet article, ou qu'elle soit créée, de cette manière:

Nous l'avons vu plus haut : les champs de type Mémo ne permettent pas ce genre de choses. Aussi, commencez par remplacer la Remarque de T_Article et de T_Commande de Mémo en Texte.

J'ai malheureusement constaté d'énormes problèmes apparemment liés au fait d'avoir transformé mon champ Remarque de Mémo en Texte, et je n'ai pas pu continuer (comme s'il croyait que le champ était toujours en Mémo).

Mon seul salut a été de créer un nouveau champ Rem2 dans chacune des tables.

Je vous invite à faire de même (histoire d'avoir les mêmes champs que moi) :

Instruction PourChaqueEnregistrement

Rendez-vous dans T_Article, dans la macro AprèsMAJ :

Voici la version de cette même macro en XML que vous avez juste à copier-coller dans votre macro :

<?xml version="1.0" encoding="UTF-16" standalone="no"?>
<DataMacros xmlns="http://schemas.microsoft.com/office/accessservices/2009/11/application"><DataMacro Event="AfterUpdate"><Statements><Action Name="SetLocalVar"><Argument Name="Name">VARRem2</Argument><Argument Name="Value">[Rem2]</Argument></Action><Action Name="SetLocalVar"><Argument Name="Name">VARIDArticle</Argument><Argument Name="Value">[IDArticle]</Argument></Action><ForEachRecord><Data><Reference>T_Commande</Reference><WhereCondition>[IDArticle]=[VARIDArticle]</WhereCondition></Data><Statements><EditRecord><Data/><Statements><Action Name="SetField"><Argument Name="Field">Rem2</Argument><Argument Name="Value">[VARRem2]</Argument></Action></Statements></EditRecord></Statements></ForEachRecord></Statements></DataMacro></DataMacros>

Réactions en chaîne

Avant de tester, jetez un oeil sur la table T_CommandeHistorique :. J'ai actuellement 7 enregistrements. Bien.

Fermez cette table, et allez dans T_Article. Précisez que l'article 3 "Contient du plomb" :

Fermez la table, et ouvrez T_Commande :

Toutes les commandes de l'article numéro 3 ont été maintenant mises à jour. Mais ce n'est pas tout ! Vous vous rappelez de T_CommandeHistorique ?

Il y avait - dans mon cas - 7 enregistrements. Si vous vous souvenez, nous avons précisé plus haut qu'une ligne d'historique devait être rajoutée à chaque modification, ajout ou suppression d'une ligne de commande... C'est bien ce qui vient de se passer, non ? ... Toutes les lignes de T_Commande de l'article 3 ont été modifiées. Du coup, le 2ème effet Kiss-Cool, c'est que les trois lignes sont maintenant référencées dans T_CommandeHistorique (j'en suis à 10 enregistrements, maintenant):

Récursivité accidentelle

Du coup, imaginez : si nous avons une table A pourvue d'une macro qui va modifier des champs d'une table B, qui, elle-même possède une macro qui va changer deschamps dans la table A ... qui va donc changer dans la table B, et ainsi de suite, à l'infini ?

Ca planterait tout le système !

Heureusement, les macros de table sont pourvues d'un garde-fou (je ne me rappelle plus combien, mais il remarque assez rapidement ce problème de "boucle infinie" - Appelée "Récursivité" - le cas échéant).

Revenons à nos moutons : ce qui est un peu bête, c'est que ces trois lignes ne nous servent strictement à rien puisque Rem2 n'est pas présent dans cette T_CommandeHistorique. Tiens : Ajoutez ce Rem2 dans cette table en modifiant la macro Après MAJ.

 

 

Solution :

Cet exercice (tel qu'il est présenté) est stupide, puisque nous aurions pu tout aussi bien obtenir le même résultat avec une requête basée sur les deux tables T_Article et T_Commande, avec bien moins d'effort :

 

Mais que se passe-t-il si nous modifions une Rem2 directement dans T_Commande ? (Par exemple, on modifie un des "Longueur : 14CM" en "Made in China")

Ca ne changera évidemment rien dans T_Article, mais dans l'exemple de la requête ci-dessus, cette modification n'apparaîtra pas du tout (Puisque, justement, on prend Rem2 de T_Article).

Concaténation

Mais... Si on se rend dans T_Article, et qu'on remplace "Longueur : 14CM" par "Garanti 1 an", alors, toutes les Rem2 de l'article 3, dans T_Commande (y compris "Made in China") seront irrémédiablement écrasés par "Garanti 1 an" !

Du coup, si je me réfère au libellé initial de l'exercice, il ne s'agit pas de simplement écraser les remarques au bulldozer, mais d'ajouter les remarques de T_Article à la fin des remarques de T_Commande.

En clair, ça veut dire ça :

Corrigez la macro comme suit :

Essayez maintenant d'écrire "Bois d'Asie" dans la Rem2 de l'article N°3 de T_Article, et allez ensuite voir à quoi ressemble T_Commande :

Ce n'est pas mal du tout !

Le seul petit truc qu'on pourrait reprocher, c'est dans le cas ou il y avait déjà une Rem2, la nouvelle Rem2 vient se coller directement à sa droite : plutôt que "MadeinChinaBois d'Asie", j'aurais préféré : "Made in China. Bois d'Asie". Ne serait-ce pas sympathique de rajouter un point et un espace après la remarque existante. Par contre, il ne faudrait pas faire ça pour les cas ou la remarque est encore vide. Un petit Si va nous régler ce problème :

Comme ceci :

Il n'y a plus qu'à tester :

Parfait ! Elle est pas belle, la vie ?

Bon, on peut toujours faire mieux : s'il y avait déjà un point à la fin d'une Rem2 existante, il va en rajouter un deuxième, et, si le nombre de caractères permis pour Rem2 dans T_Commande ne permet pus d'ajouter le contenu de Rem2 de T_Article, une erreur sera générée dans USYSApplicationLog, mais voilà... On va s'arrêter là !

Liste des Macros de table

Nous voici donc avec un bon petit paquet de macros.

Il est possible de lister d'un seul coup d'oeil toutes les macros de toutes les tables.

Rendez-vous dans n'importe quelle table en mode saisie de données, et cliquez sur Macro Nommée, Renommer/supprimer une macro :

 

On regrettera que cette fenêtre soit juste une fenêtre de visualisation générique des macros de données, sans les détails de leurs instructions, ainsi que le fait qu'on ne puisse pas double-cliquer directement dessus pour s'y rendre.

Macros nommées

Si vos différents événements de chaque table effectuent chaque fois la même tâche, il est possible de créer une Macro nommée, et nous appellerons ensuite cette macro depuis les différents événements de nos différentes tables.

Par exemple, imaginons qu'à chaque mise à jour d'un enregistrement dans n'importe laquelle des tables, il s'agit d'envoyer un E-Mail à quelqu'un l'informant qu'une table a été modifiée.

Marche à suivre :

  1. Allez dans n'importe quelle table (Disons T_1 par exemple)

Vous voici maintenant l'heureux propriétaire d'une macro nommée, qui n'est actuellement rattachée à aucun événement de table (et qui ne s'exécutera donc jamais en l'état). Vous pouvez la voir dans la liste des macros de table :

Notez les différences avec les autres macros :

 

Si vous désirez la modifier :

Actions possibles Avant et Après les événements

Il s'agit maintenant d'exécuter cette macro de données nommée depuis n'importe quel événement "après quelque chose" de n'importe quelle table.

 

En effet, vous avez plus d'options d'actions de macros "Après" que "Avant" :

Ainsi, admettons que vous désiriez exécuter la macro MD_EMail Après Mise à jour de T_2, et Après suppression de T_1, il vous suffit d'aller dans T_1 et T_2, et d'appeler MD_EMail depuis leurs événements correspondants, ainsi :

Maintenant, essayez de modifier un enregistrement dans T_2 : un message survient :

C'est un message tout à fait normal : Outlook se demande si c'est bien normal qu'un autre programme (Access, en l'occurrrence) veuille expédier des messages automatiquement. Vous devez patienter le temps que la barre verte de progression ait terminé avant de pouvoir cliquer sur Accepter.

A la fin, vous cliquez sur Accepter, et vous ouvrirez ensuite Outlook pour constater qu'un message est bien prêt à partir dans votre boîte d'envoi.

Evidemment, cette manipulation ne fonctionne absolument pas avec des webmails comme Yahoo ou Gmail.

Si vous tentez de supprimer un enregistrement dans T_1, le même message d'attention va apparaitre.

Table dédiée aux macros de table

En conclusion, l'avantage de ces macros nommées est de pouvoir contenir beaucoup d'instructions (dans notre cas, nous en avons qu'une : Envoyer un E-Mail), et de pouvoir se faire appeler depuis plusieurs événements de plusieurs tables différentes.

Ainsi, il est peut-être sage de créer une table expressément destinée à stocker les macros nommées (T_macroNommee par exemple) - Ou, pourquoi ne pas reprendre l'astuce de la table qui commence par USYS afin d'être automatiquement masquée ? ... Ainsi, vos macros nommées seraient stockées dans une table masquée USYSMacroNommee par exemple)

Les macros nommées sont appelables depuis n'importe quelle table. Mais elles sont incrustées dans une table en particulier (Ici, dans T_1). Si vous supprimez T_1 de votre base de données, toutes les tables qui appelleraient éventuellement la macro nommée MD_EMail ne pourront plus fonctionner. Ces macros nommées ne se trouvent pas dans la liste des macros "normales", que vous aviez peut-être l'habitude d'utiliser dans les versions précédentes d'Access...

Je vous invite à suivre la deuxième partie de ce didacticiel : l'historisation complète de toutes les tables d'une base de données.