Positionner un formulaire par rapport au contrôle d'un autre formulaire

Claude Leloup

Dans un formulaire, on souhaite parfois apporter - sur demande de l'utilisateur - un complément d'information aux données affichées dans l'enregistrement actif.

Ce tutoriel décrit comment ouvrir un second formulaire donnant plus de détails à un endroit qui dépend de la position d'un contrôle du premier.

Ceci est la suite du tutoriel Positionner un formulaire à un endroit déterminé.

Nous ferons un pas de plus dans l'exploitation de la théorie exposée dans le tutoriel précédent.
Cette fois, en cliquant sur un contrôle d'un formulaire, nous allons ouvrir un second formulaire juste en dessous et nous rectifierons sa position si ce second formulaire déborde de l'écran.

Commentez

- Titre : Positionner un formulaire par rapport au contrôle d'un autre formulaire

- Auteur : Claude Leloup

- Parution : 19 janvier 2012

- Mise à jour : 3 décembre 2015

- 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 ® 2011 Claude Leloup. 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. Avant-propos

- II. Prérequis

- III. L'objectif

- IV. Plantons le décor

- V. Allons-y pas à pas, d'abord avec des mots

- V-A. Le paramètre « droite »

- V-B. Le paramètre « bas »

- V-C. Paramètres « Largeur » et « Hauteur »

- V-D. Reste à vérifier qu'il y aura assez de place à l'écran

- VI. Récapitulons les données nécessaires et voyons comment les obtenir

- VII. Trouver la dimension des bordures

- VII-A. Suivons la piste

- VII-B. Traduisons en code

- VIII. Les dimensions de l'écran

- VIII-A. Une API : SystemParametersInfo

- VIII-B. Un peu de gymnastique pour le même résultat

- VIII-C. Une procédure pour automatiser ce processus

- IX. Rassemblons les pièces du puzzle

- IX-A. Le code du formulaire fPrincipal

- IX-A-1. Sur ouverture (Private Sub Form_Open)

- IX-A-2. Sur activation (Private Sub Form_Current)

- IX-A-3. Double-clic sur zdtMILLESIME (Private Sub zdtMILLESIME_DblClick)

- IX-A-4. Sur fermeture (Private Sub Form_Close)

- IX-B. Le code du formulaire fDetail

- IX-B-1. Sur ouverture (Private Sub Form_Open)

- X. La preuve que ça fonctionne quelle que soit la section qui héberge le contrôle

- XI. Conclusion

- XII. La base de données

- XIII. Remerciements

I - Avant-propos

Le code proposé dans cet article contient des API, c'est-à-dire des fonctions externes déclarées avec l'instruction Declare Function ou Declare Sub.

Depuis Office 2010, Microsoft propose une version 64 bits.

Si vous exécutez un fichier contenant ce type de déclaration avec Office 2010 64 bits, ce code ne compile plus et vous aurez ce message

Une réécriture de ces instructions est nécessaire.

Vous trouverez la documentation nécessaire dans ce tutoriel : http://arkham46.developpez.com/articles/office/vba64bits/

------------

Considérez ceci comme un prétexte pour manipuler quelques propriétés des formulaires Access et apprendre à écrire quelques lignes de code.

Si vous êtes pressé et que votre intérêt porte sur la solution, voyez plutôt ces deux contributions :

Argyronet a traité le sujet dans son tutoriel Comment positionner un formulaire ouvert depuis un autre, à droite de l'écran ;

Arkham46 dans cette contribution, donne tout le code pour positionner un formulaire sous un contrôle d'un autre formulaire.

La solution d'Arkham46 aboutit pratiquement au même résultat que nous et en moins de cent lignes, hors commentaires ! Et si vous ne comprenez pas tout ce que font les API qu'il utilise, ce n'est pas trop grave. Personnellement, je conduis une voiture depuis un demi-siècle et je n'ai toujours pas compris comment fonctionne un carburateur !(1)

II - Prérequis

Ce texte s'adresse à des utilisateurs Access débutants qui veulent s'initier à la programmation et qui ont lu le premier tutoriel.

III - L'objectif

Dans un formulaire, un double-clic sur un contrôle provoque l'ouverture d'un autre formulaire qui viendra s'afficher juste en dessous du contrôle cliqué...

… et si l'écran est trop petit, le second formulaire se déplace pour rester visible.

IV - Plantons le décor

N'importe quel formulaire ferait l'affaire. Mais pour passer en revue toutes les possibilités, nous avons choisi un formulaire en continu, avec trois sections : En-tête de formulaire, Détail et Pied de formulaire.

Il nous fallait donc baser notre formulaire sur une table. Tant qu'à faire, autant prendre une liste de produits sympathiques, à consommer toutefois avec modération.

En voici un extrait :

Voici le formulaire  fPrincipal  qui nous servira d'exemple :

Tous les champs de la table sont repris, leur nom est zdt suivi du nom du champ. Exemple, zdtDENOMINATION pour le champ DENOMINATION (« zdt » pour zone de texte).

Un gadget : zdtMILLESIME est mis en évidence en vert dans la ligne de l'enregistrement actif. (Voir plus bas comment on y arrive.)

Ma première idée d'exemple était : si on double-clique sur le millésime, on lance une recherche Google sur les sites spécialisés et on affiche les appréciations des œnologues réputés dans un formulaire. C'est sans doute possible mais ça nous aurait conduits trop loin dans les explications. (Voir à ce sujet Arkham46 VBA et développement Web.) Je vous invite à revoir le sujet... quand le débutant, à qui je m'adresse, aura acquis un peu plus de bouteille(s).

Ici, on fera plus simple, car en fait, n'importe quel formulaire indépendant fait l'affaire pour nos explications.

Dans l'exemple, il s'appelle fDetail et ne contient rien d'autre qu'une image, sans aucun rapport. Quoique !

Notre objectif : un double-clic sur le contrôle zdtMILLESIME de l'enregistrement en cours provoque l'ouverture de fDetail qui viendra se superposer à fPrincipal juste en dessous et aligné à gauche du contrôle double-cliqué. Ou ailleurs si manque de place.

V - Allons-y pas à pas, d'abord avec des mots

Si le formulaire fDetail est une fenêtre indépendante, pour afficher à l'endroit voulu, il « suffit » de fournir les bons paramètres à une instruction Docmd.MoveSize droite, bas, largeur, hauteur.

Dès lors, le problème se ramène à localiser le point sous la loupe :

en s'exprimant en twips, par rapport au coin supérieur gauche de notre écran.

V-A - Le paramètre « droite »

Relativement au coin supérieur gauche de l'écran, il se décompose en :

- la distance horizontale du coin supérieur gauche du formulaire fPrincipal ;

- l'épaisseur de la bordure gauche du formulaire fPrincipal ;

- la largeur de la coulisse du sélecteur d'enregistrement ;

- la distance entre cette coulisse et le bord gauche du contrôle ciblé.

V-B - Le paramètre « bas »

Relativement au coin supérieur gauche de l'écran, il se décompose en :

- la distance verticale du coin supérieur gauche du formulaire fPrincipal ;

- la hauteur de la bordure supérieure du formulaire fPrincipal ;

- à l'intérieur de fPrincipal, la position de la section détail actuellement active ;

- dans cette section, la position supérieure de contrôle ciblé ;

- la hauteur de ce contrôle.

V-C - Paramètres « Largeur » et « Hauteur »

Cela dépend des dimensions du formulaire fDetail. Fixons les idées : 4245 et 3495 twips, respectivement.

V-D - Reste à vérifier qu'il y aura assez de place à l'écran

En d'autres termes que les distances entre notre point visé et les bords droit et inférieur de l'écran permettent de loger le formulaire fDetail.

Si ça déborde à droite, il faudra corriger le paramètre droite actuel :

- ajouter la largeur du contrôle zdtMILLESIME ;

- soustraire la largeur de fDetail.

Donc, aligner le côté droit de fDetail sur le côté droit de zdtMILLESIME.

Si ça déborde en dessous, il faudra corriger le paramètre bas actuel :

- soustraire la hauteur du contrôle zdtMILLESIME ;

- soustraire la hauteur de fDetail.

Donc, placer le bord inférieur de fDetail sur le bord supérieur de zdtMILLESIME.

VI - Récapitulons les données nécessaires et voyons comment les obtenir

- Les expressions en vert n'ont pas de secret pour ceux qui ont lu le précédent tutoriel sur ce sujet.

- Pour les expressions en orange, le réflexe offre une explication bien documentée. Je ne vais pas la reproduire ici. Tapez <F1> pour ouvrir l'aide Access et ensuite le mot-clé dans la zone de recherche.

Vous obtiendrez quelque chose comme ceci :

- Pour les parties en rouge, on n'a pas de fonction ni de propriété. On va devoir ruser !

VII - Trouver la dimension des bordures

Monsieur de La Palice aurait dit un quart d'heure avant de mourir (donc quand il vivait encore) :

l'espace occupé par les bordures, c'est la différence entre
les mesures du formulaire avec bordures
et
ses mesures sans bordure.

Ce n'est pas aussi bête qu'il y paraît !

Une bordure latérale = la moitié de (la - ls ).

La bordure inférieure = la bordure latérale.

La bordure supérieure = (ha - hs) - bordure inférieure.

VII-A - Suivons la piste

Nous allons nous organiser pour ne faire les calculs qu'une seule fois par session (le fait d'ouvrir la base de données).

Nous allons mémoriser la dimension des bordures dans des variables globales.

Pour relever les mesures, nous allons créer une copie du formulaire fPrincipal et l'ouvrir successivement avec et sans bordures. Nous pourrons ainsi réaliser notre objectif.

Voici l'algorithme que nous allons appliquer :

VII-B - Traduisons en code

Nous affecterons les dimensions des bordures dans une variable globale d'un type que nous définissons dans le module mFonctions. Nous aurons aussi besoin de mémoriser les coefficients de conversion pixels => twips:

Type Bordure
Laterale As Long
Inferieure As Long
Superieure As Long
End Type
Type Pixel2Twip
Horizontal As Long
Vertical As Long
End Type
Global gBordure As Bordure
Global gBordureDispo As Boolean
Global gPixel2Twip As Pixel2Twip

Par défaut, gBordure.Laterale, gBordure.Inferieure et gBordure.Superieure valent zéro et gBordureDispo vaut False (Faux).

Dans le module de code du formulaire fPrincipal nous créons une procédure.

Enfoncez simultanément <Alt> et <F11> (on note <Alt-Key-F11>) pour ouvrir l'éditeur de code.

<Ctrl-Key-R> pour afficher l'explorateur de projet.

Dans ce dernier, vous cliquez sur Form_fPrincipal. Votre écran devrait afficher :

Cliquez sur le dérouleur de l'icône , il vient ceci . Cliquez sur Procédure...

Nous baptisons notre procédure : « CalculBordures ».

Nous choisissons de donner une portée « privée » à notre procédure. C'est-à-dire qu'elle sera disponible uniquement dans le périmètre du formulaire.

Cliquez sur OK, votre écran affiche :

Le reste du code sera inséré là où votre curseur clignote. Entre la déclaration de la procédure et son indicateur de fin.

Dans ce code, la propriété « Style Bordure » s'écrit « . BorderStyle ».
Pour trouver la correspondance entre les noms en français et la syntaxe de la propriété, procédez comme ceci :
-          affichez les propriétés du formulaire (ou de l'état) ;
-          cliquez sur la propriété choisie, elle s'affiche en surbrillance ;
-          enfoncez <F1>, l'aide s'affiche et vous donne le nom anglais.
Si une liste déroulante offre un choix de valeurs, cette dernière s'exprime dans le code au moyen d'un nombre. Ce nombre est en fait le rang de la ligne dans la liste en commençant par zéro.

Par exemple :
à « Aucun » correspond « 0 » ;
à « Dimensionnable » correspond « 2 ».
La syntaxe serait :
Forms!LeFormulaire.BorderStyle = 0 pour exprimer qu'il ne faut pas de bordure.

On définit des variables pour mémoriser les dimensions des fenêtres (rectForm) et les résultats intermédiaires (lLargeAvec, lHautAvec, lLargeSans et lHautSans, le « l » rappelle qu'elles sont de type entier long) :

Dim rectForm As RECT
Dim lLargeAvec As Long
Dim lHautAvec As Long
Dim lLargeSans As Long
Dim lHautSans As Long

Prenez dès le début de bonnes habitudes de nommage. Si vous aimez la rigueur, voyez les Conventions typographiques de Argyronet.

On affecte la valeur True à la variable gBordureDispo, pour signifier que les données relatives aux bordures seront disponibles. Cela nous évitera de recommencer tous les calculs dès que les dimensions seront connues.

S'il s'avère que le formulaire n'a pas de bordure, on laisse telles quelles les dimensions initiales (zéro) :

gBordureDispo = True
If Me.BorderStyle = 0 Then Exit Sub

Dans le cas contraire (une valeur de BorderStyle ≠0), on crée une copie du formulaire que l'on nomme « |bordure| ». Ce nom est arbitraire, la seule contrainte, c'est qu'aucun formulaire ne se nomme déjà ainsi dans la base de données. C'est pour cette raison que je choisis de décorer son nom avec des « pipes » (« | », <Alt> et <124> sur le pavé numérique), ce caractère étant rarement utilisé, un formulaire homonyme est peu probable.

On ouvre ce formulaire en mode caché. Bien qu'invisible à l'écran, la fenêtre existe et nous pouvons la mesurer au moyen de notre fonction PositionFormPx que nous connaissons depuis le tutoriel précédent :

DoCmd.CopyObject , "|Bordures|", acForm, CurrentProject.AllForms(Me.Name).Name
DoCmd.OpenForm "|Bordures|", , , , , acHidden
lLargeAvec = PositionFormPx("|Bordures|").Largeur
lHautAvec = PositionFormPx("|Bordures|").Hauteur

Nous refermons.
Nous allons maintenant modifier la copie en enlevant les bordures.

Nous l'ouvrons en mode création (caché), pour affecter la valeur 0 à sa propriété BorderStyle. Et nous passons en mode formulaire pour mesurer sans bordures :

DoCmd.Close acForm, "|Bordures|"
DoCmd.OpenForm "|Bordures|", acDesign, , , , acHidden
Forms![|Bordures|].BorderStyle = 0
DoCmd.OpenForm "|Bordures|", , , , , acHidden
lLargeSans = PositionFormPx("|Bordures|").Largeur
lHautSans = PositionFormPx("|Bordures|").Hauteur

À ce stade, nous disposons des éléments pour calculer la dimension de chacune des bordures :

gBordure.Laterale = (lLargeAvec - lLargeSans) / 2
gBordure.Inferieure = gBordure.Laterale
gBordure.Superieure = lHautAvec - lHautSans - gBordure.Inferieure

Nous n'avons plus besoin de notre copie, on la ferme (sans sauver) et on la supprime :

DoCmd.Close acForm, "|Bordures|", acSaveNo
DoCmd.DeleteObject acForm, "|Bordures|"

On est presque au bout : nous avons la dimension des bordures en pixels, il reste à les transformer en twips.

Il suffit de multiplier les données par leur nombre de twips par pixel.

Souvenez-vous, il s'agit du contenu des contrôles ZdtNbreTwipspPixelHor et ZdtNbreTwipspPixelVer de notre formulaire fOuSuisJe.

Nous allons ouvrir, en mode caché, fOusuisJe.
On y puise les coefficients de conversion, on en profite pour les mémoriser dans une variable globale (nous en aurons encore besoin plus tard) et nous refermerons le formulaire :

DoCmd.OpenForm "fOuSuisJe", , , , , acHidden
gPixel2Twip.Horizontal = Forms!fOuSuisJe.ZdtNbreTwipspPixelHor
gPixel2Twip.Vertical = Forms!fOuSuisJe.ZdtNbreTwipspPixelVer
DoCmd.Close acForm, "fOuSuisJe"
gBordure.Laterale = gBordure.Laterale * gPixel2Twip.Horizontal
gBordure.Superieure = gBordure.Superieure * gPixel2Twip.Vertical
gBordure.Inferieure = gBordure.Inferieure * gPixel2Twip.Vertical

Et voilà, nous avons un « truc » pour les marges en twips.

VIII - Les dimensions de l'écran

VIII-A - Une API : SystemParametersInfo

Si, pour vous, l'important est que « ça » fonctionne, même si on ne comprend pas, ce serait sans doute raisonnable de faire appel à une API !

Voilà pourquoi nous donnons ici les ingrédients de la recette, sans expliquer pourquoi c'est bon.

Un peu plus loin dans ce tutoriel, nous expliquerons un moyen plus « acrobatique », avec quelques contorsions qui nous permettront de progresser dans la maîtrise du code.

Vous collez ceci dans un module, par exemple dans le module mAPI.

Public Declare Function SystemParametersInfo Lib "User32" _
Alias "SystemParametersInfoA" (ByVal uAction As Long, _
ByVal uParam As Long, ByRef lpvParam As RECT, _
ByVal fuWinIni As Long) As Long
Public Const SPI_GETWORKAREA = 48

Ensuite, cette fonction par exemple dans mFonctions.

Public Function EcranPx() As Position
Dim lScreenRect As RECT
SystemParametersInfo SPI_GETWORKAREA, 0, lScreenRect, 0
EcranPx.Largeur = lScreenRect.Right - lScreenRect.Left
EcranPx.Hauteur = lScreenRect.Bottom - lScreenRect.Top
End Function

Et pour constater que ça fonctionne, ouvrez la fenêtre d'exécution <Ctrl-key-G>, et saisissez :

? EcranPx().Largeur <Enter> => la largeur de l'écran s'affiche en pixels.

? EcranPx().Hauteur <Enter> => la hauteur de l'écran s'affiche en pixels. Il s'agit de la hauteur disponible. C'est-à-dire la distance entre le bord supérieur de l'écran et la barre des tâches si vous avez choisi de l'afficher en permanence.

VIII-B - Un peu de gymnastique pour le même résultat

On va se servir de la théorie développée dans le tutoriel précédent.

Avec notre formulaire fOusuisJe et une dose d'imagination, nous allons mesurer notre écran.

Pour comprendre la démarche, nous allons un peu torturer une copie de notre formulaire fOuSuisJe.

Suivez pas à pas :

- prenez une copie : clic sur fOuSuisJe dans la fenêtre des objets => surbrillance => <Ctrl-key-C> suivi de <Ctrl-key-V> (équivaut à Copier/Coller) => Access vous invite à donner un nom =>« Martyr » (par exemple !) ;

- modifiez la propriété Style Bordure : « Aucune » ;

- modifiez le code des événements : <Alt-key-F11> pour ouvrir l'éditeur, dans l'explorateur de projets cliquez sur Form_Martyr et modifiez le code pour arriver à ceci :

La flèche bleue indique une instruction ajoutée : le formulaire occupera la totalité de la surface libre, en l'occurrence tout l'écran puisque notre formulaire est une fenêtre indépendante.

Par contre, les flèches vertes montrent des instructions qui ont été « commentées » : elles sont maintenant neutralisées (Access les confondra avec du commentaire). Ceci est une astuce pour garder une trace de la situation initiale quand on met du code au point.

Ces modifications sont en fait : l'ouverture du formulaire à dimension maximale et incorporation dans l'événement « Sur ouverture », du code initialement prévu dans la minuterie (timer) qui a d'ailleurs disparu.

- Sauvegardez « Martyr » et ouvrez-le en mode formulaire.

Le contrôle zdtLargeurTwi affiche la largeur de l'écran et zdtHauteurTwi affiche la hauteur totale (ce qu'on veut, c'est la hauteur disponible).

Cela dépend de la résolution de votre écran, par exemple avec une résolution de 1280 x 1024, vous obtiendrez : 19 200 (twips) de largeur et 15 360 (twips) de hauteur.

Puisque vous avez créé une copie de l'objet fOuSuisJe, lorsque vous fermerez la session, vous aurez ce message :

Nous avons déjà la moitié de ce qu'on cherche : la largeur. Continuons nos basses œuvres sur Martyr :

- ouvrons-le à nouveau en mode création, et cette fois, remplaçons DoCmd.Maximize par DoCmd.Minimize et ouvrons en mode formulaire. Le bas de l'écran affiche ceci :

- en fait la hauteur que nous recherchons, c'est la distance entre le bord supérieur de l'écran et le bord supérieur de Martyr (zdtBasTwi) + la hauteur de Martyr (zdtHauteurTwi). Dommage que l'affichage est tel que ces données ne sont pas visibles ;

- qu'à cela ne tienne, on peut quand même les voir !
Ouvrons la fenêtre d'exécution et demandons d'afficher la somme de ces deux valeurs :
? forms!Martyr.zdtBasTwi + forms!Martyr.zdtHauteurTwi <Enter>.

Les nombres affichés dépendent de la résolution de votre écran. Par exemple avec une résolution de 1280 x 1024, vous obtiendrez : 14 910 (twips).

Et voilà le travail !

À titre d'exercice, comparez ces valeurs à celles obtenues par la voie rapide décrite au paragraphe précédent (exprimés en pixels). Les résultats sont identiques. Cependant, un peu comme de la soupe en sachet comparée à celle préparée avec les légumes du jardin, la seconde semble meilleure, juste parce qu'on l'a cuisinée soi-même.

VIII-C - Une procédure pour automatiser ce processus

Dans mfonctions, nous ajoutons une variable globale gEcran de type Position. Nous pourrons ainsi mémoriser les mesures dans gEcran.Largeur et gEcran.Hauteur.

Avant de poursuivre, nous allons apporter, manuellement, quelques modifications dans le code de notre formulaire fOuSuisJe.

Nous allons donner une portée publique à la procédure Sub Form_Timer(). Il y a Private Sub Form_Timer(), nous modifions en Public Sub Form_Timer(). Ainsi, nous pourrons faire appel à cette procédure depuis l'extérieur du formulaire.

Public Sub Form_Timer()
[...]

Nous allons aussi conditionner la copie dans le presse-papier. Elle ne surviendra que si le formulaire a été ouvert par l'utilisateur.
Pour détecter ce fait, nous utilisons la propriété CodeContextObject, elle permet de déterminer dans quel objet, du code Visual Basic est en cours d'exécution. Dans le cas où le formulaire fOuSuisJe a été ouvert par l'utilisateur, la propriété CodeContextObject.name aura pour valeur : « fOuSuisJe ». Par contre, si l'ouverture a été commandée par un processus inclus dans fPrincipal, nous aurons CodeContextObject.name=fPrincipal.

[...]
If CodeContextObject.Name = Me.Name Then
'Commande dans le presse-papier
Me.zdtParam.SetFocus
'on sélectionne tout le texte
Me.zdtParam.SelStart = 0
Me.zdtParam.SelLength = Len(Me.zdtParam.Text)
'on copie dans le presse-papier
DoCmd.RunCommand acCmdCopy 'équivalent du raccourci CTRL + C
End If
[...]

Par prudence, nous ajouterons une gestion d'erreur. Celle qui surviendrait, si pour une raison ou l'autre, la fonction « copier » n'est pas disponible à l'instant où le programme l'appelle.

GestionErreur:
Select Case Err.Number
Case 2046 'pour une raison ou l'autre la fonction "copier" n'est pas disponible
Resume Next
Case Else
MsgBox "Erreur non prévue dans Form_Timer" & vbLf _
& Err.Number & " : " & Err.Description & "."
End Select

Le code complet de l'événement « Sur minuterie » devient donc :

Public Sub Form_Timer()
On Error GoTo GestionErreur
'La position du coin sup gauche
Me.zdtDroitePix = PositionFormPx(Me.Name).Droite
Me.zdtBasPix = PositionFormPx(Me.Name).Bas
Me.zdtDroiteTwi = Me.zdtDroitePix * Me.ZdtNbreTwipspPixelHor
Me.zdtBasTwi = Me.zdtBasPix * Me.ZdtNbreTwipspPixelVer
'La Largeur et la hauteur
Me.zdtLargeurTwi = Me.WindowWidth
Me.zdtHauteurTwi = Me.WindowHeight
Me.zdtLargeurPix = Me.zdtLargeurTwi / Me.ZdtNbreTwipspPixelHor
Me.zdtHauteurPix = Me.zdtHauteurTwi / Me.ZdtNbreTwipspPixelVer
Me.zdtParam = "DoCmd.MoveSize " & Me.zdtDroiteTwi & " , " & Me.zdtBasTwi & " , " _
& Me.zdtLargeurTwi & " , " & Me.zdtHauteurTwi

' Si exécution à partir d'un autre formulaire, on n'effectue pas
'la copie presse-papiers de la zone de texte
If CodeContextObject.Name = Me.Name Then
'Commande dans le presse-papier
Me.zdtParam.SetFocus
'on sélectionne tout le texte
Me.zdtParam.SelStart = 0
Me.zdtParam.SelLength = Len(Me.zdtParam.Text)
'on copie dans le presse-papier
DoCmd.RunCommand acCmdCopy 'équivalent du raccourci CTRL + C
End If
Exit Sub
GestionErreur:
Select Case Err.Number
Case 2046 'pour une raison ou l'autre la fonction "copier" n'est pas disponible
Resume Next
Case Else
MsgBox "Erreur non prévue dans Form_Timer" & vbLf _
& Err.Number & " : " & Err.Description & "."
End Select
End Sub

Ceci fait, traduisons en code les manœuvres que nous avons faites au § 8. B.

D'abord la largeur de l'écran.

Nous ouvrons fOuSuisJe en mode création, pour supprimer ses bordures.

Ensuite, nous l'ouvrons et maximisons la fenêtre et dans la foulée (pour ne pas devoir attendre une seconde), nous déclenchons le code de la minuterie. C'est pour cela que nous la voulions « publique ».

Nous relevons alors la largeur et la mémorisons dans la variable gEcran.Largeur. Et terminons sans sauvegarder : fOuSuisJe reste donc intact.

Private Sub Ecran()
'La Largeur
DoCmd.OpenForm "fOuSuisJe", acDesign
Forms!fOuSuisJe.BorderStyle = 0
DoCmd.OpenForm "fOuSuisJe", acNormal
DoCmd.Maximize
Forms!fOuSuisJe.Form_Timer
gEcran.Largeur = Forms!fOuSuisJe.zdtLargeurTwi
DoCmd.Close acForm, "fOuSuisJe", acSaveNo
DoCmd.Restore

Et on refait la même opération, pour la hauteur.

DoCmd.OpenForm "fOuSuisJe", acDesign
Forms!fOuSuisJe.BorderStyle = 0
DoCmd.OpenForm "fOuSuisJe", acNormal
DoCmd.Minimize
Forms!fOuSuisJe.Form_Timer
gEcran.Hauteur = Forms!fOuSuisJe.zdtBasTwi + Forms!fOuSuisJe.zdtHauteurTwi
'Refermer sans sauver
DoCmd.Close acForm, "fOuSuisJe", acSaveNo

Avant de terminer, on rétablit la dimension des autres objets ouverts (entre autres : fPrincipal).

DoCmd.Restore
End Sub

IX - Rassemblons les pièces du puzzle

IX-A - Le code du formulaire fPrincipal

Nous allons expliquer le code événement par événement.

Nous ne réexpliquerons pas ici comment on navigue entre l'affichage en mode formulaire, en mode création ou de l'éditeur de code. Cela a été décrit en détail au § 3.B du tutoriel précédent.

IX-A-1 - Sur ouverture (Private Sub Form_Open)

À l'ouverture du formulaire, nous allons déclencher les processus relatifs au calcul des bordures et des dimensions de l'écran.

La première fois que l'on ouvre le formulaire depuis que la base de données est ouverte, la variable globale gBordureDispo a sa valeur initiale : False (faux). Lorsque la procédure CalculBordures aura été exécutée gBordureDispo passera à True (vrai).

C'est la valeur constatée de gBordureDispo qui va nous servir de critère pour déclencher ou non l'appel des procédures CalculBordures et Ecran.

Ainsi, dans une même session, si le formulaire est ouvert plusieurs fois, les calculs ne seront lancés qu'une seule fois.

Remarquez cette instruction Application.Echo False, elle a pour effet de figer l'écran. Les actions qui suivent ne seront pas affichées. On rétablit la situation normale avec Application.Echo False quelques fractions de seconde plus tard. Cela pour éviter, à l'utilisateur, l'effet de flash désagréable.(2)

Private Sub Form_Open(Cancel As Integer)
If gBordureDispo = False Then
Application.Echo False
Call CalculBordures
Call Ecran
Application.Echo True
End If
End Sub

Pour vérifier que ça marche :

passez en mode formulaire ;

dans la fenêtre d'exécution, saisissez : ? gBordureDispo ;

True s'affiche.

IX-A-2 - Sur activation (Private Sub Form_Current)

C'est ici que nous allons faire le nécessaire pour que le contrôle zdtMILLESIME s'affiche en vert dans l'enregistrement actif.

Ici aussi, on va ruser. Nous allons utiliser le format conditionnel pour colorer le contrôle uniquement dans l'enregistrement actif.

Dans fPrincipal ouvert en mode création, cliquez sur le contrôle zdtMILLESIME pour le sélectionner.

Dans la barre des menus, vous cliquez sur Format et ensuite Mise en forme conditionnelle...

Il vient :

Si vous cliquez pour dérouler le choix des conditions, vous ne trouvez pas les mots pour dire : « changer la couleur dans l'enregistrement actif ». Il y a bien « Champ activé », mais ce que nous voulons, c'est changer la couleur de zdtMILLESIME quel que soit le contrôle activé (lui ou un autre, ou aucun) et ce, uniquement sur la ligne qui correspond à l'enregistrement en cours, comme ceci :

Voici une astuce pour dire « dans l'enregistrement actif ».

Dans notre formulaire, nous allons ajouter un contrôle indépendant (pas de source) que nous appelons par exemple zdtActif. Et nous le rendons invisible parce qu'il n'a pas d'intérêt pour l'utilisateur.

À chaque changement d'enregistrement, nous allons systématiquement copier dans ce contrôle, la valeur que nous savons unique, d'un autre contrôle. Par exemple la valeur de zdtDENOMINATION (DENOMINATION étant la clé de tVins, sa valeur est forcément unique).

Avec ce subterfuge le seul cas où on a égalité entre zdtDENOMINATION et zdtActif, c'est dans l'enregistrement actuellement actif !

Dès lors, nous exprimons la condition pour le formatage particulier comme ceci :

Et pour l'événement « Sur activation » nous codons ceci :

Private Sub Form_Current()
Me.zdtActif = Me.zdtDENOMINATION
End Sub

Vérifions que ça marche :

fermez fPrincipal en le sauvegardant ;

ouvrez-le à nouveau et naviguez entre les enregistrements pour constater le comportement du contrôle qui affiche le millésime, en vert dans le seul enregistrement actif.

IX-A-3 - Double-clic sur zdtMILLESIME (Private Sub zdtMILLESIME_DblClick)

Il s'agit de commander l'ouverture du formulaire fDetail.

Juste une précaution : on referme d'abord fDetail au cas où il aurait déjà été précédemment ouvert. Ceci pour provoquer le déclenchement de son événement sur ouverture que nous verrons au § suivant.

Remarquez aussi la gestion d'erreur. Vous comprendrez mieux lorsque nous passerons en revue le code de fDetail.

Private Sub zdtMILLESIME_DblClick(Cancel As Integer)
On Error GoTo GestionErreur
'Fermer le formulaire fDetail s'il est encore ouvert (appel précédent)
If CurrentProject.AllForms("fDetail").IsLoaded Then DoCmd.Close acForm, "fDetail"
'Ouvrir le formulaire fDetail
DoCmd.OpenForm "fDetail"
Select Case Err.Number
Case 2501
Resume Next
Case Else
MsgBox "Erreur inattendue dans ""Sub zdtMILLESIME_DblClick""" _
& vbLf & Err.Number & " : " & Err.Description & "."
End Select
End Sub

IX-A-4 - Sur fermeture (Private Sub Form_Close)

Si on ferme fPrincipal, fDetail n'a plus de raison de rester ouvert. On le referme si c'était le cas.

Private Sub Form_Close()
'Fermer le formulaire fDetail s'il est encore ouvert (appel précédent)
If CurrentProject.AllForms("fDetail").IsLoaded Then DoCmd.Close acForm, "fDetail"
End Sub

IX-B - Le code du formulaire fDetail

IX-B-1 - Sur ouverture (Private Sub Form_Open)

Il s'agit « simplement » de calculer les paramètres de la méthode MoveSize de l'objet DoCmd.

Nous définissons une variable pour chacun de ces paramètres :

Private Sub Form_Open(Cancel As Integer)
Dim lDroite As Long
Dim lBas As Long
Dim lLargeur As Long
Dim lHauteur As Long

Nous allons nous assurer que fDetail a bien la propriété Fen indépendante = oui. C'est à cette condition que les paramètres de doCmd.MoveSize s'expriment par rapport au bord supérieur gauche de l'écran.

Si ce n'est pas le cas, on affiche un message à l'utilisateur et on interrompt le processus d'ouverture de fDetail.

If Not Me.PopUp Then
MsgBox "Le formulaire à positionner doit être en fenêtre indépendante" & _
vbCrLf & "(onglet Autre dans les propriétés du formulaire)", vbInformation
Cancel = True
Exit Sub
End If

Si on fixe la valeur de Cancel à True, une erreur n° 2501 sera levée dans l'exécution du code qui avait demandé l'ouverture. Voir Sub zdtMILLESIME_DblClick de fPrincipal.

Pour la largeur et la hauteur, nous fixons les valeurs arbitraires de notre choix :

lLargeur = 4245
lHauteur = 3495

Dans un premier temps nous calculons les paramètres Droite et Bas, comme s'il y avait la place disponible à l'écran. On traduit donc en code les distances que nous avons montrées aux § 5 A et 5 B en utilisant la syntaxe montrée au § 6 :

'Calcul du paramètre Droite
lDroite = PositionFormPx("fPrincipal").Droite * gPixel2Twip.Horizontal _
+ gBordure.Laterale _
+ Forms!fPrincipal.CurrentSectionLeft _
+ Forms!fPrincipal.zdtMILLESIME.Left
'Calcul du paramètre Bas
lBas = PositionFormPx("fPrincipal").Bas * gPixel2Twip.Vertical _
+ gBordure.Superieure _
+ Forms!fPrincipal.CurrentSectionTop _
+ Forms!fPrincipal.zdtMILLESIME.Top _
+ Forms!fPrincipal.zdtMILLESIME.Height

Nous vérifions ensuite qu'il y a place à l'écran et nous rectifions si nécessaire :

'c'est trop bas si détail est en dehors de l'écran
If lBas + lHauteur > gEcran.Hauteur Then
lBas = lBas - Forms!fPrincipal.zdtMILLESIME.Height - lHauteur
End If
'c'est trop à droite si détail est en dehors de l'écran
If lDroite + lLargeur > gEcran.Largeur Then
lDroite = lDroite + Forms!fPrincipal.zdtMILLESIME.Width - lLargeur
End If

Et on ouvre le formulaire à l'endroit précis :

DoCmd.MoveSize lDroite, lBas, lLargeur, lHauteur
End Sub

Pensez à compiler votre code.
Dans le menu de l'éditeur de code, cliquez sur Débogage et ensuite sur Compiler.

X - La preuve que ça fonctionne quelle que soit la section qui héberge le contrôle

Ouvrez le formulaire fPrincipal en mode création, sélectionnez le contrôle zdtMILLESIME et faites-le glisser dans la section Entête du formulaire. Passez en mode formulaire et essayez.

De même si le contrôle se trouve dans la section Pied du formulaire.

XI - Conclusion

On y est, fdetail s'affiche en entier à proximité du millésime cliqué, quelle que soit la position de fPrincipal.

C'était un des objectifs, mais peut-être pas le principal. Mon but était en fait de vous mettre le pied à l'étrier et vous montrer qu'il n'est pas tellement difficile d'apprendre à programmer, cela vous ouvre de nouvelles perspectives pour tirer meilleur parti de votre logiciel Access.

XII - La base de données

La base de données qui a servi à construire ce tutoriel se trouve ici.

XIII - Remerciements

À Arkham46 qui m'a généreusement offert l'idée d'écrire ce tutoriel et qui m'a guidé tout au long de la conception.

À l'équipe Office pour son soutien technique et logistique, en particulier Domi2.

À Djibril et _MAX_ qui m'ont aidé à respecter la langue de Molière. Pardon à ce dernier, si des fautes ont échappé à notre vigilance et merci à vous si vous me les dénoncez.

(1)Pour ceux qui aiment la précision : j'ai changé plusieurs fois de voiture durant ce laps de temps.

(2)Désolé, la suspension temporaire du flash ne fonctionne pas pour certains appareils placés le long des routes.