|
||
| [IT] Kylix: Creazione nuovi componenti grafici visuali | ||
|
Kylix: Creazione nuovi componenti grafici visuali REQUISITIUn componente visuale e' un oggetto che ha una immagine grafica statica (es. figura) o dinamica (es. edit box). Creare un nuovo componente visuale significa creare un controllo da inserire nelle proprie form con una grafica e delle proprieta' generalmente diverse da quelle dei componenti gia' esistenti. La creazione del nuovo componente richiede l'estensione di una determinata classe. I componenti visuali possono dividersi in due categorie: quelli che accettano un input e che vengono selezionati a runtime (focus), mediante TAB, click del mouse, ecc., e quelli che non ne hanno bisogno, pur continuando a rispondere agli eventi di sistema. Nel primo caso si puo' estendere la classe TCustomControl, nel secondo caso, invece, la classe TGraphicControl. Entrambe queste classi derivano direttamente o indirettamente da un'altra piu' generale che e' TControl. TControl e' la classe da cui derivano tutti i componenti visibili a runtime, fornendo metodi, proprieta' ed eventi per il loro controllo. Puo' essere interessante dare un'occhiata alla gerarchia completa delle classi di cui si e' parlato fino ad ora:
TObject
|
TPersistent
|
TComponent
|
TControl
|
+--------+--------+
| |
TGraphicControl ---> TWidgetControl
|
TCustomControl
Supponiamo di voler creare un componente rappresentante una freccia.![]() fig. 1 "esempio di componente" Eccone le specifiche:
Le informazioni richieste sono:
unit QFeelingArrow;
interface
uses
SysUtils, Types, Classes, QGraphics, QControls, QForms, QDialogs;
type
TFeelingArrow = class(TGraphicControl)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('FeelingLinux', [TFeelingArrow]);
end;
end.
La unit contiene una semplice estensione di classe, con una piccola aggiunta: viene generato il metodo Register
che consente l'inserimento del componente nella palette indicata ("FeelingLinux", in questo caso).I passi per completare la creazione del componente, a grandi linee sono i seguenti:
![]() fig.2 "Arriveremo a poter fare questo!" Pubblicazione delle proprieta' dell'oggetto Cominciamo con l'orientamento della freccia. Definiamo un tipo enumerato contenente i quattro orientamenti possibili, 0, 90, 180, 270 gradi. Prima della definizione della classe TFeelingArrow inseriamo TOrientation = (deg_0, deg_90, deg_180, deg_270);Tali valori devono essere pubblicati e resi disponibili all'utente per la scelta. Sara' necessario definire all'interno di TFeelingArrow sia una variabile di tipo TOrientation che un metodo per la sua modifica. Eventualmente si puo' inserire un metodo anche per la lettura, ma nella maggior parte dei casi la lettura della variabile coincide con la restituzione del valore della variabile stessa. Nella sezione private inseriamo _Orientation: TOrientation; procedure SetOrientation(v: TOrientation);con la seguente implementazione per SetOrientation:
procedure TFeelingArrow.SetOrientation(v: TOrientation);
begin
if _Orientation <> v then begin
_Orientation := v;
SetRealDimension;
Invalidate;
end;
end;
In pratica, il nuovo orientamento viene salvato solo se e' diverso dal precedente. Nel caso in cui l'orientamento
scelto sia effettivamente un nuovo orientamento, con il metodo Invalidate ereditato da TControl, viene ridisegnata
completamente l'immagine del componente. Cambiando l'orientamento, cambiano anche le dimensioni dell'oggetto,
che devono quindi essere modificate. Di cio' si occupa il metodo SetRealDimension che verra' introdotto piu' avanti.Per finire con la proprieta' Orientation, dobbiamo pubblicarla e definire un valore di default. Nella sezione published inseriamo la riga property Orientation: TOrientation read _Orientation write SetOrientation;Scrivere read _Orientation significa che il contenuto di Orientation viene letto direttamente dalla variabile privata _Orientation. Scrivere write SetOrientation, invece, significa usare la procedura SetOrientation per modificare il valore di Orientation (la stessa cosa poteva essere fatta anche con read). Ovviamente, il parametro accettato da SetOrientation deve essere dello stesso tipo della proprieta'. Il valore di default lo possiamo indicare nel costruttore che va quindi ridefinito. Nella sezione public inseriamo constructor Create(AOwner: TComponent); override;con cui specifichiamo che stiamo ridefinendo il costruttore. L'implementazione del nuovo costruttore e' la seguente:
constructor TFeelingArrow.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
_Orientation := deg_0;
end;
Con inherited Create(AOwner); viene creato il costruttore che abbiamo sovrascritto. E' necessario farlo perche'
servono comunque le inizializzazioni di default.Analogamente procediamo per le altre proprieta', con piccole variazioni. Le coordinate dell'angolo in alto a sinistra vengono pubblicate gia' di default come proprieta' Top e Left, derivate da TControl. Chiamiamo ArrowWidth la distanza in pixel fra la punta della freccia e la coda (incluse). Chiamiamo ArrowHeight la dimensione del lato verso cui e' rivolta la punta della freccia. Per pubblicare le proprieta' ArrowWidth ed ArrowHeight, inserire nella sezione private: _Height: Integer; _Width: Integer; _ArrowWidth: Integer; _ArrowHeight: Integer; procedure SetArrowWidth(v: Integer); procedure SetArrowHeight(v: Integer); procedure SetRealDimension;Il metodo SetRealDimension aggiusta le dimensioni grafiche del componente in base all'orientamento della freccia. Ad esempio, se la freccia e' orientata verso l'alto (90 gradi), allora la altezza reale del componente deve coincidere con la larghezza della freccia, mentre la larghezza reale del componente deve coincidere con la altezza della freccia (vedere definizione di ArrowHeight e di ArrowWidth). Dal momento che l'altezza e la larghezza del componente devono essere regolate in base ad ArrowHeight ed ArrowWidth, si rende necessario nascondere le proprieta' di default Height e Width, che verranno modificate automaticamente. Vengono nascoste con un piccolo "trucco": si ridichiarano sia la proprieta' Height che la Width come "read only", redirigendo la lettura alle variabili private _Height e _Width. Nella sezione published: property ArrowWidth: Integer read _ArrowWidth write SetArrowWidth; property ArrowHeight: Integer read _ArrowHeight write SetArrowHeight; property Height: Integer read _Height; property Width: Integer read _Width;Essendo sia Height che Width a sola lettura, non appariranno nell'Object Inspector. All'interno del costruttore: _ArrowWidth := 100; _Width := 100; inherited Width := _Width; _ArrowHeight := 49; _Height := 49; inherited Height := _Height;Dal momento che devono variare le dimensioni reali dell'oggetto, bisogna ricorrere alle proprieta' Height e Width originarie usando la parola chiave inherited. Se non lo facessimo, le dimensioni del componente non potrebbero variare. A 0 gradi, come a 180, l'altezza reale coincide con quella della freccia e cosi' anche per la larghezza. Infine nella parte di implementazione:
procedure TFeelingArrow.SetRealDimension;
begin
{Aggiusto la reale altezza e larghezza del componente in base
all'orientamento}
case _Orientation of
deg_0, deg_180: begin
Height := _ArrowHeight;
Width := _ArrowWidth;
end;
deg_90, deg_270: begin
Height := _ArrowWidth;
Width := _ArrowHeight;
end;
end;
inherited Width := _Width;
inherited Height := _Height;
end;
procedure TFeelingArrow.SetArrowWidth(v:integer);
begin
if (_ArrowWidth <> v) and (v > _ArrowHeight) then begin
_ArrowWidth := v;
SetRealDimension;
Invalidate;
end;
end;
procedure TFeelingArrow.SetArrowHeight(v:integer);
begin
if (_ArrowHeight <> v) and (_ArrowWidth > v) then begin
_ArrowHeight := v;
SetRealDimension;
Invalidate;
end;
end;
Le condizioni "(v > _ArrowHeight)" ed "(_ArrowWidth > v)" ci assicurano che esista sempre una coda "decente" per la
freccia.Rimangono da sistemare il colore di fondo ed il colore del bordo della freccia. Il disegno del componente avviene sul canvas del componente. Le funzioni definite nella classe TCanvas (da cui discende il canvas) usano due oggetti di tipo TPen e TBrush. TPen serve a disegnare i bordi (penna), mentre TBrush serve a disegnare in genere il fondo (pennello). Ad esempio, nel disegno di un rettangolo, l'oggetto di tipo TPen viene usato per definire le proprieta' del bordo, mentre l'oggetto di tipo TBrush viene usato per definire le proprieta' della parte interna (riempimento del rettangolo). Ancora un esempio, il metodo TCanvas.FillRect usa il pennello (TBrush) corrente. Questa volta, non abbiamo dei tipi semplici come proprieta', ma degli oggetti. Il procedimento di creazione della proprieta' cambia un po'. Innanzitutto, gli oggetti contenenti la penna ed il pennello li abbiamo gia' all'interno del Canvas, ereditato da TGraphicsControl. Ogni volta che si cambiano le proprieta' di penna e pennello, la freccia deve essere ridisegnata. Sia TPen che TBrush prevedono l'evento OnChange che si attiva quando vengono modificati. Basta quindi modificare la gestione dell'evento assegnando una funzione che si occupa di invalidare la grafica del componente. Nella sezione private, aggiungiamo procedure SetBrush(v: TBrush); procedure SetPen(v: TPen); function GetPen: TPen; function GetBrush: TBrush; procedure ChangeEvent(Sender: TObject);Le due funzioni Set hanno la stessa funzione vista in precedenza con le altre proprieta'. Le due funzioni Get si rendono necessarie perche', nella pubblicazione della penna e del pennello non si riesce ad ottenere direttamente le proprieta' Canvas.Pen e Canvas.Brush. La procedura ChangeEvent non fa altro che chiamare Invalidate e serve da gestore degli eventi onChange. Ecco l'implementazione dei 5 metodi:
procedure TFeelingArrow.ChangeEvent(Sender: TObject);
begin
{Metodo pensato per gli eventi di cambiamento di proprieta' che
comportano il ridisegno del componente}
Invalidate;
end;
function TFeelingArrow.GetPen: TPen;
begin
Result := Canvas.Pen;
end;
function TFeelingArrow.GetBrush: TBrush;
begin
Result := Canvas.Brush;
end;
procedure TFeelingArrow.SetBrush(v: TBrush);
begin
Canvas.Brush.Assign(v);
end;
procedure TFeelingArrow.SetPen(v: TPen);
begin
Canvas.Pen.Assign(v);
end;
Nelle due funzioni Set, attenzione al fatto che viene chiamato il metodo Assign che non distrugge l'oggetto
gia' esistente, ma ne riassegna i campi in base a quelli dell'oggetto passato.L'assegnazione dell'evento onChange va fatta nel costruttore: Canvas.Pen.OnChange := ChangeEvent; Canvas.Brush.OnChange := ChangeEvent;Infine, la pubblicazione del pennello e della penna avviene nella sezione published con le seguenti righe: property Pen: TPen read GetPen write SetPen; property Brush: TBrush read GetBrush write SetBrush;Notare come questa volta siano stati usati dei metodi anche per la lettura della variabile. Disegno del componente Avendo definito tutte le proprieta' del componente freccia, abbiamo tutto cio' che serve a disegnarlo. Per disegnare il componente si deve sovrascrivere il metodo Paint derivato da TGraphicsControl. L'implementazione standard, infatti non fa niente. Nella sezione Protected, inseriamo la dichiarazione di sovrascrittura procedure Paint; override;con questa implementazione:
procedure TFeelingArrow.Paint;
begin
case _Orientation of
deg_0: Draw0;
deg_90: Draw90;
deg_180: Draw180;
deg_270: Draw270;
end;
end;
Per non creare un metodo troppo lungo e per facilitarne la lettura, il disegno e' stato spostato nei quattro metodi
"Draw", uno per ogni angolo possibile.Nella sezione private, inseriamo le seguenti dichiarazioni: procedure Draw0; procedure Draw90; procedure Draw180; procedure Draw270;la cui implementazione sara':
procedure TFeelingArrow.Draw0;
var a: array[1..7] of TPoint;
begin
a[1].X := _ArrowWidth - 1; a[1].Y := Round(_ArrowHeight/2);
a[2].X := a[1].X - a[1].Y; a[2].Y := 0;
a[3].X := a[2].X; a[3].Y := Round(_ArrowHeight/4);
a[4].X := 0; a[4].Y := a[3].Y;
a[5].X := 0; a[5].Y := 3*a[3].Y;
a[6].X := a[2].X; a[6].Y := a[5].Y;
a[7].X := a[2].X; a[7].Y := _ArrowHeight - 1;
Canvas.Polygon(a);
end;
procedure TFeelingArrow.Draw90;
var a: array[1..7] of TPoint;
begin
a[1].X := Round(_ArrowHeight/2); a[1].Y := 0;
a[2].X := 0; a[2].Y := a[1].X;
a[3].X := Round(_ArrowHeight/4); a[3].Y := a[1].X;
a[4].X := a[3].X; a[4].Y := _ArrowWidth - 1;
a[5].X := 3*a[3].X; a[5].Y := a[4].Y;
a[6].X := a[5].X; a[6].Y := a[1].X;
a[7].X := _ArrowHeight - 1; a[7].Y := a[1].X;
Canvas.Polygon(a);
end;
procedure TFeelingArrow.Draw180;
var a: array[1..7] of TPoint;
begin
a[1].X := 0; a[1].Y := Round(_ArrowHeight/2);
a[2].X := a[1].Y; a[2].Y := _ArrowHeight - 1;
a[3].X := a[1].Y; a[3].Y := 3*Round(_ArrowHeight/4);
a[4].X := _ArrowWidth - 1; a[4].Y := a[3].Y;
a[5].X := a[4].X; a[5].Y := Round(_ArrowHeight/4);
a[6].X := a[1].Y; a[6].Y := a[5].Y;
a[7].X := a[1].Y; a[7].Y := 0;
Canvas.Polygon(a);
end;
procedure TFeelingArrow.Draw270;
var a: array[1..7] of TPoint;
begin
a[1].X := Round(_ArrowHeight/2); a[1].Y := _ArrowWidth - 1;
a[2].X := _ArrowHeight - 1; a[2].Y := a[1].Y - a[1].X;
a[3].X := 3*Round(_ArrowHeight/4); a[3].Y := a[2].Y;
a[4].X := a[3].X; a[4].Y := 0;
a[5].X := Round(_ArrowHeight/4); a[5].Y := 0;
a[6].X := a[5].X; a[6].Y := a[3].Y;
a[7].X := 0; a[7].Y := a[3].Y;
Canvas.Polygon(a);
end;
Per come e' stato implementato il componente, il Canvas contiene gia' il pennello e la penna corretti,
quindi non serve altro che disegnare il poligono rappresentante la freccia. Il metodo Polygon, definito
nell'istanza di TCanvas, crea un poligono avente il bordo con le proprieta' della penna e l'interno con le
proprieta' del pennello.
Inserimento di eventi Molti eventi sono gia' definiti in TControl e basta solo renderli disponibili tramite la sezione published. Inseriamo le seguenti linee nella sezione published:
property OnClick; {click del mouse}
property OnDblClick; {doppio click del mouse}
property OnMouseEnter; {il mouse comincia a passare sulla freccia}
property OnMouseLeave; {il mouse non e' piu' sopra la freccia}
property OnMouseMove; {il mouse si sta muovendo sulla freccia}
property OnMouseDown; {pulsante del mouse premuto sulla freccia}
property OnMouseUp; {pulsante del mouse non piu' premuto sulla freccia}
Pubblicando le proprieta', queste diventano automaticamente disponibili ed utilizzabili come in tutti gli altri
componenti gia' esistenti.
Inserimento immagine per la "Component palette" Ogni componente nella Component palette ha un'immagine con cui e' faclimente distinguibile dagli altri. Se non esiste un'immagine appropriata, ne viene usata una di default. La stessa cosa avviene per i nuovi componenti creati. Assegnare un'immagine e' semplice e comporta i seguenti passi:
Registrazione del componente La registrazione del componente avviene in modo semplice. Basta definire all'interno dell'unita' la procedura Register che chiama la procedura RegisterComponents come gia' visto all'inizio con lo scheletro del componente. RegisterComponents accetta come parametri la pagina dove inserire il componente nella Component palette (nel codice mostrato e' "FeelingLinux") ed un elenco di componenti da inserire in tale pagina (in questo caso abbiamo un solo componente, ovvero "FeelingArrow"). ![]() fig.3 "Prima fase della registrazione del componente" Dal menu, scegliere "Component" -> "Install Component..." (figura 3). Apparira' la schermata di installazione, dove richiede il nome dell'unita' contenente il componente, il path dove trovare i file necessari (dovrebbe essere automaticamente aggiornato), il nome del package dove inserire il componente ed un commento al package. Per installare il componente in un nuovo package, basta cambiare pagina cliccando sul tab "Into new package". ![]() fig.4 "Seconda fase della registrazione del componente" Dando l'Ok, si arriva al form di gestione del package (figura 4). Si puo' notare come sia stata aggiunta la unit con il codice della freccia. Selezionando Install, viene compilato il package ed il componente viene inserito all'interno della palette dei componenti di Kylix. Se il componente gia' era stato inserito, si dovra' selezionare Compile e l'unica variazione sara' nel comportamento dell'oggetto (rispecchiante le modifiche apportate). Il codice completo della unit "QFeelingArrow" Come riepilogo finale, ecco il codice completo della unit, contenente tutti i pezzi di codice gia' visti. Manca solo l'immagine del componente, che comunque verra' inclusa in un package FeelingLinux di prossima realizzazione.
{
Author: Gabriele Giansante (c) 2002
Package: FeelingLinux
Component: TFeelingArrow
License: Do whatever you want with this code,
but specify this copyright notes.
}
unit QFeelingArrow;
interface
uses
SysUtils, Types, Classes, QGraphics, QControls, QForms, QDialogs;
type
TOrientation = (deg_0,deg_90,deg_180,deg_270);
TFeelingArrow = class(TGraphicControl)
private
_Height: Integer;
_Width: Integer;
_ArrowWidth: Integer;
_ArrowHeight: Integer;
_Orientation: TOrientation;
procedure SetArrowWidth(v: Integer);
procedure SetArrowHeight(v: Integer);
procedure SetRealDimension;
procedure SetOrientation(v: TOrientation);
procedure SetBrush(v: TBrush);
procedure SetPen(v: TPen);
function GetPen: TPen;
function GetBrush: TBrush;
procedure ChangeEvent(Sender: TObject);
procedure Draw0;
procedure Draw90;
procedure Draw180;
procedure Draw270;
protected
procedure Paint; override;
public
constructor Create(AOwner: TComponent); override;
published
property Orientation: TOrientation read _Orientation write SetOrientation;
property ArrowWidth: Integer read _ArrowWidth write SetArrowWidth;
property ArrowHeight: Integer read _ArrowHeight write SetArrowHeight;
property Pen: TPen read GetPen write SetPen;
property Brush: TBrush read GetBrush write SetBrush;
property Height: Integer read _Height;
property Width: Integer read _Width;
property OnClick;
property OnDblClick;
property OnMouseEnter;
property OnMouseLeave;
property OnMouseMove;
property OnMouseDown;
property OnMouseUp;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('FeelingLinux', [TFeelingArrow]);
end;
constructor TFeelingArrow.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
{Con l'orientamento a 0 gradi la larghezza reale coincide con la larghezza
della freccia e cosi' anche per l'altezza}
_Orientation := deg_0;
_ArrowWidth := 100;
_Width := 100;
_ArrowHeight := 49;
_Height := 49;
inherited Height := _Height;
inherited Width := _Width;
Canvas.Pen.OnChange := ChangeEvent;
Canvas.Brush.OnChange := ChangeEvent;
end;
procedure TFeelingArrow.Paint;
begin
case _Orientation of
deg_0: Draw0;
deg_90: Draw90;
deg_180: Draw180;
deg_270: Draw270;
end;
end;
procedure TFeelingArrow.Draw0;
var a: array[1..7] of TPoint;
begin
a[1].X := _ArrowWidth - 1; a[1].Y := Round(_ArrowHeight/2);
a[2].X := a[1].X - a[1].Y; a[2].Y := 0;
a[3].X := a[2].X; a[3].Y := Round(_ArrowHeight/4);
a[4].X := 0; a[4].Y := a[3].Y;
a[5].X := 0; a[5].Y := 3*a[3].Y;
a[6].X := a[2].X; a[6].Y := a[5].Y;
a[7].X := a[2].X; a[7].Y := _ArrowHeight - 1;
Canvas.Polygon(a);
end;
procedure TFeelingArrow.Draw90;
var a: array[1..7] of TPoint;
begin
a[1].X := Round(_ArrowHeight/2); a[1].Y := 0;
a[2].X := 0; a[2].Y := a[1].X;
a[3].X := Round(_ArrowHeight/4); a[3].Y := a[1].X;
a[4].X := a[3].X; a[4].Y := _ArrowWidth - 1;
a[5].X := 3*a[3].X; a[5].Y := a[4].Y;
a[6].X := a[5].X; a[6].Y := a[1].X;
a[7].X := _ArrowHeight - 1; a[7].Y := a[1].X;
Canvas.Polygon(a);
end;
procedure TFeelingArrow.Draw180;
var a: array[1..7] of TPoint;
begin
a[1].X := 0; a[1].Y := Round(_ArrowHeight/2);
a[2].X := a[1].Y; a[2].Y := _ArrowHeight - 1;
a[3].X := a[1].Y; a[3].Y := 3*Round(_ArrowHeight/4);
a[4].X := _ArrowWidth - 1; a[4].Y := a[3].Y;
a[5].X := a[4].X; a[5].Y := Round(_ArrowHeight/4);
a[6].X := a[1].Y; a[6].Y := a[5].Y;
a[7].X := a[1].Y; a[7].Y := 0;
Canvas.Polygon(a);
end;
procedure TFeelingArrow.Draw270;
var a: array[1..7] of TPoint;
begin
a[1].X := Round(_ArrowHeight/2); a[1].Y := _ArrowWidth - 1;
a[2].X := _ArrowHeight - 1; a[2].Y := a[1].Y - a[1].X;
a[3].X := 3*Round(_ArrowHeight/4); a[3].Y := a[2].Y;
a[4].X := a[3].X; a[4].Y := 0;
a[5].X := Round(_ArrowHeight/4); a[5].Y := 0;
a[6].X := a[5].X; a[6].Y := a[3].Y;
a[7].X := 0; a[7].Y := a[3].Y;
Canvas.Polygon(a);
end;
procedure TFeelingArrow.ChangeEvent(Sender: TObject);
begin
{Metodo pensato per gli eventi di cambiamento di proprieta' che
comportano il ridisegno del componente}
Invalidate;
end;
procedure TFeelingArrow.SetOrientation(v: TOrientation);
begin
if _Orientation <> v then begin
_Orientation := v;
SetRealDimension;
Invalidate;
end;
end;
procedure TFeelingArrow.SetRealDimension;
begin
{Aggiusto la reale altezza e larghezza del componente in base
all'orientamento}
case _Orientation of
deg_0, deg_180: begin
_Height := _ArrowHeight;
_Width := _ArrowWidth;
end;
deg_90, deg_270: begin
_Height := _ArrowWidth;
_Width := _ArrowHeight;
end;
end;
inherited Height := _Height;
inherited Width := _Width;
end;
procedure TFeelingArrow.SetArrowWidth(v:integer);
begin
if (_ArrowWidth <> v) and (v > _ArrowHeight) then begin
_ArrowWidth := v;
SetRealDimension;
Invalidate;
end;
end;
procedure TFeelingArrow.SetArrowHeight(v:integer);
begin
if (_ArrowHeight <> v) and (_ArrowWidth > v) then begin
_ArrowHeight := v;
SetRealDimension;
Invalidate;
end;
end;
function TFeelingArrow.GetPen: TPen;
begin
Result := Canvas.Pen;
end;
function TFeelingArrow.GetBrush: TBrush;
begin
Result := Canvas.Brush;
end;
procedure TFeelingArrow.SetBrush(v: TBrush);
begin
Canvas.Brush.Assign(v);
end;
procedure TFeelingArrow.SetPen(v: TPen);
begin
Canvas.Pen.Assign(v);
end;
end.
|
||
(c) 1999-2006
|