Création d'un générateur d'étiquettes

Jean-Marie BAGNIS

Cet article est destiné à un public ayant quelques notions dans le langage VBA et dans la manipulation des requêtes SQL. L'exemple a été réalisé sous Access 2010, et n'utilise aucune bibliothèque particulière. L'adaptation du code à des versions antérieures ne posent donc à priori aucun problème particulier.

Commentez

- Titre : Création d'un générateur d'étiquettes

- Auteur : Jean-Marie BAGNIS

- Parution : 2 mars 2014

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2014 JimboLion. 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

- II. Les entités

- II-A. Les tables génériques

- II-A-1. Description des tables génériques

- II-A-2. Description des tables servant d'illustration

- II-A-2-a. Les articles

- II-A-2-b. Les Clients

- III. La création d'un nouveau format

- III-A. Le formulaire F_ListeFormats

- III-A-1. La requête R_Articles_Sample

- III-A-2. La requête R_Clients_Sample

- III-B. Les propriétés du format

- III-B-1. La constitution du formulaire

- III-B-2. La constitution du sous-formulaire

- III-B-3. Utilisation de notre formulaire

- IV. La création de notre état Gabarit

- V. Exploitation du format depuis notre formulaire

- VI. Conclusion

- VII. L'application à télécharger

- VIII. Remerciements

I - Introduction

Cet article s'adresse particulièrement à tous ceux qui distribuent une application dont la finalité ou l'une des particularités est de générer des étiquettes. Je prendrai pour exemple, la gestion des adhérents d'une association ou l'impression des étiquettes visuelles à l'issue des mouvements d'entrée d'articles en stock.

Chaque utilisateur a ses propres demandes spécifiques (format, couleurs, positions) sur des supports souvent différents. Devant cela, il vous reste trois possibilités :

1. Développer à la demande de nouveaux formats ;

2. Former l'utilisateur à la génération de ces propres formats (imaginer la complexité de déploiement de vos mises à jour) sous condition exclusive d'acquérir une version de l'outil de développement ;

3. Imposer vos choix.

Le but de ce document n'est pas de vous donner une solution toute faite, vous constaterez d'ailleurs que par simplicité un certain nombre de champs ont volontairement été laissés visibles. Le code source fourni permettra à chacun d'adapter sa propre solution.

Nous allons au travers de deux exemples précis : l'impression d'étiquettes adresses et articles avancer dans l'élaboration de notre projet de générateur d'étiquettes.

II - Les entités

Les clés primaires seront préfixées ID_, le nom correspond au nom de l'entité. Les clés étrangères seront suffixées de _FK, le nom du champ correspond au nom de la table héritée. Le nom de chaque champ est explicite. Aucune clé composite n'a été nécessaire dans la construction de ce tutoriel.

II-A - Les tables génériques

Nous entendrons par table générique, l'ensemble des tables nécessaires au fonctionnement de l'applicatif. Ces tables peuvent être déclinées en plusieurs tables mises en relation mais j'ai sciemment regroupé des valeurs au risque de les rendre redondantes afin de simplifier ce tutoriel.

Le modèle présenté ci-dessous matérialise les tables génériques nécessaires à notre projet.

II-A-1 - Description des tables génériques

T_TypesEtiquettes: il s'agit des différents types d'étiquettes proposées à l'utilisateur. Dans notre exemple étiquettes adresses et étiquettes articles. Cette table est non accessible à l'utilisateur. Elle est mise en place à l'initiation de l'application. L'évolution est assurée par le développeur.

Nom du Champ

Type de données

Description

ID_TypeEtiquette

Numéro Auto

Identifiant unique caractérisant un type d'étiquette.

DescriptionFormat

Texte

Nom du type d'étiquette.

T_ChampsEtiquettes: dans cette entité, nous isolerons les items nécessaires à l'élaboration d'une étiquette. L'exhaustivité des champs de la table n'est nullement nécessaire, le développeur aura pris soin de mettre à disposition de l'utilisateur l'ensemble des champs dont il a réellement besoin. Cette table est non accessible par l'utilisateur final.

Nom du Champ

Type de données

Description

ID_ChampEtiquette

Numéro Auto

Identifiant unique caractérisant un item nécessaire à l'élaboration de l'étiquette.

NomEtiquette

Texte

Nom d'alias du champ. Cette notion d'alias permet de spécifier à l'utilisateur sous forme compréhensible la nature de l'information à intégrer sur l'étiquette.

Dictionnaire

Texte

Nom du champ ou de l'alias tel que fourni dans la table référencée ou la requête servant de source de données à l'état. Le dictionnaire intègre également la notion de champ calculé (voir plus loin).

Couleur

Entier Long

Code couleur servant à matérialiser le positionnement de l'item dans l'outil de génération. Cette donnée est exprimée en décimal et non en hexadécimal.

Ordre

Octet

Chaque champ dans un type d'étiquette se voit attribué un ordre. Les champs communément utilisés se verront attribuer une valeur basse. Un numéro d'ordre est unique pour chaque type d'étiquette. Le champ Section (obligatoire car définit les dimensions de l'étiquette) porte toujours le numéro 1.

TypeEtiquette_FK

Entier Long

Clé étrangère issue de la table T_ChampsEtiquettes.

TypeLigne

Octet

Matérialise le type d'Item. La valeur 1 correspond à la section, la valeur 2 à un champ. Cette distinction permettra d'agir différemment sur chaque nature de contrôle lors de la manipulation de nos objets ou dans le code.

T_NomsEtiquettes: Cette table va permettre de stocker les nouvelles étiquettes générées par l'utilisateur ainsi que fournir un certain nombre de formats prédéfinis.

Nom du Champ

Type de données

Description

ID_NomEtiquette

Numéro Auto

Identifiant unique caractérisant une étiquette.

DescriptionFormat

Texte

Nom du format d'étiquette choisi par l'utilisateur.

TypeEtiquette_FK

Entier Long

Clé étrangère issue de la table T_ChampsEtiquettes.

Source

Texte

Nom de la source de données fournie par le concepteur et associé au format d'étiquettes. Il peut s'agir d'une table ou d'une requête. La source de données générée par une étiquette adresses ou articles diffère logiquement.

GabaritEtat

Texte

Nom du report générique associé au format programmé. Le même gabarit peut servir à l'ensemble des formats et c'est là tout l'intérêt de cet article. Malgré tout je considère qu'un état associé aux différents formats d'articles et un format associé aux différents formats d'adresses doivent être privilégiés. Nous verrons pourquoi plus tard.

T_ParametresEtiquettes: cette table va permettre de stocker l'ensemble des données personnalisables et renseignées par l'utilisateur. À la création d'un nouveau format, et fonction du type d'étiquette, une requête de type ajout alimentera automatiquement cette table. Nous aurons l'occasion de visualiser le contenu de cette requête dans la section relative aux formulaires.

Nom du Champ

Type de données

Description

ID_ParametreEtiquette

Numéro Auto

Identifiant unique caractérisant le paramétrage d'un item.

ChampEtiquette_FK

Entier Long

Clé étrangère issue de la table T_ChampsEtiquettes.

NomEtiquette_FK

Entier Long

Clé étrangère issue de la table T_NomsEtiquettes.

NomPolice

Texte

Nom de la police utilisée. J'ai volontairement affecté une liste de valeurs. D'autres utilisations sont possibles (table héritée, accès aux API afin de récupérer la liste des fontes installées).

TaillePolice

Octet

Taille de la police.

PositionHaut

Réel

Position de l'élément à partir du bord supérieur de la section. La valeur de l'élément section est égale à 0.

PositionGauche

Réel

Position de l'élément à partir du bord gauche de la section. La valeur de l'élément section est égale à 0.

Hauteur

Réel

Hauteur de l'élément.

Largeur

Réel

Largeur de l'élément.

EpaisseurCaracteres

Entier

Parmi les différents choix standards, gras.

Italique

Booléen

Indique si la matérialisation de l'élément est en italique sur le visuel.

Souligne

Booléen

Indique si la matérialisation de l'élément doit être soulignée sur le visuel.

Alignement

Octet

Permet de définir l'alignement de l'élément sur l'étiquette (standard, droite, gauche, centré.)

AfficherOuiNon

Booléen

Cet élément doit-il apparaître sur le visuel ?

NombreDecimales

Octet

Nombre de décimales à afficher sur les valeurs numériques ou cadrage spécifique sur le nombre de caractères pour les éléments de type Texte (n premiers caractères). Ce champ n'a aucun impact sur les champs de type mémo ou date.

II-A-2 - Description des tables servant d'illustration

II-A-2-a - Les articles

T_Articles: cette table permet de stocker l'ensemble des informations concernant un article.

La description de cette table comporte peu d'intérêt puisque dépendante de chaque solution. Le contenu des informations présenté sur le modèle ci-dessus est suffisamment explicite pour que chacun puisse s'y reconnaître.

T_Fournisseurs: cette table permet de stocker l'ensemble des informations concernant un fournisseur. La relation entre la table fournisseurs et articles est basée sur la règle suivante :

II-A-2-b - Les Clients

T_Clients: cette table permet de stocker l'ensemble des informations concernant un client. La description de cette table comporte peu d'intérêt puisque dépendante de chaque solution. Le contenu des informations présenté sur le modèle ci-dessus est simple et ne requiert aucune explication supplémentaire.

T_CodesPostaux: cette table permet de stocker l'ensemble des informations concernant les différents codes postaux.

T_Civlités: cette table permet de stocker l'ensemble des informations concernant les différentes civilités.

III - La création d'un nouveau format

Afin d'illustrer la création d'un nouveau format, nous avons mis en place un formulaire de création. Si la construction d'un tel formulaire est d'une simplicité primaire, cela nous permettra de mieux comprendre les différentes étapes nécessaires à la réalisation ultime de notre étiquette.

III-A - Le formulaire F_ListeFormats

Ce formulaire est directement lié à la source de données T_NomEtiquettes. Il va nous servir à accéder à la modification ou à la création de nos formats. Lors de la création d'un nouveau format, l'insertion automatique dans la table T_ParametresEtiquettes s'opère automatiquement.

Le changement d'un type d'étiquette opère la suppression automatique des anciens paramètres avant réinjection des nouveaux paramètres. Un message d'avertissement vous invite à confirmer votre choix.

Les fonctionnalités d'usage sont prévues sur cet écran, exception faite de la suppression d'un format. Tel n'est pas l'objet de cet article.

La procédure suivante s'exécute donc après changement d'une valeur du type d'étiquette sur le formulaire.

Private Sub Lb_TypeEtiquette_AfterUpdate()
'
' Fonction permet de gérer les entrées dans la table T_ParametresEtiquettes
' lors de la création ou la modification d'un format.
'
Dim oDb As DAO.Database
Set oDb = CurrentDb
Dim R_Sql As String
Dim Reponse As Integer
'
On Error GoTo Err_Lb_TypeEtiquette_AfterUpdate
Me.Refresh
'
' Contrôle existence format présent dans la table parametres
' on indique à l'utilisateur que la suppression des anciens formats va avoir lieu
'
If DCount("[nomEtiquette_FK]", "Controle_Existence_Format", "") Then
Reponse = MsgBox("Vous tentez de modifier un format déjà créé," & vbCrLf & "les anciennes valeurs seront supprimées." & vbCrLf & vbCrLf & "Souhaitez-vous continuer ?", vbQuestion + vbYesNo, "Modification format")
If Reponse = 7 Then Exit Sub
End If
'
' Suppression des anciennes entrées dans ma table T_ParametresEtiquettes
' En cas de changement de type d'articles par exemple
'
If Nz(Me.ID_Etiquette, 0) > 0 Then           ' on vérifie si un enregistrement est créée
R_Sql = "DELETE T_ParametresEtiquettes.nomEtiquette_FK FROM T_ParametresEtiquettes WHERE (((T_ParametresEtiquettes.NomEtiquette_FK)=" & Me.ID_Etiquette & "));"
oDb.Execute R_Sql, dbFailOnError
End If
'
' Insère en fonction du typage d'articles les enregistrements dans la table Parametres_Etiquettes
'
If Nz(Me.Lb_TypeEtiquette, 0) > 0 Then
R_Sql = "INSERT INTO T_ParametresEtiquettes ( ChampEtiquette_FK, NomEtiquette_FK ) SELECT T_ChampsEtiquettes.ID_ChampEtiquette, " & Me.ID_Etiquette & " AS Etiquette_ID FROM T_ChampsEtiquettes WHERE (((T_ChampsEtiquettes.typeEtiquette_FK)=" & Me.Lb_TypeEtiquette & "));"
oDb.Execute R_Sql, dbFailOnError
'
' Mise à jour des paramètres de la section (largeur par defaut...)
' On récupère pour cela le numéro d'ordre à 1 et propriété de la section Visible à True
'
R_Sql = "UPDATE T_ChampsEtiquettes INNER JOIN T_ParametresEtiquettes ON T_ChampsEtiquettes.ID_ChampEtiquette = T_ParametresEtiquettes.ChampEtiquette_FK SET T_ParametresEtiquettes.PositionHaut = 0, T_ParametresEtiquettes.PositionGauche = 0, T_ParametresEtiquettes.Hauteur = 2, T_ParametresEtiquettes.Largeur = 5, T_ParametresEtiquettes.AfficherOuiNon = True WHERE (((T_ParametresEtiquettes.NomEtiquette_FK)=" & Me.ID_Etiquette & ") AND ((T_ChampsEtiquettes.TypeLigne)=1));"
oDb.Execute R_Sql, dbFailOnError
End If
'
' Fermeture et libération
'
Exit_Lb_TypeEtiquette_AfterUpdate:
oDb.Close
Set oDb = Nothing
Exit Sub
Err_Lb_TypeEtiquette_AfterUpdate:
MsgBox Err.Description
Resume Exit_Lb_TypeEtiquette_AfterUpdate
End Sub

La matérialisation du formulaire de création ou modification du format nous permet d'obtenir l'écran suivant :

Les champs sources de données et gabarit du format seront renseignés par l'utilisateur. Le concepteur aura à charge de fournir dans la solution la liste des sources de données (requêtes ou tables dépendantes de son modèle) et gabarits disponibles (format générique de l'état d'impression). Le développeur saura adapter cette partie aux règles de sécurisation nécessaires (accès restrictifs aux objets permettant l'adaptation du gabarit d'étiquettes). Dans mon exemple, les valeurs inscrites dans les deux zones de liste déroulantes (sources de données et gabarits) ont été définies par le concepteur. Ces notions vous paraîtront plus claires en avançant dans la lecture. La correspondance entre les champs (ou alias) de votre requête sont inscrites pour mémoire dans la table T_ChampsEtiquettes.

Nous avons donc préalablement bâti deux requêtes correspondant aux critères énoncés dans l'élaboration de notre générateur. L'heure est venue de vous montrer ces deux requêtes.

III-A-1 - La requête R_Articles_Sample

Le rôle attendu par cette requête est d'afficher l'ensemble des produits impactés par la génération de nos étiquettes. Cette requête est soumise à la contrainte suivante : l'article est supposé de type imprimable. Pour cela, un champ booléen de type étiquette Article_Aimprimer a été créé dans la liste des champs de l'entité T_Articles. Cette requête répond également à une nouvelle contrainte posée, elle concerne l'ensemble des produits du stock. Vous savez tous comme moi qu'une requête supposée de relation 1 à 1 ne retourne qu'une ligne. Dans notre cas si l'article N dispose de 4 entrées en stock, le nombre d'étiquettes à imprimer est de 4. Nous avons donc rajouté une table TBL_Count afin de dupliquer automatiquement les lignes. Cette astuce est fournie dans la base exemple, et extrait de la FAQ https://access.developpez.com/faq/?page=TAEtat#multEnreg

L'exécution de la requête pourrait ressembler à ceci :

III-A-2 - La requête R_Clients_Sample

Cette requête ne revêt aucune difficulté particulière. Afin d'intégrer la notion de champ calculé, nous créerons un alias composé du nom complet de la personne (civilité + nom + prénom) ainsi qu'un alias situation composé du code postal et la ville ceci afin d'imaginer la notion abordée dans la définition des entités. Vous visualiserez à la fin du document le rôle et l'intérêt d'agrégation d'un champ.

L'exécution de la requête :

III-B - Les propriétés du format

L'ouverture de la l'écran de modification des paramètres du format s'opère en cliquant sur le bouton <Générateur> du formulaire F_ListeFormats. Le code généré par ce bouton est inscrit ci- dessous :

Private Sub Btn_ModifyFormat_Click()
Dim F_Etiquette As Long
'
On Error GoTo Err_Btn_ModifyFormat
F_Etiquette = Nz(Me.ID_Etiquette, 0)
Dim Reponse As Integer
'
If Nz(Me.Tb_LongLibel, "") = "" Then
Reponse = MsgBox("Le nom du format doit être renseigné", vbCritical, "Erreur")
Exit Sub
End If
'
If Nz(Me.TypeEtiquette_FK, 0) = 0 Then
Reponse = MsgBox("La sélection d'un type de format est obligatoire", vbCritical, "Erreur")
Exit Sub
End If
'
'
' Fermeture du formulaire F_Liste
'
DoCmd.Close
'
' Ouveture des propriétés du format en utilisant le filtre
'
If F_Etiquette > 0 Then
DoCmd.OpenForm "F_Etiquettes_Principal", , , "ID_NomEtiquette=" & F_Etiquette, , acWindowNormal
End If
'
Exit_Btn_ModifyFormat:
Exit Sub
Err_Btn_ModifyFormat:
MsgBox Err.Description
Resume Exit_Btn_ModifyFormat
End Sub

Le formulaire F_Etiquettes_Principal hérite donc d'une relation entre la table T_NomEtiquettes et T_ParametresEtiquettes mettant en œuvre la notion de formulaire et de sous-formulaire.

En mode création, la visualisation de cet écran nous donne cette représentation :

III-B-1 - La constitution du formulaire

La réalisation de notre formulaire nécessite une étape préparatoire et de réflexion assidue. Vous remarquerez sur la partie supérieure droite un certain nombre d'étiquettes (nommées et2 à et19). Toutes les propriétés <visible> de chaque étiquette sont définies à <Non>. Les règles de nommage de nos champs sont essentielles, car elles vont nous permettre d'utiliser la notion de variable lors de la communication entre le code VBA et les actions menées par l'utilisateur. L'état de chacun de ces champs variera donc sur les règles de gestion d'événements placés dans notre sous-formulaire. Cela vous semble un peu barbare à première vue, mais la lecture de ce chapitre devrait vous éclairer un peu plus. La partie gauche de l'écran dispose également d'un champ indépendant (nommé et1) et correspondant à l'élément de la section. Vous comprenez donc les règles précises de numéro d'ordre évoquées dans le chapitre II (Description des tables génériques). Deux traits nommés Trait et Trait2 apparaissent également dans la partie supérieure de l'écran et vont servir de repère pour le pliage de l'étiquette (option intéressante lorsque le support nécessite un pliage de l'étiquette visuelle).

Concernant les autres contrôles, nous reviendrons plus tard sur leurs utilisations respectives. Afin de construire dynamiquement notre étiquette, attardons-nous sur la réalisation de notre sous-formulaire, préambule nécessaire au fonctionnement du formulaire principal. Comme évoqué précédemment, la liste des champs de la table T_ParametresEtiquettes constitue donc la source de notre sous-formulaire. Cette table, je le rappelle est elle-même issue de la table T_champsEtiquettes, le nombre d'enregistrements varie donc en fonction du type d'étiquettes (articles, clients voire autres si besoin).

Dorénavant, nous allons visualiser le contenu de notre table T_ChampsEtiquettes (liste des éléments mis à disposition par le concepteur à destination de l'utilisateur) :

ainsi que la génération spontanée dans la table t_parametresEtiquettes lors de la création d'un nouveau format.

Afin de mieux comprendre les relations entre ces deux tables et aborder de manière plus sereine la suite de nos explications, le regroupement de ces deux requêtes dont le principe est le suivant :

et la visualisation précise de cette relation (pour une étiquette article) :

Nous avons conservé dans le source cette requête sous le nom R_Sample_ChampsEtiquettes_ParametresEtiquettes. Elle vous permettra de valider la cohérence de vos relations et vérifier que l'ensemble des éléments souhaités seront mis à disposition de l'utilisateur.

À partir de cet exemple précis, nous visualisons beaucoup plus facilement la relation bâtie entre les différentes étiquettes (et1 à et19) et les champs nécessaires à la construction de notre format. Le numéro d'ordre de chaque élément influera donc sur le comportement de chaque étiquette, par exemple la section (et1), le champ Fournisseur sur l'étiquette et3, le champ prix de vente sur l'étiquette et9 et ainsi de suite de notre formulaire principal.

III-B-2 - La constitution du sous-formulaire

La source de données de ce sous-formulaire basée sur la table T_Parametres_Etiquettes servira à l'utilisateur. Les propriétés accessibles sur le champ section étant différentes de celles des autres champs, nous allons donc interdire la saisie d'informations inutiles du champ section. Pour mémoire, la section correspond à la section Détail de l'état et hérite donc des mêmes propriétés. Ainsi la navigation opérée sur le sous-formulaire répond au code suivant. Ce code est appelé sur événement <SurActivation> du sous-formulaire :

Private Sub Form_Current()
'
Property_Fields
Me.Auto_EnTete.Caption = Me.Nom_Du_Champ
'
End Sub

La procédure Property_Fields associée à l'appel :

Private Sub Property_Fields()
'
' Fonction permettant d'inhiber l'affichage des contrôles sur la ligne de type section
'
Dim bolType_Ligne As Boolean
bolType_Ligne = Not (Me.Type_Ligne = 1)
'
Me.Position_Haut.Enabled = bolType_Ligne
Me.Position_Gauche.Enabled = bolType_Ligne
Me.Souligne.Enabled = bolType_Ligne
Me.Italique.Enabled = bolType_Ligne
Me.Taille_Police.Enabled = bolType_Ligne
Me.Nom_Police.Enabled = bolType_Ligne
Me.Epaisseur_Caractères.Enabled = bolType_Ligne
Me.Alignement.Enabled = bolType_Ligne
Me.Nombre_Décimales.Enabled = bolType_Ligne
Me.Afficher_OuiNon.Enabled = bolType_Ligne
Me.Couleur_Texte.Enabled = bolType_Ligne
Me.Format_Champ.Enabled = bolType_Ligne
'
End Sub

Détaillons donc l'ensemble des champs fournis dans le sous-formulaire. Celui-ci se décompose en quatre parties essentielles à savoir :

1. La définition des propriétés des champs de chaque élément nécessaire à l'élaboration de notre format d'étiquette ;

2. La position de départ de l'élément ainsi que ses dimensions ;

3. L'aide à la réalisation caractérisée par une image ;

4. L'affichage de l'élément dans le format.

La modification de certaines propriétés influe donc dynamiquement sur le formulaire principal en positionnant l'élément dans la zone de section définie.

L'action effectuée sur les flèches de navigation est directement liée à l'ordre d'affichage défini. Ainsi l'élément section sera toujours le premier enregistrement. Les propriétés susceptibles d'être utilisées le plus fréquemment auront donc un numéro d'ordre bas afin de faciliter le travail de conception.

La modification des propriétés suivantes : hauteur du champ, largeur du champ, position en hauteur, position en largeur et afficher l'élément impliquent donc la gestion d'un événement afin de mettre à jour le formulaire principal. Pour chacun de ces champs, l'événement déclenché se gère sur la propriété <Après Mise à Jour>. Nous verrons l'effet des autres propriétés au moment de la génération de notre état (aucun impact sur le formulaire parent).

Les procédures générées : les deux premières procédures sont contenues dans le code du sous-formulaire lui-même. Les deux dernières procédures sont enregistrées dans un module séparé.

Private Sub Hauteur_AfterUpdate()
'
' Actualisation des données du formulaire Parent
Show_Property

End Sub

Private Sub Show_Property()
'
' Actualisation des données du formulaire Parent
'
Dim Larg_Section As Integer
Dim Reponse As Integer
'
On Error GoTo Err_Show_Property
Select Case Me.Type_Ligne
'
' Contrôle de la position du champ dans la section
'
Case Is <> 1
'
' Contrôle dépassement bord droit
'
Larg_Section = Parent.Et1.Width
'
If (Me.Largeur * 567) + (Me.Position_Gauche * 567) > Nz(Parent.Et1.Width, 0) Then

Reponse = MsgBox("La largeur du champ " & Me.Nom_Du_Champ & vbCrLf & "dépasse la section de l'étiquette", vbCritical, "Erreur")
'
Select Case Me.ActiveControl.Name
Case "Largeur"
Me.Largeur = 0
Case "Position_Gauche"
Me.Position_Gauche = 0
End Select
End If
'
' Contrôle dépassement bord haut
'
If (Me.Hauteur * 567) + (Me.Position_Haut * 567) > Nz(Parent.Et1.Height, 0) Then
Reponse = MsgBox("La hauteur du champ " & Me.Nom_Du_Champ & vbCrLf & "dépasse la section de l'étiquette", vbCritical, "Erreur")
'
Select Case Me.ActiveControl.Name
Case "Hauteur"
Me.Hauteur = 0
Case "Position_Haut"
Me.Position_Haut = 0
End Select
End If
End Select
'
Call Affiche_Propriétés(Me.Parent.Name, Me.Nom_Du_Champ, Me.Afficher_OuiNon, Me.Hauteur, Me.Largeur, Me.Position_Haut, Me.Position_Gauche, Me.ChampEtiquette_Ordre)
'
Exit_Show_Property:
Exit Sub
Err_Show_Property:
MsgBox Err.Description
Resume Exit_Show_Property
End Sub

Notez que dans la partie code évoquée ci-dessus, le contrôle mis en place sur l'élément afin de ne pas dépasser les dimensions spécifiées dans la section.

Public Sub Affiche_Propriétés(NomForm, Nom_Champ, AFF, Haut, Larg, P_Ve, P_Ho, O_Element)
'
' Fonction permettant de vérifier l'affichage et envoyer le rafraîchissement dynamique du
' formulaire Parent
'
Dim retour As Boolean       ' retourne false si la fonction échoue
'
If O_Element Then
retour = M_Propriétés(NomForm, O_Element, Nom_Champ, AFF, Haut, Larg, P_Ve, P_Ho)
End If
'
End Sub

Function M_Propriétés(NomForm, Element, Nom_Champ, Affiche, Hauteur, Largeur, Pos_Haut, Pos_Gauche) As Boolean
'
' Fonction de rafraîchissement dynamique du formulaire F_Etiquettes
' L'argument Element (défini par l'ordre) autorise la modification du comportement de chaque étiquette (ET1 à Etn)
' Nous utiliserons donc une variable afin de manipuler l'ensemble des contrôles
'
' La fonction renvoie False en cas d'erreur
'
Dim Fto As Form
Set Fto = Forms(NomForm)

On Error GoTo Err_M_Propriétés
'
Dim Nam_Control As String
Dim Aff_Repere As Boolean
'
M_Propriétés = False
'
' Afficher les reperes de pliage
'
Aff_Repere = Nz(Fto![Aff_Repère], True)
'
Nam_Control = "Et" & CStr(Element)
If Nz(Element, 0) = 0 Then Exit Function
'
Select Case Element     ' Element 1 il s'agit d'une section
Case 1
Select Case Affiche
Case -1
With Fto.Controls(Nam_Control)
.Visible = True
.Width = Largeur * 567
.Height = Hauteur * 567
End With
'
Select Case Aff_Repere
Case -1
With Fto
.[Trait].Visible = True
.[Trait].Height = Hauteur * 567
.[Trait].top = Pos_Haut * 567 + (0.15 * 567)
.[Trait].Left = (Pos_Gauche + (Largeur / 2)) * 567 + (0.2 * 567)
'
.[Trait2].Visible = True
.[Trait2].Width = Largeur * 567
.[Trait2].top = (Pos_Haut + (Hauteur / 2)) * 567 + (0.15 * 567)
.[Trait2].Left = Pos_Gauche * 567 + (0.2 * 567)
End With
Case Else
With Fto
.[Trait].Visible = False
.[Trait2].Visible = False
End With
End Select
Case Else
Fto.Controls(Nam_Control).Visible = False
Select Case Aff_Repere
Case -1
With Fto
.[Trait].Visible = False
.[Trait2].Visible = False
End With
End Select
End Select
Case Else
Select Case Affiche
Case -1
With Fto.Controls(Nam_Control)
.Visible = True
.Width = Largeur * 567
.Height = Hauteur * 567
.top = Pos_Haut * 567 + (0.15 * 567)
.Left = Pos_Gauche * 567 + (0.2 * 567)
.Caption = Nom_Champ
'
End With
Case Else
Fto.Controls(Nam_Control).Visible = False
End Select
End Select
'
Set Fto = Nothing
M_Propriétés = True
'
Exit Function
Err_M_Propriétés:
M_Propriétés = False
MsgBox Err.Description & " dans le Module M_Propriétés"
End Function

Notez bien l'utilisation de Controls(Nam_Control) permettant de passer dans une variable la modification du comportant des étiquettes du formulaire F_Etiquettes_Principal. Une fois de plus, vous comprendrez le rôle décisif de l'ordre d'affichage dans les tables T_ChampsEtiquettes et la façon de le réutiliser à plusieurs effets (navigation, manipulation, etc.). Notez également que le positionnement des éléments est exprimé en Twips et non en centimètres (1 cm équivaut à 567 twips) et que distinction est faite des propriétés de type champ et section dans notre select case.
Un centimètre équivaut à 567 twips (et un pouce à 1 440 twips).

Nous n'avons pas pour autant résolu tous les problèmes posés par la modification des propriétés des champs dans notre sous-formulaire. Il est essentiel de pouvoir visualiser le format de l'étiquette au moment du chargement de notre formulaire. Dans cette problématique, et puisque nous avons pris soin d'isoler dans des fonctions dissociées les règles d'affichage de nos éléments, nous allons donc boucler sur tous les enregistrements de notre table T_ParametresEtiquettes du format concerné afin de rafraîchir dynamiquement la visualisation du format à l'écran. Ce code est donc mis en place sur l'événement <sur Ouverture> des propriétés du formulaire parent (F_Etiquettes_Principal) matérialisé par le code suivant :

Private Sub Form_Open(Cancel As Integer)
'
' Chargement des codes couleurs spécifié pour les valeurs de champs de nos contrôles
'
On Error GoTo Err_Form_Open

'
If Nz(Me.TypeEtiquette_FK, 0) = 0 Then Exit Sub
'
Dim oDb As DAO.Database
Set oDb = CurrentDb
Dim rst As DAO.Recordset
Dim Ordre_Affichage As Byte, Couleur As Long
Dim Nam_Control As String
Dim X As Boolean
'
' Génération du Recordset permettant de lier l'ensemble des couleurs spécifiées dans le paramétrage
' des champs
'
Me.Auto_EnTete.Caption = Me.Description_Format
'
Set rst = oDb.OpenRecordset("SELECT T_ChampsEtiquettes.Ordre, T_ChampsEtiquettes.Couleur, T_ChampsEtiquettes.TypeEtiquette_FK FROM T_ChampsEtiquettes WHERE (((T_ChampsEtiquettes.TypeEtiquette_FK)=" & Nz(Me.TypeEtiquette_FK, 0) & "));")
'
If rst.RecordCount = 0 Then
rst.Close
Set rst = Nothing
Exit Sub
End If
' Boucle sur le recordset afin de modifier la couleur de fond des étiquettes (et1 à et19)
Do Until rst.EOF

Ordre_Affichage = rst.Fields(0)
Couleur = rst.Fields(1)
'
Nam_Control = "Et" & CStr(Ordre_Affichage)
Me.Controls(Nam_Control).BackColor = Couleur
'
rst.MoveNext
Loop
rst.Close
'
' Affichage dynamique de l'étiquette en lecture des propriétés
' de la table T_ChampsEtiquettes
'
Set rst = oDb.OpenRecordset("SELECT T_ChampsEtiquettes.NomEtiquette, T_ParametresEtiquettes.PositionHaut, T_ParametresEtiquettes.PositionGauche, T_ParametresEtiquettes.Hauteur, T_ParametresEtiquettes.Largeur, T_ParametresEtiquettes.AfficherOuiNon, T_ChampsEtiquettes.Ordre, T_ChampsEtiquettes.TypeLigne FROM T_ChampsEtiquettes INNER JOIN T_ParametresEtiquettes ON T_ChampsEtiquettes.ID_ChampEtiquette = T_ParametresEtiquettes.ChampEtiquette_FK WHERE ((T_ParametresEtiquettes.NomEtiquette_FK)=" & Nz(Me.TB_Numero_Format, 0) & ");")
'
If rst.RecordCount = 0 Then
rst.Close
Set rst = Nothing
Exit Sub
End If
'
Do Until rst.EOF
'
'Nom_Champ, Ele_Affiche, Ele_Hauteur, Ele_Largeur, Pos_Haut, Pos_Gauche, Ordre_Affiche
'
Call Affiche_Propriétés(Me.Name, Nz(rst.Fields(0), 0), Nz(rst.Fields(5), 0), Nz(rst.Fields(3), 0), Nz(rst.Fields(4), 0), Nz(rst.Fields(1), 1), Nz(rst.Fields(2), 1), Nz(rst.Fields(6), 0))
'
rst.MoveNext
Loop
'
' Fermeture et libération
'

Exit_Form_Open:
rst.Close
Set rst = Nothing
oDb.Close
Set oDb = Nothing
'
Exit Sub
Err_Form_Open:
MsgBox Err.Description
Resume Exit_Form_Open
End Sub

Le fait d'avoir déporté la fonction de rafraîchissement des éléments dans un module séparé nous permet donc d'accéder indépendamment à ce code depuis n'importe quel formulaire.

III-B-3 - Utilisation de notre formulaire

Lors de la création de notre format d'étiquette, l'image ci-dessous s'affiche sur notre écran :

L'élément Section (1) dont les valeurs par défaut (2) ont étés enregistrées dans la table est seul visible à l'écran. En effet, lors de l'insertion des données dans la table, cette valeur a été forcée à <Oui>. En modifiant les valeurs d'affichage des repères de pliage (3), les zones trait et trait2 sont rendues visibles ou invisibles.

Continuons donc notre article en modifiant les propriétés de notre étiquette. En changeant la valeur de la largeur du champ et de la hauteur du champ, vous constaterez la modification dynamique de la représentation de l'élément <Section> sur la partie supérieure. Les formats préconisés par les étiquettes articles ou adresses auront bien évidemment des valeurs tout à fait différentes. Concernant la section, seuls les éléments permettant de renseigner les dimensions sont disponibles.

Sur la partie inférieure de l'écran, nous pouvons ainsi naviguer sur les différents éléments mis à disposition de l'utilisateur.

En modifiant les valeurs coordonnées (3) et en ordonnant l'affichage de l'élément, la partie supérieure de l'écran a été instantanément redessinée. Le champ décrivant l'élément en cours est inscrit dans la zone (4). Cette valeur correspond au texte renseigné dans la table T_ChampsEtiquettes (NomEtiquette) et non à l'alias ou au nom du champ figurant dans la table (articles ou clients dans notre exemple), ceci afin d'offrir à l'utilisateur un confort particulièrement apprécié (vous le savez comme moi que le nommage d'un champ dans une table ou une requête ne paraît pas évident pour un client néophyte). Vous pouvez modifier l'ensemble des propriétés de chaque élément mais ces changements n'interviennent pas dans la visualisation dynamique de notre format (nous verrons leurs utilités dans la partie conception de notre état). En marge de la couleur, un écran vous permettra d'afficher un panel de différents coloris avec l'ensemble des valeurs décimales à associer au champ.

Procédez ainsi pour l'ensemble des éléments. En cliquant sur le bouton <Imprimer Etiquettes> vous pourrez visualiser l'affichage de votre format généré. Nous reviendrons plus tard sur cette fonctionnalité.

Dans la liste des formats, j'ai inséré une police 3of9 de type code- barres. L'utilisation de cette valeur exige que vous ayez préalablement installé cette police.

Après quelques manipulations, vous devriez obtenir ce genre de représentation à l'écran :

Le contenu de la table T_Parametres filtrée sur le format nous affiche donc le résultat suivant :

Nous en avons terminé sur la partie Formulaires. Vous pouvez également vous essayer à la création d'un format d'étiquettes adresses. Les exemples ci-dessus sont fournis dans le code source de l'applicatif.

IV - La création de notre état Gabarit

Nous avons dans l'exemple précis créé deux gabarits destinés à chaque type d'étiquettes. Tout utilisateur souhaitant à la fois utiliser les étiquettes produits et étiquettes adresses n'aura ainsi pas à modifier les marges d'impression ou fonctionnalités de mise en page (bac, nombre de colonnes, etc.) à chaque impression. Les fonctionnalités évoquées ici sont étroitement liées à l'état lui-même et non aux propriétés modifiables. En effet, chaque imprimante disposant de caractéristiques différentes, il nous est malheureusement impossible de définir avec certitude la position de cadrage au bord gauche et haut de l'état. Mais cela ne revêt en fait aucune importance, car chaque utilisateur aura pris soin d'identifier son propre format et mis à jour cette partie configuration qu'une seule et unique fois. Un de mes prochains articles sera d'ailleurs consacré à cette partie. Le code contenu dans chaque gabarit est strictement identique puisque lié dans tous les cas au contenu inscrit dans les tables de paramétrage et nom d'étiquettes.

Notre état reprend les mêmes principes que ceux évoqués lors de la création de notre Formulaire Etiquettes. Dix-neuf étiquettes indépendantes ont été insérées et nommées suivant les mêmes règles (et1 à et19). La zone détail reçoit comme propriété Hauteur 0,55 cm alors que la largeur de l'état reçoit une valeur de 0,6 cm. Ces valeurs aussi basses ont pour unique but de positionner notre nuage d'étiquettes dans l'état.

À l'ouverture de notre format, l'événement <sur Ouverture> associé aux propriétés de l'état va donc associer les valeurs contenues dans la table paramètres aux étiquettes indépendantes. Un argument est transmis au format afin de spécifier à la fonction quel format a été invoqué depuis le formulaire F_Etiquettes_Principal. L'utilisation d'arguments est décrit dans la FAQ https://access.developpez.com/faq/?page=Ctrl#PreremplChamp. L'événement <Sur Clic> du bouton <Imprimer Etiquettes> contient le code suivant :

Private Sub Btn_Print_Click()
'
' Ouverture du Report
'
Dim report_Format As Long
Dim Gabarit As String
'
report_Format = Me.TB_Numero_Format
Gabarit = Nz(Me.Gabarit_Etat, "E_Etiquettes_Articles")
'
DoCmd.OpenReport Gabarit, acViewPreview, , , acWindowNormal, report_Format
'
End Sub

Le code associé à l'ouverture du report :

Sub Report_Open(Cancel As Integer)
'
'
' Fonction déclenchée sur ouverture de l'état et permettant d'associer aux étiquettes
' les valeurs renseignées par l'utilisateur
'
On Error GoTo Err_Report_Open_Etiquettes
'
' Déclarations
'
Dim oDb As DAO.Database
Set oDb = CurrentDb
Dim oRst As DAO.Recordset
Dim N_Source As String, Chaine_Sql As String, Nam_Control As String, chaine As String
Dim Numero_Format As Long
'
' Récupération du Numéro de Format
' Si le report est ouvert directement depuis le volet de navigation on travaille sur le format 1
'
Numero_Format = Val(Nz(Me.OpenArgs, 1))
'
' Attention l'ordre est important car la section doit être défini en premier lieu
'
N_Source = DLookup("Source", "T_NomEtiquettes", "[ID_NomEtiquette] = " & Numero_Format)
'
' Enregistre dans le RecordSource la valeur sélectionnée dans le paramétrage
'
Me.RecordSource = N_Source
'
Chaine_Sql = "SELECT T_ChampsEtiquettes.Ordre, T_ChampsEtiquettes.Dictionnaire, T_ParametresEtiquettes.* FROM T_ChampsEtiquettes INNER JOIN T_ParametresEtiquettes ON T_ChampsEtiquettes.ID_ChampEtiquette = T_ParametresEtiquettes.champEtiquette_FK "
Chaine_Sql = Chaine_Sql & " WHERE (((T_ParametresEtiquettes.AfficherOuiNon) = True) And ((T_ParametresEtiquettes.NomEtiquette_FK) = " & Numero_Format & ")) "
Chaine_Sql = Chaine_Sql & "ORDER BY T_ChampsEtiquettes.Ordre;"
'
Set oRst = oDb.OpenRecordset(Chaine_Sql)
'
If oRst.RecordCount = 0 Then
oRst.Close
Set oRst = Nothing
Exit Sub
End If
'
Do Until oRst.EOF
'
'MsgBox rst![ChampEtiquette_Ordre]
Nam_Control = "Et" & CStr(oRst![ordre])
'
Select Case oRst![ordre]
Case 1
' on travaille sur la section
Me.Et1.Height = Nz(oRst![Hauteur], 0) * 567
Me.Report.Width = Nz(oRst![Largeur], 0) * 567
Case Else
With Me.Controls(Nam_Control)
.ControlSource = Nz(oRst![Dictionnaire], Null)
.FontName = Nz(oRst![NomPolice], "Arial")
.FontSize = Nz(oRst![TaillePolice], 12)
'
.top = Nz(oRst![PositionHaut], 0) * 567
.Left = Nz(oRst![PositionGauche], 0) * 567
.Height = Nz(oRst![Hauteur], 0) * 567
.Width = Nz(oRst![Largeur], 0) * 567
.FontWeight = Nz(oRst![EpaisseurCaracteres], 400)
.FontItalic = Nz(oRst![Italique], 0)
.FontUnderline = Nz(oRst![Souligne], 0)
.TextAlign = Nz(oRst![Alignement], 0)
.Visible = True
.ForeColor = Nz(oRst![CouleurTexte], 0)
.Format = Nz(oRst![FormatChamp], "Text")
End With
'
' Le champ est tronqué à x caractères ou x décimales (type monétaire /
'
Select Case Nz(oRst![NombreDecimales], 0)
Case Is > 0
Select Case Nz(oRst![FormatChamp], "Text")
Case "Text"
chaine = "=left([" & Nz(oRst![Dictionnaire], Null) & "]," & Mid(Str(Nz(oRst![NombreDecimales], 0)), 2) & ")"
Me.Controls(Nam_Control).ControlSource = chaine
Case "Currency", "General Number", "Euro", "Fixed", "Standard", "Percent"
Me.Controls(Nam_Control).DecimalPlaces = Nz(oRst![NombreDecimales], 0)
End Select
End Select
End Select
'
oRst.MoveNext
Loop
'
oRst.Close
Set oRst = Nothing
'
oDb.Close
Set oDb = Nothing
'
Exit Sub
Err_Report_Open_Etiquettes:
MsgBox Err.Description
'Resume
End Sub

Ce code porte toute l'intelligence que l'on a mise en œuvre depuis le début de cet article. Remarquez précisément cette ligne :

.ControlSource = Nz(oRst![Dictionnaire], Null)

La mise à jour de la propriété <Source Contrôle> est associée au nom du champ spécifié dans la table ou de l'alias dans la requête (nécessaire lors des fonctions d'agrégation ou l'utilisation de champs calculés dans la requête). Chaque concepteur pourra donc définir à souhait la nature des informations à intégrer dans le format sous condition d'intégrer les valeurs dans la table T_ChampsEtiquettes.

V - Exploitation du format depuis notre formulaire

À partir de notre formulaire F_Etiquettes_Principal :

En cliquant sur le bouton <Imprimer>, vous devriez obtenir votre premier format généré de manière totalement paramétrée :

Un format étiquette articles :

un format étiquette adresses:

La mise en page sera configurée par chaque utilisateur afin de spécifier les paramètres intrinsèques de l'état. Une fois enregistré, ces paramètres seront mémorisés et demeureront indépendants des modifications effectuées dans le générateur d'états.

Pour ceux que cela intéressent, et qui souhaitent tirer encore plus parti de leurs états, je conseille d'étudier la propriété application.printer disponible depuis Access 2003. En intégrant les spécificités liées à chaque imprimante, vous disposerez alors d'un étal générique unique destiné à toute la partie impression d'étiquettes. Afin de faciliter la lecture de cet article et le rendre accessible au plus grand nombre, j'ai délibérément choisi de ne pas parler de cet aspect.

VI - Conclusion

À l'issue de cet article, vous comprendrez l'intérêt d'utiliser un générateur d'état. La diffusion de solutions en matière de gestion nécessite souvent une personnalisation eu égard à la nature de l'utilisateur. Lorsque cette diffusion se fait en nombre, il est impératif d'offrir des solutions pérennes et évolutives sans que cela remette en cause la maintenance de votre produit. Imaginez alors les coûts et temps perdus en déploiement, le nombre d'états générés, la perte en réactivité, etc.

Si vos applications sont distribuées en mode packagé incluant le RunTime, l'utilisateur pourra lui-même ré-adapter les formats existants ou modifier son propre format sans acquisition d'une licence Office.

Afin de donner encore plus de qualités à son application, le concepteur aura pris soin, à l'image des solutions de traitement de texte existant, d'intégrer les principaux formats standards du marché.

Cette approche détaillée qui je l'espère vous aura permis de donner une nouvelle dimension à votre solution est adaptable en fonction des besoins de chacun (mise à jour de gabarits, ajout de nouvelles propriétés, etc.). Il n'y a pas de limites quant à l'évolution que l'on pourrait apporter au générateur.

VII - L'application à télécharger

La base de données qui a servi d'exemple à cet article est disponible ici Générateur

VIII - Remerciements

Un immense merci à l'équipe de DVP et particulièrement à claudeleloup et f-leb pour leurs corrections orthographiques ainsi qu'à Tofalu, Gayot et Argyronet pour leurs relectures techniques.

Un remerciement très spécial à f-leb pour la patience qu'il a su me consacrer dans l'organisation et les processus de mise en place de cet article, sans qui celui-ci n'aurait jamais vu le jour.