Concevoir des formulaires exotiques déformés ou géométriques avec Visual Basic 6.0

Jean-Philippe AMBROSINO

Ce document a pour but de vous montrer comment, avec Visual Basic versions 4.0 à 6.0, concevoir des formulaires de différentes formes, qu'elles soient géométriques ou tracées à main levée, voire dotées de trous…
L'exploitation de ce tutoriel vous ouvrira de nombreuses possibilités pour réaliser des fenêtres de formulaire hors du commun.
Pour mettre en œuvre ce tutoriel, vous devez maîtriser la conception des formulaires, connaître le langage Visual Basic et plus particulièrement être à l'aise avec l'usage des API Windows.
Vous devez également disposer d'un logiciel de retouche d'images ou de dessins tel que PaintShop Pro, The Gimp ou encore mon préféré, Photoshop…
Mais MsPaint® de Microsoft® peut s'avérer suffisant pour les formes simples
Commentez ♪

- Titre : Concevoir des formulaires exotiques déformés ou géométriques avec Visual Basic 6.0

- Auteur : Jean-Philippe AMBROSINO

- Parution : 20 décembre 2005

- Licence : Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.

Sommaire

- I. Avant-propos

- I-A. Remerciements

- I-B. Contact

- II. Présentation du projet

- III. Conception d'un formulaire déformé

- III-A. Création du formulaire

- III-B. Affectation de l'image au formulaire

- IV. Écriture du code du projet

- IV-A. Écriture du module

- IV-A-1. Comment fonctionne cette procédure ?

- IV-B. Définition du code pour le formulaire

- V. Test de fonctionnement

- V-A. Correction de la zone de bleus

- VI. Déplacer le formulaire

- VI-A. Mise en œuvre de la routine de déplacement

- VII. Conclusion

I - Avant-propos

Ce document a pour but de vous montrer comment, avec Visual Basic versions 4.0 à 6.0, concevoir des formulaires de différentes formes, qu'elles soient géométriques ou tracées à main levée, voire dotées de trous…
L'exploitation de ce tutoriel vous ouvrira de nombreuses possibilités pour réaliser des fenêtres de formulaires hors du commun.
En effet, lorsque vous créez un nouvel objet Form, celui-ci est une simple fenêtre rectangulaire dotée ou non d'une barre de titre, mais restera de toute façon à quatre coins.
Grâce à ce tutoriel, vous serez en mesure de concevoir des formulaires polyformes.

I-A - Remerciements

Je tiens à remercier tout particulièrement toutes celles et tous ceux qui ont participé à la relecture de ce document en y incluant leurs remarques.

I-B - Contact

Pour tout renseignement complémentaire, veuillez me contacter directement (Argyronet) par MP.

II - Présentation du projet

Pour illustrer ce tutoriel et vous donner envie de le lire dans sa totalité, je vous mets d'entrée 4 idées représentatives de ce que va pouvoir vous apporter la lecture de ce document.
En effet, pour mes projets de réalisations multimédias sous Visual Basic, je me suis penché sur la possibilité de sortir de l'ordinaire en matière de conception de formulaires entre guillemets rigolos…
Cela était possible… En C++ ; Je me suis dit qu'en exploitant les mêmes API sous Visual Basic, on devrait pouvoir satisfaire cette possibilité ; ce fut le cas :

Un formulaire sous forme de feuille

Un formulaire sous forme d'étoile trouée

Un formulaire sous forme de calculatrice^1

(1) - Calculatrice pédagogique animée et parlante développée pour les enfants

Un formulaire sous forme d'Horloge Coucou^2

(2) - Horloge pédagogique animée et parlante développée pour les enfants

Comme vous pouvez le constater, nous pouvons réaliser à peu près n'importe quelle forme de formulaire pour peu que l'on respecte deux éléments essentiels :
- faire en sorte que l'image possède un contour uniforme et monochrome suffisamment discret pour que cela ne se remarque pas ;
- faire en sorte que l'arrière-plan soit défini avec une couleur RVB uniforme absolument absente de l'image dessinée ;
en effet, le principe de mise en exécution de ces formulaires se base sur le fait qu'une couleur (celle de l'arrière plan) soit mise en transparence grâce à une combinaison de fonctions API Windows spécifiques.

III - Conception d'un formulaire déformé

Avant toute chose, je précise ici que je ne m'étendrai pas sur les explications concernant à l'utilisation même de Visual Basic et en particulier la création de formulaires… Vous devez être en mesure de comprendre ce qu'est l'objet formulaire et toutes les propriétés qui le composent ainsi que de connaître les événements dont il est pourvu.

Si vous ignorez comment concevoir un formulaire, je vous conseille de vous rendre à cette page et de lire par exemple ce tutoriel…

III-A - Création du formulaire

Pour concevoir la forme de ce formulaire, j'utiliserai le logiciel de dessin MsPaint® livré par défaut avec Windows.

Créez d'abord un nouveau formulaire depuis la boîte de dialogue Project/Add Form… et sélectionnez la première icône de la liste nommée Form :

Vous allez alors obtenir une fenêtre vide qui portera le nom de Form suivi d'un numéro correspondant à l'index du nombre de fenêtres que vous avez déjà créées (Exemple ici Form2).

Une fois cela fait, cliquez sur le bouton Démarrer de Windows où vous choisirez Exécutez…
Pour les adeptes des raccourcis, appuyez simultanément sur les touches …

La boîte de dialogue apparaît et vous y inscrivez le nom du programme à savoir, Mspaint…
Si vous préférez utiliser un autre logiciel graphique, il n'y a aucune difficulté du moment que vous respectez les indications qui vont suivre.
Je précise que l'usage de MsPaint permet de générer des images Bitmap simples et c'est ce qu'il y a de plus conseillé pour débuter.

Vous cliquez alors sur le bouton OK.

Le logiciel vous affiche une feuille blanche sur laquelle vous allez dessiner une forme géométrique simple.

Pour illustrer cet exercice, je vous propose de dessiner une étoile.

Petite astuce

Pour dessiner une étoile, utilisez l'outil Polygone et formez un triangle isocèle que vous dupliquez cinq fois en utilisant deux instances de MsPaint et en utilisant l'outil de rotation de MsPaint…

Vous effectuez alors un copier-coller à chaque fois et nettoyez le surplus de pixels dessinés.

Dessin de l'étoile dans MsPaint

Faites très attention à une chose importante…

Il est impératif de vérifier chaque extrémité lorsque la forme dessinée est complexe de manière à s'assurer qu'il n'existe aucun interstice représenté par un pixel vide.

En effet, au moment du remplissage et en particulier avec MsPaint®, le fait de laisser un pixel vide autorisera la séquence de remplissage à recouvrir la totalité du dessin.

Dessin avec pixel manquant

Le remplissage s'étale sur l'ensemble du dessin

Une fois votre forme géométrique dessinée, vous pouvez alors choisir une couleur de remplissage.
Du fait que la palette de MsPaint® ne propose qu'un jeu de 28 couleurs, vous cliquerez deux fois sur l'une des zones colorées de manière à choisir une couleur personnalisée.

Ici, j'ai choisi une couleur gris turquoise :
R=206 V=224 B=232;
donc, ici, vous saisissez directement ces valeurs dans les zones respectives.

Vous validez alors par OK puis prenez l'outil Pot de peinture nommé Remplissage pour appliquer la couleur dans l'étoile.
De la même manière, vous appliquerez le remplissage à l'extérieur de l'étoile avec une couleur RVB simple par exemple un bleu uni :
R=000 V=000 B=255;

La figure devient alors la suivante :

Il ne vous reste plus qu'à ajuster l'image de manière à ce que l'espace de remplissage inutilement utilisé soit rogné…

Vous enregistrez alors votre image au format BMP dans le dossier .\images (sous-dossier du dossier de votre application) et le nommez par exemple imagestar.bmp.
Vous basculez alors de nouveau sous Visual Basic où nous attend patiemment le Form2 laissé à l'abandon pendant tout ce travail…

III-B - Affectation de l'image au formulaire

L'étape suivante va consister à mettre en œuvre le formulaire. Dans les propriétés de ce dernier, repérez la propriété Picture et cliquez sur le bouton afin d'aller chercher l'image que vous venez de dessiner…

Ajustez alors le formulaire à une taille un peu plus grande que l'image.
Un certain nombre d'autres propriétés sont à définir pour ce formulaire et elles sont les suivantes :

- Name = frmTransStar

- BorderStyle = 0

- ScaleMode = 3 (Pixel)

- ShowInTaskbar = True

Pour continuer, vous allez prendre dans la boîte à outils, un contrôle PictureBox que vous allez positionner sur la droite de manière à ce qu'il ne vous gêne pas lors de la pose d'éventuels autres contrôles servant au formulaire à proprement parler.

Dessinez alors un petit carré d'environ 1 cm de coté (soit en gros 700 pixels) avec le curseur (figure 1), lâchez alors le bouton de la souris et le petit carré est entouré de 8 points d'ancrage (figure 2) ;

Figure 1

Figure 2

Dans la fenêtre des propriétés, repérez la propriété AutoSize et affectez-lui True (figure 3).

Figure 3

Repérez ensuite la propriété Picture de ce contrôle et affectez-lui la même image que celle que vous avez affectée à la même propriété pour le formulaire à savoir imagestar.bmp. Un certain nombre d'autres propriétés sont à définir pour ce contrôle et elles sont les suivantes :

- Name = pctStar

- AutoRedraw = True

- AutoSize = True

- BorderStyle = 0

- ScaleMode = 3 (Pixel)

- Visible = False

Si entretemps, la taille de votre image change (vous l'avez redessinée, agrandie ou diminuée pour x raisons) il vous faut réajuster les dimensions du contrôle PictureBox de manière à ce que le bord droit et le bord bas effleurent exactement ceux de l'image insérée…

Sinon, ne changez rien…

Pourquoi devez-vous faire cela ? Tout simplement parce que le formulaire sera ajusté sur la taille de ce contrôle. Une fois cela fait, vous pouvez placer le contrôle PictureBox à peu près n'importe où sur le formulaire. Mettez-le là où il ne vous gêne pas pour travailler si par exemple, vous avez d'autres contrôles à ajouter à votre formulaire.
Prenez bien en considération que la taille maximum de votre formulaire sera celle du contrôle PictureBox.

Dans le menu Format, cliquez sur le menu Lock controls. Ainsi, vous ne pouvez pas faire de fausses manipulations quant à la modification des éléments.

En cas de besoin, déverrouillez cette propriété.

IV - Écriture du code du projet

Il est bien entendu évident que tout ce vous avez effectué jusqu'ici ne suffit pas à obtenir un formulaire en forme d'étoile…
Tout le principe de transformation du formulaire est fondé sur la destruction des zones de couleurs bien précises et ce à l'aide de fonctions spécifiques stipulées ci-après…

IV-A - Écriture du module

Insérez alors un nouveau module dans votre projet et profitez de ce que vous avez la main sur ce dernier pour le nommer de suite…
Par exemple : basTransforms

Les bonnes habitudes font gagner un temps considérable.

Dans ce module que vous ouvrez, vérifiez que la mention Option Explicit est bien mentionnée en entête de module, sinon, écrivez-la.

Déclaration des API Windows

' **************************************************************************************************

' CreateRectRgn:
' Fonction qui créée une région rectangulaire
'
' CombineRgn:
' Fonction qui combine 2 régions et stocke le résultat dans une troisième :
' Les 2 régions sont combinées selon un mode spécifique.
'
' DeleteObject:
' Fonction qui détruit tout ce qui est objet logique à savoir pen, brush, font, bitmap, region,
' ou encore palette tout en libérant les ressources consommées par le système pour cet objet.
' Bien entendu, une fois détruit, le handle de cet objet n'existera plus à partir de cet instant.
'
' SetWindowRgn:
' Fonction qui définie la région d'une fenêtre pour une fenêtre donnée.
' La région de la fenêtre détermine la zone à l'intérieure de laquelle la fenêtre alloue au système
' la possibilité de dessiner un objet.
' Toutefois, le système n'affiche aucune portion de la fenêtre qui se trouve à l'extérieure de la
' région en question.

' **************************************************************************************************
Option Explicit

Private Declare Function CreateRectRgn Lib "gdi32" (ByVal X1 As Long, ByVal Y1 As Long, ByVal _
X2 As Long, ByVal Y2 As Long) As Long
Private Declare Function CombineRgn Lib "gdi32" (ByVal hDestRgn As Long, ByVal _
hSrcRgn1 As Long, ByVal hSrcRgn2 As Long, ByVal nCombineMode As Long) As Long
Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
Private Declare Function SetWindowRgn Lib "user32" (ByVal hwnd As Long, ByVal hRgn As Long, _
ByVal bRedraw As Boolean) As Long

Private Const RGN_OR = 2

La partie des API étant écrite, il me reste à vous montrer la fonction miracle qui va transformer votre formulaire en étoile…
Vous écrirez ou recopierez le code suivant juste en dessous des déclarations :

Procédure de transformation

Public Sub GenerateTransForm(ByVal Frm As Form, ByVal Pct As PictureBox, ByVal ColorValue As Long)
Dim lngX As Long, lngY As Long
Dim lngStartX As Long, lngStartY As Long
Dim lngEndX As Long, lngEndY As Long
Dim lngHRectregion As Long, lngTempHRectRegion As Long
Dim lngVoidReturn As Long

Dim blnStatus As Boolean

Frm.Width = Frm.ScaleX(Pct.Width, vbPixels, vbTwips)
Frm.Height = Frm.ScaleY(Pct.Height, vbPixels, vbTwips)
DoEvents
blnStatus = False
For lngX = 0 To Pct.ScaleWidth
blnStatus = False
For lngY = 0 To Pct.ScaleHeight
If blnStatus Then
If Pct.Point(lngX, lngY) = ColorValue Then
lngEndX = lngX
lngEndY = lngY
If lngHRectregion = 0 Then
lngHRectregion = CreateRectRgn(lngStartX, lngStartY, lngEndX + 1, lngEndY)
Else
lngTempHRectRegion = CreateRectRgn(lngStartX, lngStartY, lngEndX + 1, lngEndY)
lngVoidReturn = CombineRgn(lngHRectregion, lngHRectregion, lngTempHRectRegion, RGN_OR)
DeleteObject lngTempHRectRegion
End If
blnStatus = False
End If
Else
If Pct.Point(lngX, lngY) <> ColorValue Then
lngStartX = lngX
lngStartY = lngY
lngEndX = lngX
lngEndY = lngY
blnStatus = True
End If
End If
Next
' *** Complément de code à ajouter entre les deux blocs Next pour éviter qu'une zone
' *** soit ignorée si le dernier pixel en bas de l'image n'est pas dans la couleur ciblée (ici bleue).
' *** Merci à "David Ughetto" pour cette suggestion...
If blnStatus Then
lngEndX = lngX
lngEndY = lngY
If lngHRectregion = 0 Then
lngHRectregion = CreateRectRgn(lngStartX, lngStartY, lngEndX + 1, lngEndY)
Else
lngTempHRectRegion = CreateRectRgn(lngStartX, lngStartY, lngEndX + 1, lngEndY)
lngVoidReturn = CombineRgn(lngHRectregion, lngHRectregion, lngTempHRectRegion, RGN_OR)
DeleteObject lngTempHRectRegion
End If
End If
Next
lngVoidReturn = SetWindowRgn(Frm.hwnd, lngHRectregion, True)
lngVoidReturn = DeleteObject(lngHRectregion)
End Sub

Cette procédure utilise trois arguments (Paramètres) qui sont respectivement l'objet Form, l'objet PictureBox et la combinaison convertie en Long de la palette RGB qui entoure l'objet dessiné, en l'occurrence l'étoile et donc ici, le bleu uni que j'ai appliqué à l'image.

IV-A-1 - Comment fonctionne cette procédure ?

Dans un premier temps, la procédure définit la largeur et la hauteur du formulaire aux dimensions de l'image, c'est pourquoi je vous ai précisé ci-avant cet avertissement.

Une fois cela fait, elle affecte à un Boolean la valeur False de manière à évaluer quelles actions entreprendre dans le bloc If/End If et ainsi, définir les différentes régions de couleur à détruire.
Si une zone de couleur correspond à l'argument ColorValue, la fonction DeleteObject se charge de détruire la région définie.
Cette procédure s'exécute dans une boucle qui est définie elle-même sur chaque Pixel X de la largeur de l'image et à l'intérieure de laquelle une autre boucle s'occupe de chaque Pixel Y.

Vous enregistrez alors le projet en donnant un nom de fichier à votre Formulaire (frmTransform), au module (basTransform) et enfin au projet (pjtTransform).
Vous pouvez alors fermer la fenêtre du module ; la fenêtre du formulaire réapparaît alors.

IV-B - Définition du code pour le formulaire

Il reste pour terminer à écrire quelques lignes pour le formulaire de manière à ce que la fonction soit appelée dès son chargement…

Code du Formulaire

Option Explicit

Private Sub Form_Load()
GenerateTransForm Me, pctStar, RGB(0, 0, 255)
'   SetFormToTop Me, True
End Sub

On appelle donc la procédure GenerateTransForm suivie de ses trois paramètres qui sont, comme stipulé plus haut, les objets qui constituent le formulaire.
Pour définir l'argument ColorValue, on associera la fonction RGB() qui prendra ses trois valeurs relatives à la couleur de remplissage de l'arrière-plan à savoir, 0 pour le rouge, 0 pour le vert et 255 pour le bleu.

Point très important
Il est impératif que vous sachiez déterminer en R-V-B la couleur de l'arrière-plan sans quoi, la procédure sera incapable d'agir correctement.

Si vous ignorez la valeur, alors il vous faut ouvrir le fichier contenant l'image dans un logiciel pourvu d'une pipette pour en prendre la couleur et de là, connaître la valeur réelle RVB du niveau de la couleur. (Par acquit de conscience, mettez 0, 0, 254 à l'argument ColorValue et vous constaterez que ça ne marche pas.)

Il y a une ligne commentée dans le code… J'ai fait exprès de la laisser telle que pour vous montrer qu'il est possible de mettre au premier plan la fenêtre de telle sorte à ce qu'elle ne passe jamais en dessous des autres dans l'environnement Windows.
Cette procédure utilise l'API SetWindowPos() avec les arguments qui s'imposent.
Je vous laisse le soin de chercher dans les codes source du Forum l'usage de cette fonction.

V - Test de fonctionnement

Après avoir vérifié que tout est bien rédigé, que les propriétés ont bien été définies, vous pouvez appuyer sur la touche F5…
Le formulaire en étoile apparaît alors…

V-A - Correction de la zone de bleus

Vous pouvez constater que le pourtour du formulaire est coiffé d'une légère ligne bleue qui est mal nettoyée… Cela n'est pas forcément gênant si la figure est bien dessinée.
En fait, il est délicat d'obtenir quelque chose de parfait, le mélange noir/bleu étant ignoré de la combinaison 0, 0, 255.
Il vous faut effectuer des tests à maintes reprises en ayant plusieurs jeux d'images. Mais je conseille tout de même de conserver une bordure autour de la forme et surtout de faire en sorte que votre forme ne possède aucune couleur identique à celle de l'arrière-plan.
C'est par tests successifs que vous obtiendrez ce que vous souhaitez, j'en parle en connaissance de cause.

VI - Déplacer le formulaire

Eh oui, vous vous en doutez, du fait que la propriété BorderStyle est à+ zéro, il n'y a plus de barre de titre, donc impossibilité de déplacer le formulaire…
Il existe plusieurs solutions pour déplacer un formulaire dont la barre de titre a été ôtée… Le principe consiste en fait à déterminer le Rectangle qui constitue la fenêtre du formulaire (je précise Rectangle, car même s'il est transparent et déformé, le formulaire est toujours situé dans une région rectangulaire) et par la même, déterminer les coordonnées XY de départ du pointeur de la souris. Une fois cela isolé, un calcul s'effectue pour déterminer les nouvelles coordonnées XY lorsque la souris a été déplacée et enfin, redessiner le rectangle dans son intégralité.
Dans l'absolu, l'opération est totalement transparente pour l'utilisateur, car les effets graphiques conséquents sont invisibles.

VI-A - Mise en œuvre de la routine de déplacement

Pour déplacer le formulaire dans cet état, je vais utiliser deux API supplémentaires associées à un jeu de constantes que je vais exploiter sur l'événement qui gère les déplacements de la souris.
L'événement en question se nomme MouseMove() et il est disponible pour quasiment tous les contrôles posés sur un formulaire. Celui qui nous intéresse, en l'occurrence, est celui du formulaire lui-même.
Vous allez ajouter cette portion de code dans le module basTransforms:

Code ajouté au module

' ReleaseCapture
' Fonction permettant de libérer la capture de la région d'une fenêtre dans le processus en cours
' puis reconstitue le processus d'entrée par la souris a son état normal.
'
' SendMessage
' Fonction qui envoie un message spécifique à une ou plusieurs fenêtres. Aucun résultat n'est retourné
' tant que la procédure d'appel n'a pas intercepté et traité le message contrairement d'ailleurs à la
' fonction PostMessage qui envoie un message dans une file d'attente de processus.

Public Declare Function ReleaseCapture Lib "user32" () As Long
Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, _
ByVal wParam As Long, lParam As Any) As Long

Public Const WM_NCLBUTTONDOWN = &HA1
Public Const HTCAPTION = 2

Comme vous pouvez le constater, les fonctions autant que les constantes sont déclarées en Public.
Il suffit alors de solliciter l'événement MouseMove() précité dans le module du formulaire comme suit :

Code de l´événement MouseMove

Private Sub Form_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
If Button = vbLeftButton Then
ReleaseCapture
SendMessage Me.hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0&
End If
End Sub

J'ai expressément ajouté un bloc If /End If qui vérifie quel bouton est appuyé et ici, c'est le gauche. cela se traduit par :
Si c'est bien le bouton gauche qui est enfoncé, alors appel respectif des fonctions ReleaseCapture() et SendMessage().

Vous pouvez alors tester de nouveau en appuyant sur F5 et vous constatez que vous pouvez déplacer le formulaire à votre guise.

VII - Conclusion

Ce tutoriel peut s'avérer utile pour les développeurs exigeants et surtout originaux qui souhaitent créer des formulaires qui sortent de l'ordinaire…

Bien entendu, je préconise de réserver ce genre de conception à des applications plutôt ludiques ou multimédias et en aucun cas à des projets de gestion… Vous pouvez constater toute la puissance de Visual Basic qui initialement ne prévoit pas de développer des projets comme celui-ci.
Je vous laisse vous entraîner avec cette pratique. Si vous rencontrez des difficultés ou si j'ai omis de préciser quelque chose qui reste obscur, n'hésitez pas à m'en faire part.

Pour compléter ce tutoriel, vous pouvez télécharger le projet de démonstration ici…