Creation d'un controle Winform avec Delphi 8 .NET
http://nono40.developpez.com/delphi/delphi8/source0080/ Par Nono40 (nono40.developpez.com)
Ce document presente la creation d'un controle WinForm personnalise. La version telechargeable de ce document ( HTML ) est disponible ici : article0080d8.zip Introduction
I. Creation de l'assemblage
II. Ajout de la propriete AiguilleSec
III. Ajout du code de dessin
IV. Surcharge de la taille par defaut
V. Ajout d'un evenement
VI. Utilisation du composant dans un projet Delphi .NET
VII. Utilisation du composant dans un projet C#
Conclusion Introduction Cet article va presenter la creation d'un composant WinForm personnalise derivant du type System.Windows.Forms.UserControl. Ce composant est une horloge analogique avec ou sans l'aiguille des secondes : La methode proposee ici est l'adaptation a Delphi 8 de l'article de CGI : Creation d'un controle WinForm pour .Net I. Creation de l'assemblage La notion de paquet en tant que tel n'existe plus dans Delphi 8. Ceux-ci ont ete remplaces par la definition d'assemblage introduite par .NET. Pour creer un composant WinForm utilisable il faut creer un nouvel assemblage contenant ce composant. Pour creer un nouvel assemblage pour le composant, utilisez Fichier->Nouveau->Paquet. Enregistrez le projet comme PHorloge.bdsprj. Ensuite ajouter un controle Winform avec Fichier->Nouveau->Controle utilisateur. Enregistrer le code comme UHorloge.pas. Dans l'editeur de propriete qui vient de s'ouvrir renommez ( propriete Name ) le composant en THorloge. Notez que Delphi fait alors directement deriver le composant de la classe System.Windows.Forms.UserControl et cree dans l'unite tout le code minimum de gestion du composant. Le gestionnaire de projet doit alors vous donner les informations suivantes : II. Ajout de la propriete AiguilleSec La propriete AiguilleSec va definir si l'aiguille des secondes est affichee ou non. La definition d'une nouvelle propriete est identique a la methode utilisee dans la VCL. Il faut ajouter une variable privee contenant la valeur de la propriete et definir les methodes eventuelles de lecture et ecriture de la propriete. Pour ceci il faut ajoutez dans la definition du composant les ligne suivantes : private
{ Private Declarations }
FAiguilleSec:Boolean;
Procedure SetAiguilleSec(Value:Boolean);
public
constructor Create;
published
[Category('Appearance'),
Description('Affiche l''aiguille des secondes.'),
DefaultValue(true)]
Property AiguilleSec:Boolean Read FAiguilleSec Write SetAiguilleSec;
end;
Notez ici la definition des attributs de la propriete permettant de definir la categorie d'affichage ainsi que le texte d'aide associe dans l'inspecteur d'objet. Dans le constructeur du composant, ajouter la valeur initiale de la propriete : constructor THorloge.Create;
begin
inherited Create;
//
// Required for Windows Form Designer support
//
InitializeComponent;
//
// TODO: Add any constructor code after InitializeComponent call
//
FAiguilleSec := True;
end;
Et pour terminer, definir le code de la methode d'ecriture de la propriete, l'appel d'invalidate va demander au controle de se redessiner. procedure THorloge.SetAiguilleSec(Value: Boolean);
begin
FAiguilleSec:=Value;
Invalidate;
end; III. Ajout du code de dessin Le dessin devant etre effectue toutes les secondes, nous allons ajouter un Timer au controle afin d'obtenir un evenement regulier. Le dessin en lui-meme sera assure par la surcharge de la methode OnPaint du controle. La definition de la classe devient donc : THorloge = class(System.Windows.Forms.UserControl)
//....
strict protected
///
/// Clean up any resources being used.
///
procedure Dispose(Disposing: Boolean); override;
procedure OnPaint(e:PaintEventArgs); Override;
private
{ Private Declarations }
T:Timer;
FAiguilleSec:Boolean;
Procedure SetAiguilleSec(Value:Boolean);
Procedure Tempo(myObject:System.Object; myEventArgs:System.EventArgs );
public
constructor Create;
published
[Category('Appearance'),
Description('Affiche l''aiguille des secondes.'),
DefaultValue(true)]
Property AiguilleSec:Boolean Read FAiguilleSec Write SetAiguilleSec;
end;
Comme d'habitude le composant enfant sera cree dans le constructeur de l'objet. constructor THorloge.Create;
begin
inherited Create;
//
// Required for Windows Form Designer support
//
InitializeComponent;
//
// TODO: Add any constructor code after InitializeComponent call
//
FAiguilleSec := True;
SetStyle(ControlStyles.DoubleBuffer Or ControlStyles.UserPaint Or
ControlStyles.AllPaintingInWmPaint, true);
T:=Timer.Create;
Include(T.Tick,Tempo);
T.Interval := 1000;
T.Start;
end;
Notez ici l'ajout d'un gestionnaire d'evenement ( Tempo dans notre exemple ) a la liste des gestionnaire associes a T.Tick. Cet ajout est realise a l'aide de la procedure Include, ce point est different de la surcharge habituelle d'un evenement d'un controle VCL. Notez aussi la modification du style du controle afin de definir un dessin personnalise et un double-buffer pour eviter le scintillement lors du trace. Le code de la procedure Tempo ne fera qu'invalider la zone d'affichage du composant : procedure THorloge.Tempo(myObject: TObject;
myEventArgs: System.EventArgs);
begin
Invalidate;
end;
L'evenement OnPaint se chargera du dessin du composant : procedure THorloge.OnPaint(e: PaintEventArgs);
Var BlackPen :Pen;
Rect :Rectangle;
d :DateTime;
Pinceau :SolidBrush;
drawFont :System.Drawing.Font;
n,z,u,x0,y0,x1,y1,x2,y2,x3,y3:Single;
aigPoints : Array[0..3]Of PointF;
begin
BlackPen := Pen.Create(Color.Black, 1);
Rect := Rectangle.Create( 2, 2, 118, 118);
e.Graphics.DrawEllipse(blackPen, rect); d := DateTime.Now; //-----------------------
// Aiguille des Secondes (si propriete AiguilleSec a true)
if AiguilleSec Then
Begin
n := d.Second*200/60;
z := n/100*3.14159;
u := (n 50)/100*3.14159; x0 := Math.Sin(z)*50;
y0 := -Math.Cos(z)*50; x1 := -Math.Sin(z)*10;
y1 := Math.Cos(z)*10; x2 := Math.Sin(u)*2;
y2 := -Math.Cos(u)*2; x3 := -Math.Sin(u)*2;
y3 := Math.Cos(u)*2; Pinceau:=SolidBrush.Create(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(Pinceau, aigPoints);
e.Graphics.DrawPolygon(blackPen, aigPoints);
Pinceau.Dispose();
End; //-----------------------
// Aiguille des Minutes n := d.Minute*200/60;
z := n/100*3.14159;
u := (n 50)/100*3.14159; x0 := Math.Sin(z)*50;
y0 := -Math.Cos(z)*50; x1 := -Math.Sin(z)*10;
y1 := Math.Cos(z)*10; x2 := Math.Sin(u)*4;
y2 := -Math.Cos(u)*4; x3 := -Math.Sin(u)*4;
y3 := Math.Cos(u)*4; Pinceau:=SolidBrush.Create(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(Pinceau , aigPoints);
e.Graphics.DrawPolygon(blackPen, aigPoints);
Pinceau.Dispose(); //-----------------------
// Aiguille des Heures n := d.Hour*200/12 d.Minute*200/60/12;
z := n/100*3.14159;
u := (n 50)/100*3.14159; x0 := Math.Sin(z)*35;
y0 := -Math.Cos(z)*35; x1 := -Math.Sin(z)*10;
y1 := Math.Cos(z)*10; x2 := Math.Sin(u)*4;
y2 := -Math.Cos(u)*4; x3 := -Math.Sin(u)*4;
y3 := Math.Cos(u)*4; Pinceau:=SolidBrush.Create(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(Pinceau , aigPoints);
e.Graphics.DrawPolygon(blackPen, aigPoints);
Pinceau.Dispose();
//-----------------------
// Chiffres
drawFont := System.Drawing.Font.Create('Arial', 8);
Pinceau := SolidBrush.Create(Color.Black); e.Graphics.DrawString('12', drawFont, Pinceau, 55, 6);
e.Graphics.DrawString( '6', drawFont, Pinceau, 58, 101);
e.Graphics.DrawString( '3', drawFont, Pinceau, 105, 53);
e.Graphics.DrawString( '9', drawFont, Pinceau, 8, 53);
//-----------------------
drawFont.Dispose();
Pinceau.Dispose();
blackPen.Dispose();
inherited;
End; IV. Surcharge de la taille par defaut Afin que la taille du composant soit correcte ( 121 x 121 ) lors de sa creation, il faut redefinir la taille par defaut du composant par surcharge de la propriete taille : THorloge = class(System.Windows.Forms.UserControl)
{$REGION 'Designer Managed Code'}
//...
strict protected
///
/// Clean up any resources being used.
///
procedure Dispose(Disposing: Boolean); override;
procedure OnPaint(e:PaintEventArgs); Override;
private
{ Private Declarations }
T:Timer;
FAiguilleSec:Boolean;
Function GetSize:Size;
Procedure SetAiguilleSec(Value:Boolean);
Procedure Tempo(myObject:System.Object; myEventArgs:System.EventArgs );
public
constructor Create;
published
Property Size Read GetSize;
[Category('Appearance'),
Description('Affiche l''aiguille des secondes.'),
DefaultValue(true)]
Property AiguilleSec:Boolean Read FAiguilleSec Write SetAiguilleSec;
end;
La methode GetSize est alors definie comme suit : function THorloge.GetSize: Size;
begin
Result := System.Drawing.Size.Create(121,121);
end; V. Ajout d'un evenement Un evenement en dotnet comporte toujours deux parametres, le premier est l'objet declenchant ( comme en VCL ) le deuxieme est un objet donnant la liste des valeurs specifiques a l'evenement. On va donc creer un nouvelle classe de description des parametres de l'evenement. Cette classe ne va donner ici que l'heure de declenchement, ce n'est pas tres utile, mais c'est juste pour l'exemple. TReveilArgs = Class
Public
DateHeureReveil : DateTime;
Constructor Create(D:DateTime);
End; TReveilEvent = Procedure ( Sender:TObject ; R : TReveilArgs )Of Object;
La date et l'heure de declenchement de l'evenement sera conserve dans une nouvelle propriete Reveil de type System.DateTime. L'ecriture d'un gestionnaire d'evenement est identique a celle utilisee dans la VCL. Une propriete de type evenement n'est plus declaree avec les mots clef Read et Write ( bien que ce soit toujours possible par compatibilite ) mais par les mots clefs Add et Remove. Ceci permet de gerer les evenements multicast de la CLR .NET. THorloge = class(System.Windows.Forms.UserControl)
//...
private
{ Private Declarations }
FReveil:DateTime;
FReveilEvent:TReveilEvent;
//...
published
[Category('Misc'),
Description('Date/heure de declenchement du reveil.'),
DefaultValue(true)]
Property Reveil:DateTime Read FReveil Write FReveil;
[Category('Misc'),
Description('Evenement declenche si Now=Reveil.'),
DefaultValue(true)]
Property OnReveil:TReveilEvent Add FReveilEvent Remove FReveilEvent;
end;
Bien sur, le code du timer va etre modifie pour tester si l'heure de l'evenement correspond et ainsi appeler le gestionnaiure d'evenement. Notez qu'il faut appeler la variable privee et non l'evenement lui-meme comme on le faisait en VCL. procedure THorloge.Tempo(myObject: TObject;
myEventArgs: System.EventArgs);
begin
Invalidate;
If DateTime.Now.ToString=Reveil.ToString Then
FReveilEvent(Self,TReveilArgs.Create(Reveil));
end; VI. Utilisation du composant dans un projet Delphi .NET Une fois le projet compile, installez le nouveau composant WinForm dans Delphi avec Composants->Composants .NET intalles. Puis en selectionnant l'assemblage nouvellement cree et de definir la categorie dans laquelle il faut ajouter le composant ( general par defaut ). Apres installation le composant apparait dans la liste : Il suffit alors de poser le composant sur une fiche ( WinForm ) afin de l'utiliser comme n'importe quel autre composant. VII. Utilisation du composant dans un projet C# Pour tester le composant das une application C# il suffit de compiler l'exemple suivant avec le compilateur en ligne csc. using System;
using System.Drawing;
using System.Windows.Forms; namespace MonProjet
{
public class WinForm : Form
{
private UHorloge.THorloge horloge1 = new UHorloge.THorloge(); public WinForm()
{
horloge1.Location = new Point(20, 20); Size = new Size(170, 200);
Text = "WinForm"; Controls.Add(this.horloge1);
} [STAThread]
static void Main()
{
Application.Run(new WinForm());
}
}
}
Notez que l'espace de nom n'est pas celui de la dll mais celui de l'unite contenant le composant. Lors de la compilation il faut donner en reference la dll contenant notre composant : csc /target:winexe /reference:PHorloge.dll WinForm.cs
http://nono40.developpez.com/sources/zips/source0080.zip