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 :
- Développer à la demande de nouveaux formats ;
- 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 ;
- 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 IILes entités (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 :
- La définition des propriétés des champs de chaque élément nécessaire à l'élaboration de notre format d'étiquette ;
- La position de départ de l'élément ainsi que ses dimensions ;
- L'aide à la réalisation caractérisée par une image ;
- 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érateurGénérateur
VIII. Remerciements▲
Un immense merci à l'équipe de DVP et particulièrement à claudeleloup et f-leb pour leurs corrections orthographiques ainsi qu'à TofaluTofalu, 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.