I. Introduction▲
Cet article répond à deux questions posées sur le forum : comment dessiner dans un formulaire Access ou comment modifier une photo.
MS Access n'est pas du tout destiné à révéler les futurs artistes qui sommeillent en vous, tout simplement parce qu'aucun de ses objets n'est approprié à la création graphique.
Pour réaliser rapidement une application sommaire de dessin, je vous recommande fortement d'utiliser Visual Basic (et VB.net aussi) avec lesquels ils vous sera possible de réaliser en très peu de lignes de codes ce que nous parvenons à faire ici à grand peine.
Un autre choix pertinent pourrait être de réaliser un OCX dans VB, pour pouvoir l'utiliser par la suite dans MS Access.
Mais, nous sommes ici pour dire que rien ne fait peur à notre SGBD favori, alors tentons l'impossible et allons exprimer notre sensibilité picturale sur nos formulaires si familiers.
II. Rendu final de l'application▲
Qu'allons-nous réaliser ici ?
Une application de type paint préhistorique à l'intérieur d'un formulaire, regardez ce que cela peut donner ...
une très belle oeuvre d'un artiste ayant souhaité garder l'anonymat.
III. Principes▲
Comment dessiner ?
Voici les principes fondateurs de l'application :
. Capter les déplacements de la souris sur l'application
. Détecter l'état des boutons de la souris
. Utiliser des API pour dessiner des pixels à l'endroit où se trouve la souris
. Utiliser une API pour afficher la boîte de dialogue de choix de couleur
. Utiliser une API pour sauvegarder notre dessin.
IV. Construction du formulaire▲
IV-A. La conception du formulaire▲
Nous dessinons notre formulaire avec :
. deux contrôles ComboBox (Taille et forme du pinceau)
. deux contrôles Label (position de la souris et état des boutons)
. deux contrôles Box (couleur clic gauche et couleur clic droit)
. deux contrôles Button (Enregistrer et Enregistrer sous)
. un contrôle Image (zone de dessin).
Toute action de clic droit sur un formulaire active l'apparition d'un menu contextuel, nous allons donc désactiver cette fonction.
Il suffit de mettre "Non" sur la propriété Menu Contextuel du formulaire.
Notre formulaire est maintenant prêt, mais il reste à coder les événements souris et bien sûr le tracé de points.
IV-B. Mission impossible ?▲
Il est, dans la pratique, très compliqué de dessiner dans un formulaire Access.
Access ne dispose pas de fonctions natives pour dessiner, hormis Pset() mais cette fonction n'est disponible que dans les états.
Pset() est la fonction idéale pour tracer des lignes verticales dans les états, problème souvent abordé sur le forum.
Sans fonctions natives, nous allons joyeusement utiliser l'extensibilité de VB / VBA et appeler des fonctions de l'interface de programmation (Application Programming Interface) qu'on appelle affectueusement API.
L'API que nous allons massivement utiliser est GDI (Graphic Device Interface), elle est dédiée à l'affichage.
Là où VB et VBA divergent, et ce, avant même l'avènement de VB.net c'est dans l'accessibilité des formulaires et des contrôles.
Dans Visual Basic, les forms et les contrôles sont accessibles par une poignée (handle) : un entier (de type long en 32 bits) qui permet à windows d'accéder directement à cet objet.
Cet entier est renvoyé par la propriété .hWnd
Ce n'est malheureusement pas le cas pour Access, du moins pour les versions XP et inférieures. La propriété .hWnd n'existe que pour les formulaires mais pas pour les contrôles.
Nous pourrons donc dans le meilleur des cas, dessiner dans le formulaire et donc pas seulement dans une zone d'image.
Mais il y a pire :
Access ne dispose pas de propriété qui renvoie le handle du Device Context (Contexte de périphérique).
Pour dessiner en utilisant les API, la fonction SetPixelV a besoin d'un Device Context, et là encore VB dispose de cette intéressante propriété .hDC
Il va falloir, là aussi, utiliser des API pour déterminer le handle du device contexte du formulaire puis l'ouvrir à nos fonctions de dessins.
Nous allons utiliser la fonction GetDC() de l'API user32.dll.
IV-C. Mais comment ça marche ?▲
Bon résumons, les API de GDI ne sont pas faites que pour dessiner sur Access, c'est pourquoi il faut respecter un certain principe de fonctionnement propre à Microsoft Windows.
Le diagramme ci-dessous permet de visualiser sommairement les différentes couches nécessaires au tracé d'un pixel sous Windows.
Il faut invoquer toutes les couches "Windows" pour parvenir à tracer des points sur l'écran.
Dans notre cas :
. Trouver le handle du formulaire
. Trouver le device context du formulaire
. Créer un pinceau pour dessiner d'une couleur avec un épaisseur définies
. Créer une brosse pour remplir des formes d'une couleur définie.
. Appeler une fonction de traçage de GDI.
V. Nos amis les API▲
L'utilisation d'une API se fait en deux étapes :
. déclaration pour inclure au projet VBA une fonction qui va pointer sur la DLL
. appel dans le code pour l'utilisation proprement dite.
Pour une information plus complète encore sur les API et VB, n'hésitez pas à lire l'excellent article de Bidou : Les API en Visual Basic
V-A. Comment déclarer une API ?▲
Attardons-nous un peu sur la déclaration d'une API qui sert à déterminer le Contexte d'Affichage (Device Context)
'''""""""""""""""""""""""""""""""""""""""""""""'''
'''
''' API user32.dll
'''
'''""""""""""""""""""""""""""""""""""""""""""""'''
' retourne le handle (poignée) d'un contexte d'affichage ou device context
Private Declare Function apiGetDC Lib "user32" _
Alias "GetDC" _
(ByVal hwnd As Long) _
As Long
| Mot clés | Arguments | Commentaires |
|---|---|---|
| Declare Function | apiGetDC | Ce nom est libre, je préfixe api pour le retrouver facilement dans le code. |
| Lib | "user32" | Le nom de la bibliothèque, il est inutile de rajouter .dll |
| Alias | "GetDC" | Le vrai nom de la fonction incluse dans la DLL. Attention aux majuscules : les dll sont écrites en C / C++ ou autre et ne tolèrent pas d'approximation sur la casse. |
| ( | ByVal hwnd As Long | Arguments de la fonction, ici le Handle d'une "Window" |
| ) | As Long | Type de sortie |
V-B. L'utilisation dans le code▲
hDCDest = apiGetDC(CtlhWnd(Me.imgPaint))
Nous constatons que la fonction est appelée exactement comme n'importe quelle fonction de VBA.
Ici, nous imbriquons deux fonctions : CtlhWnd() et apiGetDC()
Le résultat est d'obtenir le Handle du Device Context de notre formulaire.
V-C. Comment trouver la bonne API ?▲
A la question : "Vous êtes trop forts, comment faites-vous pour connaître par coeur toutes les API ?"
Je suis tenté de répondre par l'affirmative, mais ce serait bien trop exagéré.
En effet, nul besoin de connaître sur le bout du clavier toutes les API Windows, il faut simplement disposer des bons outils de recherche.
Je vous conseille de faire vos recherches sur l'Internet en plus de la visionneuse API qui est fournie avec Visual Basic ou sur le réseau AllAPI.
VI. Dessinons !▲
VI-A. Le code▲
Option Compare Database
Option Explicit
'''""""""""""""""""""""""""""""""""""""""""""""'''
'''
''' définition des types utilisés par les API
'''
'''""""""""""""""""""""""""""""""""""""""""""""'''
Private Type apiPOINT
x As Long
y As Long
End Type
Private Type apiRECT
left As Long
top As Long
right As Long
bottom As Long
End Type
'''""""""""""""""""""""""""""""""""""""""""""""'''
'''
''' API user32.dll
'''
'''""""""""""""""""""""""""""""""""""""""""""""'''
' retourne le handle (poignée) d'un contexte d'affichage ou device context
Private Declare Function apiGetDC Lib "user32" _
Alias "GetDC" _
(ByVal hWnd As Long) _
As Long
' libère un contexte d'affichage
Private Declare Function apiReleaseDC Lib "user32" _
Alias "ReleaseDC" _
(ByVal hWnd As Long, _
ByVal hdc As Long) _
As Long
' renvoie les dimensions d'une fenêtre
Private Declare Function apiGetWindowRect Lib "user32" _
Alias "GetWindowRect" _
(ByVal hWnd As Long, _
lpRect As apiRECT) _
As Long
' renvoie la position de la souris
Private Declare Function apiSetCursorPos Lib "user32" Alias "SetCursorPos" ( _
ByVal x As Long, ByVal y As Long) As Long
' fixe la position de la souris
Private Declare Function apiGetCursorPos Lib "user32" Alias "GetCursorPos" ( _
lpPoint As apiPOINT) As Long
'''""""""""""""""""""""""""""""""""""""""""""""'''
'''
''' API gdi32.dll
'''
'''""""""""""""""""""""""""""""""""""""""""""""'''
' sélectionne un pixel qui aura le statut de "current"
Private Declare Function apiMoveToEx Lib "gdi32.dll" Alias "MoveToEx" ( _
ByVal hdc As Long, ByVal x As Long, ByVal y As Long, _
lpPoint As apiPOINT) As Long
' dessine une ligne jusqu'au pixel désigné X Y
Private Declare Function apiLineTo Lib "gdi32" Alias "LineTo" ( _
ByVal hdc As Long, ByVal x As Long, ByVal y As Long) As Long
' dessine une ellipse sur un device context
Private Declare Function apiEllipse Lib "gdi32" Alias "Ellipse" ( _
ByVal hdc As Long, ByVal X1 As Long, _
ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
' dessine un rectange sur un device context
Private Declare Function apiRectangle Lib "gdi32" Alias "Rectangle" ( _
ByVal hdc As Long, ByVal X1 As Long, _
ByVal Y1 As Long, ByVal X2 As Long, _
ByVal Y2 As Long) As Long
' crée un crayon, permet de mettre une couleurs aux ellipses
Private Declare Function apiCreatePen Lib "gdi32" Alias "CreatePen" ( _
ByVal nPenStyle As Long, ByVal nWidth As Long, _
ByVal crColor As Long) As Long
' crée une brosse afin de remplir une forme
Private Declare Function apiCreateSolidBrush Lib "gdi32" Alias "CreateSolidBrush" ( _
ByVal crColor As Long) As Long
' sélectionne un objet Windows, dans notre cas un(e) pinceau(brosse) associé à un device context
Private Declare Function apiSelectObject Lib "gdi32" Alias "SelectObject" ( _
ByVal hdc As Long, ByVal hObject As Long) As Long
' efface un Pen ou autre objet afin de libérer la mémoire
Private Declare Function apiDeleteObject Lib "gdi32" Alias "DeleteObject" ( _
ByVal hObject As Long) As Long
'''""""""""""""""""""""""""""""""""""""""""""""'''
'''
''' Déclarations nécessaires au formulaire
'''
'''""""""""""""""""""""""""""""""""""""""""""""'''
' poignée (handle) du device context sur lequel nous allons dessiner
Private hDCDest As Long
' poignées (handles) du pinceau et de la brosse
Private hPen As Long
Private hhPen As Long
Private hBrush As Long
Private hhBrush As Long
' options de dessin
' booléen pour connaître l'état du bouton de souris (relaché / appuyé)
Private blnButtonHold As Boolean
Private intThickIndex As Integer
Private intShapeIndex As Integer
Private lngCurrentLeftColor As Long
Private lngCurrentRightColor As Long
Private PreviousPoint As apiPOINT
' dimensions en pixels du cadre à l'intérieur duquel nous dessinons
Private xMax As Long
Private xMin As Long
Private yMax As Long
Private yMin As Long
' chemin du fichier de sauvegarde
Private strCurrentFile As String
Private Sub boxLeftCol_Click()
' affichage du dialogue de choix de couleur
Dim lngReturn As Boolean
lngReturn = aDialogColor(Me.boxLeftCol.Properties("BackColor"))
lngCurrentLeftColor = Me.boxLeftCol.BackColor
End Sub
Private Sub boxRightCol_Click()
' affichage du dialogue de choix de couleur
Dim lngReturn As Boolean
lngReturn = aDialogColor(Me.boxRightCol.Properties("BackColor"))
lngCurrentRightColor = Me.boxRightCol.BackColor
End Sub
Private Sub cmbShape_BeforeUpdate(Cancel As Integer)
' gestion de la combo de forme
intShapeIndex = Me.cmbShape
End Sub
Private Sub cmbThick_BeforeUpdate(Cancel As Integer)
' gestion de la combo d'épaisseur
intThickIndex = Me.cmbThick
End Sub
Private Sub cmdNew_Click()
' bouton de création de nouveau document
strCurrentFile = vbNullString
Me.imgPaint.Picture = vbNullString
Me.Caption = "[Nouveau document]"
End Sub
Private Sub cmdOpen_Click()
' bouton de chargement d'une image
strCurrentFile = OuvrirUnFichier(Me.hWnd, "Ouvrir ...", 1, "", "", CurrentProject.Path)
If Len(strCurrentFile) > 0 Then
Me.imgPaint.Picture = strCurrentFile
Me.Caption = "[" & strCurrentFile & "]"
End If
End Sub
Private Sub cmdSave_Click()
' bouton de sauvegarde d'une image
If Len(strCurrentFile) > 0 Then
ShiftImprimEcran strCurrentFile, _
xMin, _
yMin, _
xMax, _
yMax
Me.Caption = "[" & strCurrentFile & "]"
Me.imgPaint.Picture = strCurrentFile
Else
cmdSaveAs_Click
End If
End Sub
Private Sub cmdSaveAs_Click()
' bouton enregistrer sous d'une image
' enregistrement d'un fichier temporaire au cas où la boîte
' de dialogue se superpose à notre dessin
ShiftImprimEcran CurrentProject.Path & "\tmp.wip", _
xMin, _
yMin, _
xMax, _
yMax
' affichage de boîte de dialogue de sauvegarde
strCurrentFile = EnregistrerUnFichier(Me.hWnd, "Enregistrer l'image sous", "*.bmp", CurrentProject.Path)
If Len(strCurrentFile) > 0 Then
If right(strCurrentFile, 4) <> ".bmp" Then
strCurrentFile = strCurrentFile & ".bmp"
End If
' copier coller du fichier temporaire en fichier sauvegardé
CopierColler CurrentProject.Path & "\tmp.wip", strCurrentFile, 0
Me.imgPaint.Picture = strCurrentFile
Me.Caption = "[" & strCurrentFile & "]"
Else
' l'utilisateur a annulé la sauvegarde, nous rechargeons le fichier temporaire
Me.imgPaint.Picture = CurrentProject.Path & "\tmp.wip"
End If
End Sub
Private Sub Form_Activate()
' gestion des objets nécessaires au dessin via API
' obtention des dimensions du formulaire
' calcul de la taille de l'image
Dim rectImg As apiRECT
apiGetWindowRect Me.hWnd, rectImg
' les valeurs sont les marges dues au bords du formulaire
xMin = rectImg.left + 6
yMin = rectImg.top + 25
xMax = Me.imgPaint.Width / 15 + xMin - 3
yMax = Me.imgPaint.Height / 15 + yMin - 3
intThickIndex = Me.cmbThick
intShapeIndex = Me.cmbShape
lngCurrentLeftColor = Me.boxLeftCol.BackColor
lngCurrentRightColor = Me.boxRightCol.BackColor
' utilisation de l'API GetDC() pour récupérer le handle du device context
hDCDest = apiGetDC(CtlhWnd(Me.imgPaint))
Me.Caption = "[Nouveau document]"
End Sub
Private Sub Form_Deactivate()
' libération du device context
apiReleaseDC CtlhWnd(Me.imgPaint), hDCDest
End Sub
Private Sub Form_Load()
Me.lblMouseStatus.Caption = "<none>"
blnButtonHold = False
End Sub
Private Sub imgPaint_MouseDown(Button As Integer, Shift As Integer, x As Single, y As Single)
Dim tampon As apiPOINT
apiGetCursorPos tampon
If Button = acLeftButton Or Button = acRightButton Then
blnButtonHold = True
Me.lblMouseStatus.Caption = IIf(Button = acLeftButton, "<left>", "<right>")
Me.lblMousePos.Caption = Format(x / 15, "000") & " x " & Format(y / 15, "000")
Select Case intShapeIndex
Case 1 To 4
If intShapeIndex <= 2 Then
hBrush = apiCreateSolidBrush(IIf(Button = acLeftButton, lngCurrentLeftColor, lngCurrentRightColor))
Else
hBrush = apiCreateSolidBrush(IIf(Button = acLeftButton, lngCurrentRightColor, lngCurrentLeftColor))
End If
hPen = apiCreatePen(0, 1, IIf(Button = acLeftButton, lngCurrentLeftColor, lngCurrentRightColor))
hhPen = apiSelectObject(hDCDest, hPen)
hhBrush = apiSelectObject(hDCDest, hBrush)
If intShapeIndex = 1 Or intShapeIndex = 3 Then
apiRectangle hDCDest, tampon.x - intThickIndex / 2, tampon.y - intThickIndex / 2, _
tampon.x + intThickIndex / 2, tampon.y + intThickIndex / 2
Else
apiEllipse hDCDest, tampon.x - intThickIndex / 2, tampon.y - intThickIndex / 2, _
tampon.x + intThickIndex / 2, tampon.y + intThickIndex / 2
End If
apiDeleteObject hhBrush
apiDeleteObject hBrush
apiDeleteObject hhPen
apiDeleteObject hPen
Case 5
hPen = apiCreatePen(0, 1, IIf(Button = acLeftButton, lngCurrentLeftColor, lngCurrentRightColor))
hBrush = apiCreateSolidBrush(IIf(Button = acLeftButton, lngCurrentLeftColor, lngCurrentRightColor))
hhPen = apiSelectObject(hDCDest, hPen)
hhBrush = apiSelectObject(hDCDest, hBrush)
apiEllipse hDCDest, tampon.x - (intThickIndex / 2), tampon.y - (intThickIndex / 2), _
tampon.x + (intThickIndex / 2), tampon.y + (intThickIndex / 2)
apiDeleteObject hhBrush
apiDeleteObject hBrush
apiDeleteObject hhPen
apiDeleteObject hPen
PreviousPoint.x = tampon.x
PreviousPoint.y = tampon.y
End Select
End If
End Sub
Private Sub imgPaint_MouseMove(Button As Integer, Shift As Integer, x As Single, y As Single)
Dim tampon As apiPOINT
If blnButtonHold Then
' bornage de la position de la souris
apiGetCursorPos tampon
apiSetCursorPos Max(Min(tampon.x + Fix(intThickIndex / 2), xMax) - Fix(intThickIndex / 2), xMin + Fix(intThickIndex / 2)), _
Max(Min(tampon.y + Fix(intThickIndex / 2), yMax) - Fix(intThickIndex / 2), yMin + Fix(intThickIndex / 2))
apiGetCursorPos tampon
Select Case intShapeIndex
Case 1 To 4
If intShapeIndex <= 2 Then
hBrush = apiCreateSolidBrush(IIf(Button = acLeftButton, lngCurrentLeftColor, lngCurrentRightColor))
Else
hBrush = apiCreateSolidBrush(IIf(Button = acLeftButton, lngCurrentRightColor, lngCurrentLeftColor))
End If
hPen = apiCreatePen(0, 1, IIf(Button = acLeftButton, lngCurrentLeftColor, lngCurrentRightColor))
hhPen = apiSelectObject(hDCDest, hPen)
hhBrush = apiSelectObject(hDCDest, hBrush)
If intShapeIndex = 1 Or intShapeIndex = 3 Then
apiRectangle hDCDest, tampon.x - intThickIndex / 2, tampon.y - intThickIndex / 2, _
tampon.x + intThickIndex / 2, tampon.y + intThickIndex / 2
Else
apiEllipse hDCDest, tampon.x - intThickIndex / 2, tampon.y - intThickIndex / 2, _
tampon.x + intThickIndex / 2, tampon.y + intThickIndex / 2
End If
apiDeleteObject hhBrush
apiDeleteObject hBrush
apiDeleteObject hhPen
apiDeleteObject hPen
Case 5
If PreviousPoint.x <> 0 And PreviousPoint.y <> 0 Then
hPen = apiCreatePen(0, intThickIndex, IIf(Button = acLeftButton, lngCurrentLeftColor, lngCurrentRightColor))
hhPen = apiSelectObject(hDCDest, hPen)
apiMoveToEx hDCDest, PreviousPoint.x, PreviousPoint.y, PreviousPoint
apiLineTo hDCDest, tampon.x, tampon.y
apiDeleteObject hhPen
apiDeleteObject hPen
End If
PreviousPoint.x = tampon.x
PreviousPoint.y = tampon.y
End Select
Else
Me.lblMousePos.ForeColor = vbBlack
PreviousPoint.x = 0
PreviousPoint.y = 0
End If
Me.lblMousePos.Caption = Format(x / 15, "000") & " x " & Format(y / 15, "000")
End Sub
Private Sub imgPaint_MouseUp(Button As Integer, Shift As Integer, x As Single, y As Single)
Me.lblMouseStatus.Caption = "<none>"
PreviousPoint.x = 0
PreviousPoint.y = 0
blnButtonHold = False
End Sub
Private Function Max(ByVal v1 As Long, ByVal v2 As Long) As Long
If v1 >= v2 Then Max = v1 Else Max = v2
End Function
Private Function Min(ByVal v1 As Long, ByVal v2 As Long) As Long
If v1 < v2 Then Min = v1 Else Min = v2
End Function
Nous avons défini plusieurs formes dans la combo :

Une fois le Handle du Device Context récupéré nous pouvons tracer plusieurs formes
VI-B. Les objets graphiques▲
Nous utilisons l'API : Rectangle.
Pour le tracé de la forme, nous devons créer un pinceau (Pen) grâce à CreatePen.
Si nous souhaitons un carré plein nous devons créer une brosse (Brush) grâce à CreateBrush.
La gestion d'un objet graphique comprend 3 étapes :
. Création : API CreatePen
. Sélection : API SelectObject avec un handle de Device Context
. Destructions : API DeleteObject du handle de la sélection, et du handle de l'objet
' Créations
hPen = apiCreatePen(0, 1, IIf(Button = acLeftButton, lngCurrentLeftColor, lngCurrentRightColor))
hBrush = apiCreateSolidBrush(IIf(Button = acLeftButton, lngCurrentLeftColor, lngCurrentRightColor))
' Sélections
hhPen = apiSelectObject(hDCDest, hPen)
hhBrush = apiSelectObject(hDCDest, hBrush)
' ici nous mettons la fonction Rectangle ou Ellipse
' Destructions
apiDeleteObject hhBrush
apiDeleteObject hBrush
apiDeleteObject hhPen
apiDeleteObject hPenImportant : Penser à libérer les ressources
Si vous oubliez de détruire les objets vous allez rapidement saturer la mémoire d'affichage.
Il est toujours de la responsabilité du programmeur de prévoir la libération de la mémoire.
VI-C. Fonctionnements des figures▲
VI-C-1. Carré Plein ou Vide▲
Nous utilisons l'API Rectangle() dont les arguments sont :
. le handle du device context
. les coordonnées du rectangle (gauche, haut, droite, bas)
La couleur du contour est définie par la couleur du Pinceau, la couleur du remplissage est définie par la couleur de la Brosse.
VI-C-2. Disque ou Cercle▲
Nous utilisons l'API Ellipse() dont les arguments sont :
. le handle du device context
. les coordonnées du rectangle (gauche, haut, droite, bas) qui contiennent l'ellipse tangente
La couleur du contour est définie par la couleur du Pinceau, la couleur du remplissage est définie par la couleur de la Brosse.
VI-C-3. La ligne brisée▲
Nous allons utiliser la fonction API LineTo() dont les arguments sont :
. le handle du device context
. les coordonnées du point vers lequel nous traçons la droite
Attention ! Le tracé se fait depuis le dernier point dit "courant", mais quel est-il ?
Dans la pratique c'est le dernier point sur lequel l'API GDI a travaillé, mais pour ne rien laisser au hasard nous allons nous-même le déterminer.
Nous le fixons grâce à une autre API MoveToEx() dont les arguments sont :
. le handle du device context
. les coordonnées du point courant.
VI-D. Gestion de la souris▲
Le dessin au clavier reste assez contre-intuitif voire peu ergonomique, c'est pourquoi nous allons gérer les événements souris afin de déclencher les actions de dessin.
| événement souris | actions |
|---|---|
| bouton enfoncé | Tracé de pixels aux coordonnées courantes Stockage du bouton enfoncé : acLeftButton ou acRightButton Attributions des coordonnées courantes au PreviousPoint. |
| bouton relaché | Vidage du PreviousPoint Vidage du bouton enfoncé |
| souris déplacée | Tracé de pixels aux coordonnées courantes Tracé d'une ligne depuis le point actuel jusqu'au point précédent (cas de la ligne brisée) Attributions des coordonnées courantes au PreviousPoint. |
VII. Sauvegardons▲
Nouvelle déconvenue : là aussi contrairement à VB, notre dessin en API n'est absolument pas stocké, nous avons dessiné sur l'écran mais pas à l'intérieur du contrôle image.
En cas de permutation d'application vous remarquerez que vous aurez tout perdu.
Le contrôle image que nous utilisons ne sait pas enregistrer sur le disque directement son contenu.
Pour effectuer une sauvegarde nous allons devoir faire :
. une copie d'écran dans le presse-papier.
. une écriture d'un fichier BMP à partir du presse-papier mais pour une zone délimitée précise.
Option Compare Database
Option Explicit
'''""""""""""""""""""""""""""""""""""""""""""""""""""'''
'
' API kernel32.dll
'
'''""""""""""""""""""""""""""""""""""""""""""""""""""'''
' copie de fichier
Private Declare Function apiCopyFile Lib "kernel32" Alias "CopyFileA" ( _
ByVal lpExistingFileName As String, ByVal lpNewFileName As String, _
ByVal bFailIfExists As Long) As Long
' suppression de fichier
Private Declare Function apiDeleteFile Lib "kernel32" Alias "DeleteFileA" ( _
ByVal lpFileName As String) As Long
'''""""""""""""""""""""""""""""""""""""""""""""""""""'''
'
' API gdi32.dll
'
'''""""""""""""""""""""""""""""""""""""""""""""""""""'''
' sélection d'un objet Windows
Private Declare Function apiSelectObject Lib "gdi32" Alias "SelectObject" ( _
ByVal hdc As Long, ByVal hObject As Long) As Long
' création d'un device context
Private Declare Function apiCreateCompatibleDC Lib "gdi32" Alias "CreateCompatibleDC" ( _
ByVal hdc As Long) As Long
Private Declare Function apiBitBlt Lib "gdi32.dll" Alias "BitBlt" ( _
ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, _
ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, _
ByVal XSrc As Long, ByVal YSrc As Long, ByVal dwRop As Long) As Long
Private Declare Function apiGetDIBits Lib "gdi32" Alias "GetDIBits" ( _
ByVal aHDC As Long, ByVal hBitmap As Long, _
ByVal nStartScan As Long, ByVal nNumScans As Long, _
lpBits As Any, lpBI As apiBITMAPINFO, ByVal wUsage As Long) As Long
Private Declare Function apiCreateDIBSection Lib "gdi32" Alias "CreateDIBSection" ( _
ByVal hdc As Long, pBitmapInfo As apiBITMAPINFO, ByVal un As Long, _
ByVal lplpVoid As Long, ByVal Handle As Long, ByVal dw As Long) As Long
' suppression d'un device context
Private Declare Function apiDeleteDC Lib "gdi32" Alias "DeleteDC" ( _
ByVal hdc As Long) As Long
' suppression d'un objet windows
Private Declare Function apiDeleteObject Lib "gdi32" Alias "DeleteObject" ( _
ByVal hObject As Long) As Long
'''""""""""""""""""""""""""""""""""""""""""""""""""""'''
'
' API user32.dll
'
'''""""""""""""""""""""""""""""""""""""""""""""""""""'''
' récupération du handle du Desktop
Private Declare Function apiGetDesktopWindow Lib "user32.dll" Alias "GetDesktopWindow" () As Long
' récupération du device context d'après un handle de window
Private Declare Function apiGetDC Lib "user32.dll" Alias "GetDC" ( _
ByVal hwnd As Long) As Long
Private Type apiBITMAPINFO
biSize As Long
biWidth As Long
biHeight As Long
biPlanes As Integer
biBitCount As Integer
biCompression As Long
biSizeImage As Long
biXPelsPerMeter As Long
biYPelsPerMeter As Long
biRUsed As Long
biRImportant As Long
End Type
Private Type apiBITMAPFILEHEADER
bfType As Integer
bfSize As Long
bfReserved1 As Integer
bfReserved2 As Integer
bfOffBits As Long
End Type
Private Const GHND = &H42
Private Const MAXSIZE = 4096
Private Const SRCCOPY = &HCC0020
Private Const DIB_RGB_COLORS = 0&
Private Const BI_RGB = 0&
' '''"""""""""""""""""""""""""""""""""""""""""""""""""'''
'
' Sur une section précise
' de l'écran
'
' '''"""""""""""""""""""""""""""""""""""""""""""""""""'''
Function ShiftImprimEcran(strNomDuFichier As String, X1 As Long, Y1 As Long, X2 As Long, Y2 As Long)
On Error GoTo Finally
Dim lngLargeur As Long, lngHauteur As Long
Dim lngHdc As Long
Dim lngHBmp As Long
Dim bmiBitmapInfo As apiBITMAPINFO
Dim bmfBitmapFileHeader As apiBITMAPFILEHEADER
Dim lngFnum As Integer
Dim pixels() As Byte
Dim bolOuvert As Boolean
lngHdc = apiCreateCompatibleDC(0)
If lngHdc = 0 Then
GoTo Finally
End If
'Récupère les dimensions de l'écran
lngHauteur = Y2 - Y1
lngLargeur = X2 - X1
'Crée un bitmap vierge
With bmiBitmapInfo
.biBitCount = 32
.biCompression = BI_RGB
.biPlanes = 1
.biSize = Len(bmiBitmapInfo)
.biHeight = lngHauteur
.biWidth = lngLargeur
.biSizeImage = ((((.biWidth * .biBitCount) + 31) \ 32) * 4 - _
(((.biWidth * .biBitCount) + 7) \ 8)) * .biHeight
End With
lngHBmp = apiCreateDIBSection(lngHdc, bmiBitmapInfo, DIB_RGB_COLORS, _
ByVal 0&, ByVal 0&, ByVal 0&)
If lngHBmp = 0 Then
GoTo Finally
End If
If apiSelectObject(lngHdc, lngHBmp) = 0 Then
GoTo Finally
End If
'Copie le contenu de l'ecran
If apiBitBlt(lngHdc, 0&, 0&, lngLargeur, lngHauteur, _
apiGetDC(apiGetDesktopWindow()), X1&, Y1&, SRCCOPY) = 0 Then
GoTo Finally
End If
'Crée l'entête du fichier bmp
With bmfBitmapFileHeader
.bfType = &H4D42&
.bfOffBits = Len(bmfBitmapFileHeader) + Len(bmiBitmapInfo)
.bfSize = .bfOffBits + bmiBitmapInfo.biSizeImage
End With
'Lit les bits du bitmap et les place dans le tableau de pixels
ReDim pixels(1 To 4, 1 To lngLargeur, 1 To lngHauteur)
If apiGetDIBits(lngHdc, lngHBmp, 0, lngHauteur, pixels(1, 1, 1), _
bmiBitmapInfo, DIB_RGB_COLORS) = 0 Then
GoTo Finally
End If
lngFnum = FreeFile
'Crée le fichier
Open strNomDuFichier For Binary As lngFnum
bolOuvert = True
'Ecrit l'entête
Put #lngFnum, , bmfBitmapFileHeader
'Ecrit les informations du bitmap
Put #lngFnum, , bmiBitmapInfo
'Ecrit les bits de l'image
Put #lngFnum, , pixels
Finally:
'Ferme le fichier si ouvert
If bolOuvert Then Close lngFnum
'Supprime les objets
If lngHBmp <> 0 Then apiDeleteObject lngHBmp
If lngHdc <> 0 Then apiDeleteDC lngHdc
End Function
Function CopierColler(Source As String, Destination As String, _
NepasEcraser As Long) As Long
CopierColler = apiCopyFile(Source, Destination, NepasEcraser)
End Function
Function CouperColler(Source As String, Destination As String, _
NepasEcraser As Long) As Long
Dim r As Long
r = apiCopyFile(Source, Destination, NepasEcraser)
If r Then CouperColler = apiDeleteFile(Source)
CouperColler = r
End Function
Je remercie au passage Tofalu pour son aide sur cette fonction.
L'intérêt est de passer à cette fonction les bonnes coordonnées afin de ne sauvegarder que les dimensions de l'image et non pas le contour.
VIII. Conclusion▲
Le but de ce tutoriel est de repousser les limites d'Access, d'illustrer l'utilisation intensive d'API et d'expliquer le fonctionnement des routines d'affichage de GDI sous Windows.
Je ne développerai pas les API d'affichage de dialogues de choix de couleur, disponibles dans la FAQ.
Bons dessins sous MS Access ! Vous pouvez télécharger la base exemple ici.
Merci de m'avoir lu.







