Introduction :
Dans ce tutoriel, nous allons voir comment créer un composant
visuel pour WinForms.
Dans l'environnement .Net un composant visuel pour WinForm
s'appelle un "Contrôle" et est donc par conséquent un descendant direct
ou indirect de la classe Control de l'espace de nom System.Windows.Forms.
Pour nous aider à comprendre le principe nous nous aiderons d'un exemple,
une horloge analogique qui aura la particularité d'afficher ou non l'aiguille
des secondes. (Voir image ci-dessous)
Ce composant sera donc utilisable dans vos projets tout simplement
en l'incorporant sur votre Winform, tout comme
un contrôle standard du .Net Framework.
Et si vous avez la chance de posséder un environnement RAD pour .Net, tel
Visual Studio .Net, C# Builder... vous pourrez lui associer une image pour l'intégrer
à la boîte à outils.
Création du contrôle :
Pour créer notre contrôle il faut donc, comme dit précédemment, créer une classe qui
héritera de la classe System.Windows.Forms.Control.
Cette classe sera
contenue dans une bibliothèque et sera donc compilée comme telle.
En voici le code minimum :
Imports System
Imports System.Windows.Forms
Namespace Composant
Public Class Horloge
Inherits Control
Public Sub New()
End Sub
End Class
End Namespace
Le nom de la classe est important, est doit donc être explicite car il
sera le nom de type du contrôle. Dans l'exemple nous avons choisi "Horloge".
Il est aussi préférable que l'espace de nom soit le même
que celui de l'assembly résultant de la compilation.
Nous allons maintenant compléter le fichier source "Composant.vb".
Code du contrôle :
Fichier Composant.vb :
Imports System
Imports System.Drawing
Imports System.Windows.Forms
Imports System.ComponentModel
Namespace Composant
Public Class Horloge
Inherits Control
Private t As New Timer()
Private fAiguilleSec As Boolean = True
'--------------------------------------------------------
Public Sub New() 'constructeur
SetStyle(ControlStyles.DoubleBuffer Or ControlStyles.UserPaint Or _
ControlStyles.AllPaintingInWmPaint, True)
AddHandler t.Tick, AddressOf Tempo
t.Interval = 1000
t.Start()
End Sub 'New
'--------------------------------------------------------
Protected Overrides ReadOnly Property DefaultSize() As Size
Get
Return New Size(121, 121)
End Get
End Property
'--------------------------------------------------------
Private Sub Tempo(myObject As [Object], myEventArgs As EventArgs)
Invalidate()
End Sub
'--------------------------------------------------------
<Category("Appearance"), Description("Affiche l'aiguille des secondes."), _
DefaultValue(True)> _
Public Property AiguilleSec() As Boolean
Get
Return fAiguilleSec
End Get
Set
fAiguilleSec = value
Invalidate()
End Set
End Property
'--------------------------------------------------------
Protected Overrides Sub OnPaint(e As PaintEventArgs)
Dim blackPen As New Pen(Color.Black, 1)
Dim rect As New Rectangle(2, 2, 118, 118)
e.Graphics.DrawEllipse(blackPen, rect)
Dim d As DateTime = DateTime.Now
Dim n, z, u, x0, y0, x1, y1, x2, y2, x3, y3 As Single
Dim aigPoints(3) As PointF
'-----------------------
' Aiguille des Secondes (si propriété AiguilleSec à true)
If AiguilleSec Then
n = d.Second * 200 / 60
z = n / 100 * 3.14159F
u =(n + 50) / 100 * 3.14159F
x0 = CSng(Math.Sin(z)) * 50
y0 = CSng(- Math.Cos(z)) * 50
x1 = CSng(- Math.Sin(z)) * 10
y1 = CSng(Math.Cos(z)) * 10
x2 = CSng(Math.Sin(u)) * 2
y2 = CSng(- Math.Cos(u)) * 2
x3 = CSng(- Math.Sin(u)) * 2
y3 = CSng(Math.Cos(u)) * 2
Dim redBrush As New SolidBrush(Color.Red)
aigPoints(0).X = x1 + 60
aigPoints(0).Y = y1 + 60
aigPoints(1).X = x2 + 60
aigPoints(1).Y = y2 + 60
aigPoints(2).X = x0 + 60
aigPoints(2).Y = y0 + 60
aigPoints(3).X = x3 + 60
aigPoints(3).Y = y3 + 60
e.Graphics.FillPolygon(redBrush, aigPoints)
e.Graphics.DrawPolygon(blackPen, aigPoints)
redBrush.Dispose()
End If
'-----------------------
' Aiguille des Minutes
n = d.Minute * 200 / 60
z = n / 100 * 3.14159F
u =(n + 50) / 100 * 3.14159F
x0 = CSng(Math.Sin(z)) * 50
y0 = CSng(- Math.Cos(z)) * 50
x1 = CSng(- Math.Sin(z)) * 10
y1 = CSng(Math.Cos(z)) * 10
x2 = CSng(Math.Sin(u)) * 4
y2 = CSng(- Math.Cos(u)) * 4
x3 = CSng(- Math.Sin(u)) * 4
y3 = CSng(Math.Cos(u)) * 4
Dim limeBrush As New SolidBrush(Color.Lime)
aigPoints(0).X = x1 + 60
aigPoints(0).Y = y1 + 60
aigPoints(1).X = x2 + 60
aigPoints(1).Y = y2 + 60
aigPoints(2).X = x0 + 60
aigPoints(2).Y = y0 + 60
aigPoints(3).X = x3 + 60
aigPoints(3).Y = y3 + 60
e.Graphics.FillPolygon(limeBrush, aigPoints)
e.Graphics.DrawPolygon(blackPen, aigPoints)
limeBrush.Dispose()
'-----------------------
' Aiguille des Heures
n = d.Hour * 200 / 12 + d.Minute * 200 / 60 / 12
z = n / 100 * 3.14159F
u =(n + 50) / 100 * 3.14159F
x0 = CSng(Math.Sin(z)) * 35
y0 = CSng(- Math.Cos(z)) * 35
x1 = CSng(- Math.Sin(z)) * 10
y1 = CSng(Math.Cos(z)) * 10
x2 = CSng(Math.Sin(u)) * 4
y2 = CSng(- Math.Cos(u)) * 4
x3 = CSng(- Math.Sin(u)) * 4
y3 = CSng(Math.Cos(u)) * 4
Dim yellowBrush As New SolidBrush(Color.Yellow)
aigPoints(0).X = x1 + 60
aigPoints(0).Y = y1 + 60
aigPoints(1).X = x2 + 60
aigPoints(1).Y = y2 + 60
aigPoints(2).X = x0 + 60
aigPoints(2).Y = y0 + 60
aigPoints(3).X = x3 + 60
aigPoints(3).Y = y3 + 60
e.Graphics.FillPolygon(yellowBrush, aigPoints)
e.Graphics.DrawPolygon(blackPen, aigPoints)
yellowBrush.Dispose()
'-----------------------
' Chiffres
Dim drawFont As New Font("Arial", 8)
Dim drawBrush As New SolidBrush(Color.Black)
e.Graphics.DrawString("12", drawFont, drawBrush, 55, 6)
e.Graphics.DrawString("6", drawFont, drawBrush, 58, 101)
e.Graphics.DrawString("3", drawFont, drawBrush, 105, 53)
e.Graphics.DrawString("9", drawFont, drawBrush, 8, 53)
'-----------------------
drawFont.Dispose()
drawBrush.Dispose()
blackPen.Dispose()
MyBase.OnPaint(e)
End Sub 'OnPaint
'--------------------------------------------------------
Protected Overloads Overrides Sub Dispose(disposing As Boolean)
If disposing And Not (t Is Nothing) Then
t.Dispose()
t = Nothing
End If
MyBase.Dispose(disposing)
End Sub 'Dispose
End Class 'Horloge
End Namespace 'Composant
En plus des directives "Imports" pour les espaces de noms
System et System.Windows.Forms nous aurons besoin de
System.Drawing pour tout ce qui est dessin.
Nous allons maintenant détailler le code du contrôle :
Le Timer :
Comme nous aurons besoin de rafraîchir le dessin de l'horloge pour dessiner
la nouvelle position des aiguilles, nous créons un Timer dont nous initialiserons
la propriété Interval à 1000 ms, soit une seconde. Nous affecterons la méthode Tempo
à son événement Tick et nous le démarrerons par sa méthode Start.
La méthode Tempo appellera la méthode Invalidate
qui provoquera le rafraîchissement du dessin en déclanchant un événement Paint.
La méthode OnPaint :
Nous allons redéfinir la méthode OnPaint de notre contrôle pour dessiner
son contenu. (Cette méthode est appelée à chaque fois que le dessin du contrôle à besoin
d'être redessiné.) Je ne vais pas renter dans le détail des fonctions de dessin,
mais en consultant son code vous pouvez observer que l'on utilise des pinceaux (Brush)
et des crayons (Pen) pour dessiner, des fontes (Font) pour écrire du texte ;
tous ces objets proviennent de l'espace de nom System.Drawing.
Dans le cas de notre horloge nous dessinerons ses aiguilles à l'aide des méthodes
DrawPolygon et FillPolygon son contour avec la méthode DrawEllipse
; nous écrirons les textes à l'aide de la méthode DrawString.
Ces méthodes provenant de la classe System.Drawing.Graphics.
Les méthodes DrawPolygon et FillPolygon utilisent un tableau de points dont les
points sont calculés avec les méthodes Sin et Cos de la classe System.Math.
On libère les pinceaux, crayons, fontes par l'appel de leurs méthodes Dispose,
et on termine par l'appel de la méthode OnPaint de la classe de base.
Dans le constructeur du composant on peut remarquer l'appel de la méthode SetStyle :
SetStyle(ControlStyles.DoubleBuffer Or ControlStyles.UserPaint Or _
ControlStyles.AllPaintingInWmPaint, True)
Cette méthode sert à modifier les styles d'un contrôle, ici nous l'utilisons
pour mettre en place un Double Buffer de dessin afin d'éviter le scintillement.
La propriété DefaultSize :
Cette propriété retourne la valeur par défaut des dimensions du contrôle par
l'intermédiaire de sa méthode get.
Pour donner une valeur par défaut à un contrôle, il est conseillé de
redéfinir cette propriété plutôt que de définir de nouvelles dimensions dans
son constructeur.
La nouvelle propriété AiguilleSec :
Dans le cas de notre horloge cette propriété sert à afficher ou ne pas
afficher l'aiguille des secondes, propriété qui est donc utilisée au moment du
dessin de cette même aiguille dans la méthode OnPaint :
If AiguilleSec Then
'....
Cette propriété sera déclarée public car elle doit être accessible en
dehors de la classe. Une propriété, vue de l'extérieur se comporte comme une donnée membre
(dans l'environnement .Net on dit "champ"), mais vue de l'intérieur elle cache deux méthodes get et
set permettant d'accéder à ce champ qui doit donc exister dans la classe.
Ces méthodes sont aussi appelées méthodes d'accesseur.
Dans notre exemple le champ se nomme fAiguilleSec, il est de type booléen déclaré private
et initialisé à true.
La propriété doit donc être de même type que le champ lui correspondant, donc booléenne
dans l'exemple.
Public Property AiguilleSec() As Boolean
Get
Return fAiguilleSec
End Get
Set
fAiguilleSec = value
Invalidate()
End Set
End Property
Comme on peut le voir dans le code la méthode get retourne la valeur du champ
mémorisant la valeur de cette propriété et la méthode set permet d'affecter une nouvelle valeur à ce
même champ. (Dans la méthode set d'une propriété le nom de la variable
retournant la nouvelle valeur est toujours value).
Dans le cas de notre horloge après l'affectation de la nouvelle
valeur dans la méthode set nous appellerons la méthode Invalidate
de notre contrôle afin de provoquer un rafraîchissement du dessin.
Ce système de propriétés permet de simplifier le code pendant l'utilisation des composants.
La syntaxe d'utilisation d'une propriété est identique à celle d'un champ
tout en préservant l'encapsulation des données.
De plus il permet une intégration dans les environnements RAD car les propriétés
peuvent être accessibles à la conception par l'intermédiaire des inspecteurs d'objets.
On termine par la libération du timer dans la méthode redéfinie Dispose.
Précisions pour les environnements RAD :
Juste avant la définition de la propriété AiguilleSec on peut voir ces lignes
entre le signe "<" et le signe ">" :
<Category("Appearance"), Description("Affiche l'aiguille des secondes."), _
DefaultValue(True)> _
Public Property AiguilleSec() As Boolean
'....
Ce sont les Attributs de la propriété, ils doivent être définis avant chaque
nouvelle propriété que l'on ajoute à notre composant.
Ici ils servent à fournir des informations à l'EDI pour affichage de la propriété
dans sa palette d'outils.
Category sélectionne la rubrique de propriétés ou l'on veut voir apparaître la
propriété. (Voir liste dans l'aide : Bibliothèque de classes -> System.ComponentModel ->
CategoryAttribute)
Description en donne une description qui apparaîtra dans la zone de texte en bas de la
palette d'outils et DefaultValue donne sa valeur par défaut.
(En leur absence la propriété sera mise dans une catégorie nommée "Divers")
Si vous définissez des attributs de propriété,
il faudra rajouter la directive using pour
l'espace de nom System.ComponentModel.
Pour ces environnements on peut aussi associer une icône au composant.
Elle doit être contenue dans un bitmap 16 x 16 pixels et devra être ajoutée au projet
avant compilation.
Voir : Procédure pour C# Builder.
Compilation en lignes de commandes :
Si vous ne possédez pas d'outil IDE,
vous pouvez toujours le compiler en lignes de commandes :
vbc /target:library /r:System.dll /r:System.Drawing.dll
/r:System.Windows.Forms.dll composant.vb
Après compilation, un fichier (Assembly) nommé Composant.dll a été créé.
C'est lui dont vous aurez besoin pour utiliser le contrôle.
C'est que nous allons voir dans l'exemple suivant.
Exemple d'utilisation :
Avec un environnement RAD ou le composant a été intégré à la palette d'outils,
il y a juste à le poser sur la WinForm comme tout autre contrôle.
L'exemple ci-dessous est destiné a être compilé en lignes de commandes.
WinForm.vb :
Imports System
Imports System.Drawing
Imports System.Windows.Forms
Namespace MonProjet
Public Class WinForm
Inherits Form
Private horloge1 As New Composant.Horloge()
Public Sub New()
MyBase.New()
horloge1.Location = New Point(20, 20)
Size = New Size(170, 200)
Text = "WinForm"
Controls.Add(Me.horloge1)
End Sub 'New
Public Shared Sub Main()
Application.Run(New WinForm())
End Sub 'Main
End Class 'WinForm
End Namespace 'MonProjet
Comme vous pouvez le constater, le positionnement de l'horloge sur une WinForm
n'est pas plus compliqué que celui d'un contrôle ordinaire du .Net Framwork.
Pour faciliter la compilation nous déplacerons l'assembly Composant.dll
dans le même dossier que le fichier source (Winform.cs) de l'exemple ci-dessus.
Compilation de l'exemple en lignes de commandes :
vbc /target:winexe /r:System.dll /r:System.Drawing.dll
/r:System.Windows.Forms.dll /r:composant.dll WinForm.vb
Voilà, j'espère vous avoir un peu aidé à concevoir vos propres contrôles pour
WinForms.
Bonne composition.
CGi
et Neo.51
Avec la contribution d'Alacazam pour la relecture.
Télécharger les sources.
|