Komponenter i Delphi - 1. del
I denne artikelserie vil jeg beskrive hvordan man kan lave sine egne komponenter i Delphi. Først vil jeg dog give en lille forklaring på hvad man normalt forstår ved et komponent (også kaldet teori :-), og derefter gå i gang med et rigtigt komponent.
Et komponent kan nok bedst beskrives som
en klasse der kan fremstå som et
visuelt objekt i Form Designeren. Det betyder altså at et komponent bliver vist
som et ikon på komponent-paletten, der så kan lægges på en form. Komponenter
kommer i tre varianter:
Usynlige (f.eks. en
TOpenDialog
),
grafiske (f.eks. en label) og
komponenter med et vindue (f.eks.
en edit-boks).
Du kan eventuelt nu
springe ned til den næste
overskrift hvis du ikke er interesseret i resten af teorien! :-)
For at forklare det lidt nærmere, så er et usynligt komponent normalt
repræsenteret som et ikon på formen, som man så kan vælge og redigere
egenskaberne for med
Object Inspector. Når man kører programmet så kan
man ikke se komponenten, med mindre komponenten opretter en eller anden form
for vindue selv (f.eks. en
TOpenDialog
). Faktisk kunne et usynligt
komponent sagtens undværes og erstattes med et objekt man så kan
Create
i run-time, men det er normalt nemmere at bruge et usynligt
komponent.
Forskellen på et grafisk komponent og et komponent med vindue, er at et
grafisk komponent ikke har sit eget
Window Handle, mens et komponent med
vindue har. Og hvad forskel gør det så? Jo, et
Window Handle optager
nogen system ressourcer og gør normalt også komponenten langsommere, til gengæld
så er det faktisk også den eneste måde man kan få Windows til at sende messages
(events) til komponenten! Det kan så måske lyde lidt underligt, at når en label
ikke har et Window Handle, hvordan kan man så give en f.eks. en
OnClick
event? Der skal så lige siges at et grafisk komponent på en
måde alligevel har er Window Handle, det deler nemlig handle med det komponent
det er placeret på (
parent
)! Derfor kan Delphi fange
Click
messages der bliver sendt til eks. formen og så tjekke om de
skulle ligge inden for et område hvor der ligger en label. Hvis klikket så gør
det, så bliver eventen bare sendt videre til labelen.
Men nu hastigt videre til et egentligt eksempel: Et komponent der kan
konvertere mellem forskellige datatyper! (wohoo! :-) Det skal være et usynligt
komponent med egenskaber for
String
,
Integer
og
Real
værdier. Når man ændrer værdien af en af egenskaberne bliver
de to andre også ændret for at reflektere den nye værdi. Hvis man sætter værdien
af
String
og den nye værdi ikke kan konverteres til et tal, så
kommer der bare til at stå 0 i de to andre egenskaber. Hvis man skriver et tal
som f.eks. 1,37 ind i
Real
egenskaben bliver det selvfølgelig
konverteret til en
String
og i
Integer
feltet kommer
der til at stå den afrundede værdi af tallet.
Først skal du selvfølgelig (surprise) have Delphi kørende. Det vil være en
god ide at lukke et evt. projekt du skulle have åbent, så bliver det nemmere at
arbejde, så:
File ->
New ->
Application. Når du
har et helt frisk projekt, så vælger du
Component menuen og
New
Component.
Nu kommer allerede den første (mere eller mindre) svære beslutning. Du skal
nemlig vælge hvilken klasse dit nye komponent skal nedarve fra. I dette tilfælde
skal det være
TComponent
det nedarver fra, det er nemlig det
alle usynlige komponenter nedarver fra. Så i den øverste boks
Ancestor
vælger du
TComponent
.
Nu skal vi finde ud af hvad komponenten skal hedde, lad os kalde den for
TTypeConverter
, så i den næste boks
Class Name
skriver du altså
TTypeConverter
. I Palette Page feltet skal du
vælge hvilken side på komponent-paletten din nye komponent skal placeres på. Du
kan enten vælge en af dem der allerede findes, eller du kan skrive navnet på en
ny side som så automatisk vil blive lavet. I
Unit File Name feltet skal
du skrive filnavnet for den nye komponent.
Search Path kan du bare lade
være som den er. Klik
Ok.
Nu laver Delphi en ny unit med en klasse-deklaration og en
Register
procedure. Det er her du skal skrive koden til dit
komponent. Det første vi skal gøre er at lave nogen felt-variabler for de
forskellige egenskaber. Det gør man i private
sektionen,
så der skal du skrive:
type
TTypeConverter = class(TComponent)
private
{ Private declarations }
FStringVal: string;
FIntVal: integer;
FRealVal: Extended;
Læg mærke til at jeg har skrevet et
F
foran alle navnene.
Det har jeg gjort af to grunde: For det første er det god praksis altid at kalde
felt-variabler noget med et
F
først, og for det andet er det for at
kunne kende dem fra egenskaber.
Nu skal vi have deklareret nogen procedurer der bliver kaldt når man ændrer
en egenskab. De skal også stå i
private
sektionen. Tilføj
dette til
private
:
type
TTypeConverter = class(TComponent)
private
{ Private declarations }
. . .
procedure SetStringVal(Value: string);
procedure SetIntVal(Value: integer);
procedure SetRealVal(Value: Extended);
Nu mangler vi kun de egentlig egenskaber. De skal stå i
published
sektionen for at man kan se dem i Object Inspector
(Delphi genererer kun Run Time Type Information for properties der er
Published
). En egenskab skal altid deklareres med enten en
read
metode, en
write
metode eller begge
dele. Hvis man kun deklarerer en
Read
metode bliver
egenskaben read-only. Hvis man kun deklarerer en
Write
metode bliver egenskaben write-only (hvad man så end skal bruge det til). Men
til noget kode:
type
TTypeConverter = class(TComponent)
. . .
published
{ Published declarations }
property StringVal: string
read FStringVal
write SetStringVal;
property IntVal: integer
read FIntVal
write SetIntVal;
property RealVal: Extended
read FRealVal
write SetRealVal;
end;
protected
og
public
sektionerne skal
vi ikke bruge, så dem kan vi sagtens slette. Vi skal også have skrevet nogen
procedurer til det vi har deklareret. Hvis du har Delphi 4 eller nyerer, så kan
du trykke
Ctrl+Shift+C for at aktivere
Class Completion. Den
finder automatisk alle metoder og egenskaber i alle klasser i den unit man er i,
og generer den kode der minimum skal til for at man kan kompilere. Hvis du har
Delphi 3 eller ældre så bliver du nødt til selv at skrive den nødvendige kode.
Her er et skelet:
{ TTypeConverter }
procedure TTypeConverter.SetStringVal(Value: string);
begin
end;
procedure TTypeConverter.SetRealVal(Value: Extended);
begin
end;
procedure TTypeConverter.SetIntVal(Value: integer);
begin
end;
Det skal stå i
implementation
delen af unit'en, under
Register
proceduren. Nu kan vi så endelig fylde noget kode
på. I hver af procedurerne skal der stå kode til at tildele
Value
parameteren til den tilhørende felt-variabel. Desuden skal
Value
parameteren konverteres til de to andre typer. Nu giver jeg koden for
SetStringVal
proceduren (som er den mest tricky) og så kan du
sikkert selv regne resten ud.
procedure TTypeConverter.SetStringVal(Value: string);
begin
// Hvis værdien ikke har ændret sig så er der ingen grund til at
// køre resten af koden.
if FStringVal <> Value then
begin
// Den første er helt ligetil
FStringVal := Value;
// Hvis det ikke lykkes at konvertere til en Real-værdi, så tildel
// i stedet en nul-værdi.
try
FRealVal := StrToFloat(Value);
except
FRealVal := 0;
end;
// Siden vi allerede har gemt en værdi i FRealVal så kan vi bare
// tage den og afrunde den for at få fat i integer-værdien. Hvis
// vi ikke brugte en afrundet værdi for FIntVal så ville den altid
// blive nul for decimal-værdier.
FIntVal := Round(FRealVal);
end;
end;
Nu er vores komponent næsten færdigt. Faktisk mangler der kun et ikon til
det! Til det skal vi bruge Borland's
Image Editor, den kan man normalt
starte fra
Tools menuen i Delphi. I Image Editor skal du vælge
File ->
New ->
Component Resource File. Nu skal
du så vælge
Ressource ->
New ->
Bitmap og lave
et nyt 24 x 24 bitmap i 16 farver. (Det er den størrelse Delphi
bruger.) Dobbeltklik på det nye
Bitmap1 punkt der er kommet for at komme
ind i editoren. Maksimer evt. vinduet, og bruge så
Ctrl+I for at zoome
ind.
Nu kan du tegne et ikon til komponenten, det behøver ikke være den store
kunst, men det er nu en god ide at man får en ide om hvad komponenten gør ud fra
ikonet. Jeg har lavet et ikon hvor der øverst står ABC og neden under står 123,
og så er der pile mellem dem i begge retninger. Luk nu vinduet med bitmappen
men ikke Image Editor! Når du er tilbage i Image Editor, så klik en
enkelt gang på navnet
Bitmap1 (eller højreklik og vælg
Rename) og
omdøb ikonet til
TTYPECONVERTER
(altså komponentens navn med rent
store bogstaver).
Gem nu filen samme sted som uniten med komponenten. Den skal hedde det
samme som uniten, så du skulle nu have
TypeConverter.pas
og
TypeConverter.dcr
i samme mappe. Afslut Image Editor. Du skulle nu
kunne kompilere komponenten. Vælg
Component ->
Install
Component i Delphi. Nu kan du enten vælge at installere komponenten i en
allerede eksisterende pakke, eller at lave en ny pakke. (Hvis du bruger Delphi 1
eller 2 så bliver du ikke spurgt om det, men hvis du bruger Delphi 1 eller 2 så
burde du alligevel opgradere.) Det kan være en meget god ide at lave en ny
pakke, så vælg
Into New Package og find selv på et filnavn til pakken og
en beskrivelse af den. Det kunne f.eks. være "Mine egne komponenter".
Klik nu på
Ok. Du vil sandsynligvis blive spurgt om:
"Package
(indsæt navn her) will be rebuilt. Continiue?"
. Der skal du bare vælge
Yes. Delphi kompilerer nu pakken med dit nye komponent, den burde ikke
give nogen fejl. Hvis den gør, så spørg i forummet. :-) Bagefter skulle den
vise:
"... The following new component(s) have been registered:
TTypeConverter". Prøv at se på komponent-paletten om ikke dit nye komponent
skulle være på den side du valgte i starten. Åbn formen i projektet
(
Shift+F12) og placer komponenten på den. Prøv at lege lidt med at skrive
forskellige værdier for
StringVal
,
IntVal
og
RealVal
.
Du skulle nu have dit første (med mindre du har lavet komponenter før :-)
fungerende komponent! Husk at hvis der er noget der driller, så kan du altid
spørge i foraerne her på Udvikleren. Og der vil selvfølgelig komme flere
artikler om hvordan man laver komponenter! Du kan hente kildekoden til det
komponent jeg har beskrevet i artiklen fra Downloads sektionen her på
Udvikleren.