Inleiding
In dit hoofdstuk zal ik proberen de basis-principes van Object Oriented Programming uit te leggen.
Ik zal dat doen aan de hand van een viertal voorbeelden.
Als uitgangspunt neem ik de uitbreiding van Records naar Classes.
Overigens is dit hoofdstuk bedoeld voor mensen die al wat langer met Delphi programmeren.
In dit hoofdstuk leer je:
- Begrippen
- Eerst Delphi zelf
- Op weg naar een eigen Class
- Overerving
- Polymorfisme
- Encapsulation
- Dynamische componenten
Begrippen
Bij oop (Object Oriented Programming; spreek uit als oep) zijn een aantal begrippen van belang.
- Encapsulation of Inkapseling
- Inheritance of Overerving
- Polymorfism of Polymorfisme
- Class of klasse
- Object of Instantie
Ad. 1: Encapsulation betekent zoveel als dat je de velden kunt verbergen voor de buitenwereld.
Ad. 2: Inheritance betekent dat je van bestaande Classes gebruik kan maken en daar naar eigen believen functionaliteit aan kunt toevoegen.
Ad. 3: Polymorfism betekent dat verschillende methodes toch dezelfde naam kunnen hebben.
Ad. 4: Een Class is niets anders dan de definitie van een object, zeg maar de blauwdruk.
Ad. 5: Een Object is een ‘instantie’ van een Class. Werken doe je met Objecten en niet met Classes. Van een Class kunnen meerdere Objecten bestaan, andersom niet!
De voorbeelden moeten één en ander duidelijk gaan maken. Voor het merendeel maak je van bovenstaande al impliciet gebruik, ervan uitgaande dat je al in Delphi of een andere Windows-taal programmeert.
Eerst Delphi zelf
We gaan eerst eens kijken wat Delphi zelf doet met Classes e.d..
– Start eens een nieuw project in Delphi en kijk naar de code:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
TForm1 = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
end.
Wat hopelijk opvalt is dat Delphi een Class TForm1 heeft aangemaakt. TForm1 stamt af van TForm. Dit betekent dat TForm1 dezelfde eigenschappen en functionaliteit heeft als TForm. Laat dit project maar eens runnen. Je krijgt nu een scherm die je kunt verplaatsen, vergroten, verkleinen, maximaliseren etc., etc..
– Zet eens een button op het form en kijk weer naar de code:
type
TForm1 = class(TForm)
Button1: TButton;
private
{ Private declarations }
public
{ Public declarations }
end;
De button staat in de Class-definitie van TForm1. Dit is nu het wezenlijke van overerving of inheritance: Maak (her)gebruik van bestaande functionaliteit en voeg en implementeer alleen nieuwe functionaliteiten toe, ofwel: ga niet opnieuw het wiel uitvinden.
– Dubbelklik op de Button en voeg de volgende regel toe:
ShowMessage(‘Knop geklikt’);
– Kijk weer naar de code:
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage(‘Knop geklikt’);
end;
De procedure Button1Click is toegevoegd aan de Class-definitie. In de implementation-sectie staat de implementatie van de procedure. Kijk goed naar de heading van de implementatie. Je moet altijd expliciet aangeven van welke Class je iets implementeerd.
– Probeer de opbouw van de code, zoals Delphi die zelf aanmaakt, goed te begrijpen.
– Wanneer je denkt dat je het begrijpt kijk dan eens naar onderstaande code:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
Label1: TLabel;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
function AantalCijfers : Integer;
var a,t : Integer;
begin
a := 0;
for t := 1 to Length(Edit1.Text) do
if Edit1.Text[t] in [‘0’..’9′]
then Inc(a);
AantalCijfers := a;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Label1.Caption := IntToStr(AantalCijfers);
end;
end.
– Voorspel waar de compiler met een foutmelding komt, en welke dat is.
– Geef twee oplossingen voor dit probleem.
Op weg naar een eigen Class
Wanneer we verschillende typen variabelen tot één logisch geheel willen maken, kunnen we in Pascal, en dus ook in Delphi, gebruik maken van Record’s.
Nog logischer zou zijn om ook functionaliteit betreffende die variabelen hierin te kunnen onderbrengen.
Helaas biedt standaard Pascal hiervoor geen mogelijkheden. Delphi echter wel!
In de volgende twee voorbeelden zullen we zien hoe een en ander in zijn werk gaat.
Het eerste voorbeeld gaat uit van de oude vertrouwde record, het tweede gaat uit van een Class-definitie. Beide programma’s doen hetzelfde en hebben dus dezelfde functionaliteit.
De programma’s houden Naam, Adres, Plaats en Leeftijd van personen bij en hebben daarbij de volgende functionaliteit: Naar volgende gaan, naar vorige gaan, indien nodig een nieuwe toevoegen, bewaren op schijf, halen van schijf.
Voorbeeld 1 – Probeer dit programma te doorgronden. Dit moet niet al te veel moeite kosten. – Merk ook op dat in dit programma gebruik wordt gemaakt van een dynamische array. In bovenstaand voorbeeld behoren naast de variabelen Naam, Adres, Plaats en Leeftijd, die we in een Record velden noemen, ook de functionaliteiten Naar volgende, Bewaar op schijd e.d. tot één logisch geheel. In het nu volgende voorbeeld gaan we dit voor elkaar maken d.m.v. een Class-definitie. – Bekijk de volgende type-declaraties: type Maar in plaats van alles vervolgens onder te brengen in losse variabelen, procedures en functies, brengen we nu alles onder in een Class-definitie. De Class TNAW bezit alles wat we willen: alle velden, alle namen en alle functionaliteit. Uiteraard is het zo dat we de definitie nog wel moeten uitwerken, ofwel moeten implementeren. En dat gaat analoog aan hoe Delphi dat doet met de opbouw van een venster! Laten we bijvoorbeeld gaan kijken naar de implementatie van de procedure Nieuw. procedure TNAW.Nieuw; Vandaar procedure TNAW.Nieuw. Binnen de procedure kunnen we vrij gebruik maken van alle velden en functionaliteiten van de Class. Nadat we de implementatie hebben gemaakt kunnen we de Class gaan gebruiken. Hiervoor dienen we van de Class een Object of Instantie te maken. Dat doen we door allereerst een variabele te definieren van het Class-type, in ons voorbeeld dus van het type TNAW. Var Namen := TNAW.Create; De methode (procedure) Create stamt af van het moeder-Object in Delphi genaamd TObject. Iedere Class stamt hier direct of indirect vanaf. Om het Object of Instantie weer op te ruimen maken we gebruik van de methode Free, ook van TObject. Nu zal het vaak voorkomen dat we bij het maken van een Object meteen een aantal zaken willen regelen. In ons voorbeeld willen we ervoor zorgen dat de variabelen Aantalnamen en Huidigenaam een startwaarde krijgen en dat er een eerste naam tot onze beschikking staat. Met de methode Create gaat dit niet lukken. In Delphi kunnen we dit voor elkaar krijgen door een Constructor-methode te maken die dan in plaats komt van de Create-methode. De aanmaak van een Object wordt in ons voorbeeld dan: Namen := TNAW.Init; Het is overigens toegestaan om aan een Constructor ook parameters mee te geven. Wat dat betreft is een Constructor dus gewoon te beschouwen als een normale procedure. Zo zouden we ook een Destructor kunnen maken die dan in plaats komt van de methode Free. Om, tot slot, gebruik te kunnen maken van de functionaliteit van ons Object, roepen we die functionaliteit aan d.m.v. de variabele gevolgd door een punt met daarachter de naam van de methode, net zoals het gebruik van ieder ander Object in Delphi. Dus bijvoorbeeld Namen.SaveToFile(‘vb2.rec’);. En voor de volledigheid volgt hier het gehele tweede voorbeeld: Voorbeeld 2 interface uses type var implementation var {$R *.DFM} constructor TNAW.Init; procedure TNAW.Nieuw; procedure TNAW.Volgende; procedure TNAW.Vorige; procedure TNAW.LoadFromFile(FileName: ShortString); procedure TNAW.SaveToFile(FileName: ShortString); procedure TForm1.FormCreate(Sender: TObject); procedure TForm1.UpdateScreen; procedure TForm1.Button2Click(Sender: TObject); procedure TForm1.Button1Click(Sender: TObject); procedure TForm1.Edit1Change(Sender: TObject); procedure TForm1.Edit2Change(Sender: TObject); procedure TForm1.Edit3Change(Sender: TObject); procedure TForm1.Edit4Change(Sender: TObject); procedure TForm1.FormActivate(Sender: TObject); procedure TForm1.Button3Click(Sender: TObject); procedure TForm1.Button4Click(Sender: TObject); procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); end. Denk eraan alles weer netjes op te ruimen zoals hierboven gebeurt in de procedure FormClose. Eén van de grote voordelen van het werken met Classes is Overerving. Het enige wat dan nog moet gebeuren is het toevoegen van nieuwe functionaliteit. Zoals reeds eerder opgemerkt, doe je niets anders wanneer je in Delphi een programma schrijft. Het nu volgende voorbeeld is iewat gekunsteld maar is slechts ter illustratie bedoeld. We gaan een Class TVervoerMiddel maken met wat algemene velden en functionaliteiten. Daarna gaan we een aantal Classes hiervan afleiden. We gaan er vanuit dat ieder vervoermiddel een aantal wielen heeft (al zijn dit er misschien nul) en dat een vervoermiddel zich op een bepaalde manier voortbeweegt. Verder willen we bij initialisatie de waarden van de velden meegeven, en willen we methodes hebben om één en ander te kunnen uitlezen. Hier komt de definitie: type TAutoType = (sedan,coupe,stationwagon,cabriolet); Formeel zou achter TVervoerMiddel tussen haakjes TObject moeten staan, maar daar deze Class default is mag dat ook worden weggelaten. De implementatie van de Constructor Init is als volgt: Constructor TVervoerMiddel.Init(aw: integer; srt: TSoort); Nu volgen de twee andere implementaties van TVervoerMiddel: function TVervoerMiddel.Hoe: string; procedure TVervoerMiddel.WatEnHoe; Alleen bij TAuto hebben we meer functionaliteit en moeten we derhalve een eigen WatEnHoe-procedure maken: procedure TAuto.WatEnHoe; procedure TAuto.WatEnHoe; Voor de volledigheid volgt hier het complete programma: Voorbeeld 3 var implementation {$R *.DFM} Constructor TVervoerMiddel.Init(aw: integer; srt: TSoort); function TVervoerMiddel.Hoe: string; procedure TVervoerMiddel.WatEnHoe; procedure TAuto.WatEnHoe; procedure TForm1.FormCreate(Sender: TObject); procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); procedure TForm1.Button1Click(Sender: TObject); procedure TForm1.Button2Click(Sender: TObject); procedure TForm1.Button3Click(Sender: TObject); procedure TForm1.Button4Click(Sender: TObject); end. Hoewel Delphi met het werken met Classes/objecten het hele verhaal pointer-impliciet heeft gemaakt, moet je wel goed in de gaten houden dat een Object-variabele, zoals bv Auto hierboven, niets anders dan een pointer-variabele is! Vandaar… Polymorfisme houdt in dat een methode van het ene object dezelfde naam heeft als een methode van een ander object, met dien verstande dat beide methodes min of meer dezelfde werking hebben. Een ander voorbeeld hebben we gezien in de vorige paragraaf met de methode WatEnHoe die we voor het type TAuto hebben aangepast. Belangrijk hierbij op te merken is dat deze aanpassing een zogenaamde statische vorm heeft. Tijdens het compileren is al bekend hoe de methode WatEnHoe voor de verschillende objecten eruit ziet. Het kan echter ook dynamisch ofwel tijdens run-time uitgemaakt worden. En zolang er sprake is van overerfde objecten kunnen ze allemaal aan 1 object-variabele worden toegekend. Overigens spreken we in het eerste geval van “early-binding”, dus een koppeling tijdens compileren, en in het tweede geval van “late-binding”, een koppeling tijdens de uitvoer. Met het komende voorbeeld begint het dus ergens op te lijken. In het volgende voorbeeld maken we een Class TDier waar we de Class THond en TKat vanaf zullen leiden. De Class TDier zou er als volgt uit kunnen zien: type Verder is de methode WieBenIk statisch en is de methode WatZegIk dynamisch. Om aan te geven dat een methode dynamisch is, dus dat pas tijdens run-time bepaalt moet worden welke implementatie moet worden uitgevoerd, voegen we bij de ouder-klasse (of bron-klasse of root-class) het woordje virtual toe. Bij iedere afstammeling voegen we bij de dynamische methode het woordje override toe. Hier volgen de definitie’s van THond en TKat: THond = class(TDier) Dan volgen nu de implementatie’s van de verschillende classes: procedure TDier.WieBenIk; procedure TDier.WatZegIk; procedure THond.WieBenIk; procedure THond.WatZegIk; procedure TKat.WieBenIk; procedure TKat.WatZegIk; Deze implementatie’s roepen hopelijk geen vragen op, behalve dan dat de woordjes virtual en override alleen maar in de class-definitie voorkomen. Maak nu eens een Delphi-project met een button waarin bovenstaande verwerkt is met een button-Click-Event zoals in het nu volgende uitgewerkte voorbeeld: Voorbeeld 4 – Probeer voordat je dit programma runt eerst de uitkomst te voorspellen Hoe werkt dit nu precies? De methode WieBenIk is in alle klasses statisch, d.i. iedere klasse heeft zijn eigen WieBenIk. De methode WatZegIk is bij TDier dynamisch gemaakt en wordt op dezelfde wijze overgenomen door THond. Bij TKat echter wordt de methode WatZegIk weer statisch. Afstammelingen van TKat kunnen geen dynamische WatZegIk-methode hebben! De variabele Dier in het voorbeeld wordt gedeclareerd als van het type TDier. Dat betekend dat bij iedere aanroep van de methode WieBenIk als antwoord ‘Ik ben een dier’ komt, omdat WieBenIk statisch is gedeclareerd. Dat laatste betekent dat de binding tijdens het compileer-proces plaats vindt (early-binding). De methode WatZegIk is echter dynamisch gedeclareerd wat betekent dat pas tijdens runtime bepaald wordt welke WatZegIk-methode moet worden gebruikt (late binding). Wanneer Dier een instantie van THond is zal WatZegIk als resultaat ‘Ik blaf’ geven. Bij TKat gaat dat verhaal niet meer op, omdat WatZegIk daar weer als statisch is gedeclareerd. Het is een goed gebruik binnen het werken met Classes om op een correcte wijze om te gaan met de scope of zichtbaarheid van velden en methodes. Een Class kent 4 soorten van scope: Alle velden en methodes die onder private worden gedeclareerd zijn alleen zichtbaar in de implementatie van de Class zelf. Het is een goed gebruik alle interne velden en methodes private te declareren. Alle velden en methodes die onder protected worden gedeclareerd zijn alleen zichtbaar in de implementatie van de Class en zijn afgeleiden. Alle velden en methodes die onder public worden gedeclareerd zijn altijd en overal zichtbaar. Wanneer geen gebruik wordt gemaakt van één van de directives wordt dit als public beschouwd. Het gebruik van published heeft te maken met componenten en properties en wordt hier niet verder behandeld. Het probleem van bovenstaande is dat wanneer je e.e.a. in de praktijk wilt testen en je beperkt je daarbij tot één en dezelfde Unit, het hele scope-verhaal de mist in gaat, omdat de scope-regels van de unit preveleren boven die van de Classes. Het volgende voorbeeld laat zien hoe je tijdens run-time componenten kunt aanmaken en weer weggooien. Dit kan erg handig zijn, denk maar bv. aan een report-generator of iets dergelijks. Bij het gebruik van dynamische componenten zijn een aantal zaken waarmee rekening moet worden gehouden: Bij dynamische componenten heb je natuurlijk geen object-inspector tot je beschikking en zul je bovenstaande zaken dus zelf moeten regelen. – Start een nieuw project in Delphi en open het code-venster – Voeg bij de Uses-clausule toe: – Voeg via de Object-inspector de event-handlers FormCreate en FormClose toe – Voeg in het code-venster onder de procedure FormClose de volgende regel toe: – Voeg in het code-venster onder de directive private de volgende regel toe: – Voeg in het code-venster in de event-handler FormCreate de volgende regels toe: – Voeg in het code-venster de event-handler KnopKlik toe: Je hebt nu, tijdens het runnen, een form met een knop erop. Wanneer je op de knop klikt wordt keurig de event-handler KnopKlik uitgevoerd. Omdat je in design-time niets op het form hebt gezet weet Delhi niet dat je tijdens run-time gebruik maakt van controls, vandaar dat je zelf de unit StdCtrls moet toevoegen aan de Uses-clausule. Het woordje Self refereert aan het ouder-object ofwel de container waar de control deel van uit gaat maken. En ondanks het feit dat je dat al aangeeft bij de creatie van de control moet dit ook nog expliciet worden toegekend aan de propertie (eigenschap) Parent. We gaan bovenstaand project uitbreiden met een listbox waarin items zitten waarop gedubbelklikt kan worden en een tiental labeltjes. Om die labeltjes te maken kun je natuurlijk gebruik maken van 10 object-variabelen, maar het kan gelukkig ook met 1. Het enige belangrijke is dan wel dat je kunt/moet bijhouden waar zo’n label zich dan bevindt, bijvoorbeeld voor het vrijmaken van het label. De procedure KnopKlik is in het volgende voorbeeld veranderd. Voorbeeld 5 Probeer bovenstaand programma te doorgronden. Dit verschaft je nu een flexibele techniek om je forms te kunnen bouwen, bijvoorbeeld je eigen report-generator gekoppel aan een of andere database o.i.d.. Veel van de voorbeelden komen uit een van de boeken van Marco Cantù, de Delphi-Goeroe! Ik hoop dat dit, misschien wat taaie, hoofdstuk je op weg heeft geholpen naar een nieuwe manier van programmeren en meer kennis van wat er zoal in de achtergrond van Delphi gebeurt.
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;type
TNAW = Record
Naam, Adres, Plaats : ShortString;
Leeftijd : Byte;
end;
TNamen = Array of TNAW;
TForm1 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
Edit4: TEdit;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
procedure FormCreate(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Edit1Change(Sender: TObject);
procedure Edit2Change(Sender: TObject);
procedure Edit3Change(Sender: TObject);
procedure Edit4Change(Sender: TObject);
procedure FormActivate(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
private
{ Private declarations }
procedure UpdateScreen;
public
{ Public declarations }
end;var
Form1: TForm1;implementationvar
Namen : TNamen;
NamenFile : File of TNAW;
AantalNamen,HuidigeNaam : Integer;{$R *.DFM}procedure TForm1.FormCreate(Sender: TObject);
begin
AantalNamen := 1;
HuidigeNaam := 0;
SetLength(Namen,AantalNamen);
end;procedure TForm1.UpdateScreen;
begin
Edit1.Text := Namen[Huidigenaam].Naam;
Edit2.Text := Namen[Huidigenaam].Adres;
Edit3.Text := Namen[Huidigenaam].Plaats;
Edit4.Text := IntToStr(Namen[Huidigenaam].Leeftijd);
end;procedure TForm1.Button2Click(Sender: TObject);
begin
Inc(HuidigeNaam);
If HuidigeNaam = AantalNamen Then
Begin
Inc(AantalNamen);
SetLength(Namen,AantalNamen);
Edit1.Text := ”;
Edit2.Text := ”;
Edit3.Text := ”;
Edit4.Text := ”;
End
Else UpdateScreen;
end;procedure TForm1.Button1Click(Sender: TObject);
begin
If HuidigeNaam > 0 Then
Begin
Dec(HuidigeNaam);
UpdateScreen;
End
end;procedure TForm1.Edit1Change(Sender: TObject);
begin
Namen[Huidigenaam].Naam := Edit1.Text;
end;procedure TForm1.Edit2Change(Sender: TObject);
begin
Namen[Huidigenaam].Adres := Edit2.Text;
end;procedure TForm1.Edit3Change(Sender: TObject);
begin
Namen[Huidigenaam].Plaats := Edit3.Text;
end;procedure TForm1.Edit4Change(Sender: TObject);
var c : Integer;
begin
Val(Edit4.Text,Namen[Huidigenaam].Leeftijd,c);
end;procedure TForm1.FormActivate(Sender: TObject);
begin
Edit1.SetFocus;
end;procedure TForm1.Button3Click(Sender: TObject);
var f : Integer;
begin
AssignFile(NamenFile,’vb1.rec’);
Rewrite(NamenFile);
For f := 0 To AantalNamen – 1 Do
Write(NamenFile,Namen[f]);
CloseFile(NamenFile);
end;procedure TForm1.Button4Click(Sender: TObject);
var f : Integer;
begin
SetLength(Namen,0);
AssignFile(NamenFile,’vb1.rec’);
Reset(NamenFile);
f := 0;
While not Eof(NamenFile) Do
Begin
SetLength(Namen,Succ(f));
Read(NamenFile,Namen[f]);
Inc(f);
End;
CloseFile(NamenFile);
AantalNamen := f;
HuidigeNaam := 0;
UpdateScreen;
end;end.
TNawRec = Record
Naam, Adres, Plaats : ShortString;
Leeftijd : Byte;
end;
TNAW = class
Namen : Array of TNawRec;
AantalNamen,HuidigeNaam : Integer;
constructor Init;
procedure Nieuw;
procedure Volgende;
procedure Vorige;
procedure SaveToFile(FileName: ShortString);
procedure LoadFromFile(FileName: ShortString);
end;
Het Record uit het vorige voorbeeld bestaat nog steeds.
begin
Inc(AantalNamen);
SetLength(Namen,AantalNamen);
end;
In de aanhef van de procedure moet expliciet worden vermeld bij welke Class de procedure hoort.
Namen: TNAW;
Vervolgens maken we de instantie als volgt:
Dus aan de variabele kennen we toe de naam van de Class gevolgd door .Create.
,omdat Init de naam van onze Constructor is.
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
TNawRec = Record
Naam, Adres, Plaats : ShortString;
Leeftijd : Byte;
end;
TNAW = class
Namen : Array of TNawRec;
AantalNamen,HuidigeNaam : Integer;
constructor Init;
procedure Nieuw;
procedure Volgende;
procedure Vorige;
procedure SaveToFile(FileName: ShortString);
procedure LoadFromFile(FileName: ShortString);
end;
TForm1 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
Edit4: TEdit;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
procedure FormCreate(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Edit1Change(Sender: TObject);
procedure Edit2Change(Sender: TObject);
procedure Edit3Change(Sender: TObject);
procedure Edit4Change(Sender: TObject);
procedure FormActivate(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
procedure UpdateScreen;
public
{ Public declarations }
end;
Form1: TForm1;
Namen : TNAW;
begin
AantalNamen := 1;
HuidigeNaam := 0;
SetLength(Namen,AantalNamen);
end;
begin
Inc(AantalNamen);
SetLength(Namen,AantalNamen);
end;
begin
Inc(HuidigeNaam);
If HuidigeNaam = AantalNamen Then
Nieuw;
end;
begin
If HuidigeNaam > 0 Then
Dec(HuidigeNaam);
end;
var
f : Integer;
NamenFile: File of TNawRec;
begin
SetLength(Namen,0);
AssignFile(NamenFile,’vb2.rec’);
Reset(NamenFile);
f := 0;
While not Eof(NamenFile) Do
Begin
SetLength(Namen,Succ(f));
Read(NamenFile,Namen[f]);
Inc(f);
End;
CloseFile(NamenFile);
AantalNamen := f;
HuidigeNaam := 0;
end;
var
f : Integer;
NamenFile: File of TnawRec;
begin
AssignFile(NamenFile,’vb2.rec’);
Rewrite(NamenFile);
For f := 0 To AantalNamen – 1 Do
Write(NamenFile,Namen[f]);
CloseFile(NamenFile);
end;
begin
Namen := TNAW.Init;
end;
begin
Edit1.Text := Namen.Namen[Namen.Huidigenaam].Naam;
Edit2.Text := Namen.Namen[Namen.Huidigenaam].Adres;
Edit3.Text := Namen.Namen[Namen.Huidigenaam].Plaats;
Edit4.Text := IntToStr(Namen.Namen[Namen.Huidigenaam].Leeftijd);
end;
begin
Namen.Volgende;
UpdateScreen;
end;
begin
Namen.Vorige;
UpdateScreen;
end;
begin
Namen.Namen[Namen.Huidigenaam].Naam := Edit1.Text;
end;
begin
Namen.Namen[Namen.Huidigenaam].Adres := Edit2.Text;
end;
begin
Namen.Namen[Namen.Huidigenaam].Plaats := Edit3.Text;
end;
var c : Integer;
begin
Val(Edit4.Text,Namen.Namen[Namen.Huidigenaam].Leeftijd,c);
end;
begin
Edit1.SetFocus;
end;
begin
Namen.SaveToFile(‘vb2.rec’);
end;
begin
Namen.LoadFromFile(‘vb2.rec’);
UpdateScreen;
end;
begin
ShowMessage(‘Closing…’);
Namen.Free;
end;Overerving
Iedere Class stamt af van een andere Class en erft daarmee meteen alle functionaliteit van de Ouder-Class.
TSoort = (rijden, vliegen, varen);
TVervoerMiddel = class
Aantal_wielen: integer;
Soort: TSoort;
constructor Init(aw: integer; srt: TSoort);
function Hoe: string;
procedure WatEnHoe;
end;
Nu gaan we een afgeleide Class TAuto maken. De nieuwe functionaliteit is het soort auto, coupe, sedan e.d..
TAuto = class(TVervoerMiddel)
Auto_type: TAutoType;
procedure WatEnHoe;
end;
Om aan te geven dat TAuto afstamt van TVervoerMiddel staat achter het woord Class tussen haakjes de ouder-Class, TVervoerMiddel.
begin
Aantal_Wielen := aw;
Soort := srt
end;
Dit is overigens vaak de manier om aan velden een waarde toe te kennen, dus d.m.v. een Constructor of een procedure!
begin
Case soort of
rijden: Hoe := ‘rijdt’;
vliegen: Hoe := ‘vliegt’;
varen: Hoe := ‘vaart’;
end;
end;
begin
ShowMessage(‘Dit vervoermiddel ‘ + Hoe + ‘ en heeft ‘ + inttostr(Aantal_wielen) + ‘ wielen.’);
end;
var tp: string;
begin
Case Auto_type of
sedan: tp := ‘sedan.’;
coupe: tp := ‘coupe.’;
stationwagon: tp := ‘stationwagon.’;
cabriolet: tp := ‘cabriolet.’
else
tp := ‘onbekend.’
end;
ShowMessage(‘Dit vervoermiddel ‘ + Hoe
+ ‘ en heeft ‘ + inttostr(Aantal_wielen) + ‘ wielen, en is van het type ‘+ tp);
end;
Met name de laatste regen (ShowMessage) lijkt iewat dubbelop, en dat is ook zo.
We zouden dit als volgt kunnen oplossen:
var tp: string;
begin
Case Auto_type of
sedan: tp := ‘sedan.’;
coupe: tp := ‘coupe.’;
stationwagon: tp := ‘stationwagon.’;
cabriolet: tp := ‘cabriolet.’
else
tp := ‘onbekend.’
end;
inherited WatEnHoe;
ShowMessage(‘Deze auto is van het type ‘ + tp);
end;
Het woord inherited zoekt naar de eerst voorgaande versie van WatEnHoe in de Class-hierarchie.
Het is natuurlijk wel noodzakelijk dat deze bestaat!
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
TSoort = (rijden, vliegen, varen);
TVervoerMiddel = class
Aantal_wielen: integer;
Soort: TSoort;
constructor Init(aw: integer; srt: TSoort);
function Hoe: string;
procedure WatEnHoe;
end;
TAutoType = (sedan,coupe,stationwagon,cabriolet);
TAuto = class(TVervoerMiddel)
Auto_type: TAutoType;
procedure WatEnHoe;
end;
TFiets = class(TVervoerMiddel)
end;
TVliegtuig = class(TVervoerMiddel)
end;
TBoot = class (TVervoerMiddel)
end;
TForm1 = class(TForm)
Button1: TButton; //met caption ‘Auto’
Button2: TButton; //met caption ‘Fiets’
Button3: TButton; //met caption ‘Vliegtuig’
Button4: TButton; //met caption ‘Boot’
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
Form1: TForm1;
var
Auto: TAuto;
Fiets: TFiets;
Vliegtuig: TVliegtuig;
Boot: TBoot;
begin
Aantal_Wielen := aw;
Soort := srt
end;
begin
Case soort of
rijden: Hoe := ‘rijdt’;
vliegen: Hoe := ‘vliegt’;
varen: Hoe := ‘vaart’;
end;
end;
begin
ShowMessage(‘Dit vervoermiddel ‘ + Hoe + ‘ en heeft ‘ + inttostr(Aantal_wielen) + ‘ wielen.’);
end;
var tp: string;
begin
Case Auto_type of
sedan: tp := ‘sedan.’;
coupe: tp := ‘coupe.’;
stationwagon: tp := ‘stationwagon.’;
cabriolet: tp := ‘cabriolet.’
else
tp := ‘onbekend.’
end;
inherited WatEnHoe;
ShowMessage(‘Deze auto is van het type ‘ + tp);
end;
begin
Auto := TAuto.Init(4,rijden);
Auto.Auto_type := coupe;
Fiets := TFiets.Init(2,rijden);
Vliegtuig := TVliegtuig.Init(16,vliegen);
Boot := TBoot.Init(0,varen);
end;
begin
Auto.Free;
Fiets.Free;
Vliegtuig.Free;
Boot.Free;
end;
begin
Auto.WatEnHoe;
end;
begin
Fiets.WatEnHoe;
end;
begin
Vliegtuig.WatEnHoe;
end;
begin
Boot.WatEnHoe;
end;
De vraag is dan ook gerechtvaardigd of we geen gebruik kunnen maken van één Object-variabele VervoerMiddel, die we naar believen kunnen gebruiken voor een auto, fiets, vliegtuig of boot.
Het antwoord op die vraag luidt: JA, maar niet zomaar naar believen.Polymorfisme
Denk hierbij bijvoorbeeld aan de mehode Hide die zowel bij een label als een button voorkomt en in beide gevallen het object onzichtbaar maakt. En dit terwijl een label en een button toch echt twee verschillende objecten zijn.
Vergeet de laatste opmerking van de vorige paragraaf niet: een object-variabele is niets anders dan een pointer-variabele, ofwel een variabele die naar een geheugenplaats wijst.
TDier = class
procedure WieBenIk;
procedure WatZegIk;virtual;
end;
Met de methode WieBenIk moet duidelijk worden gemaakt om welk dier het gaat, en met de methode WatZegIk moet duidelijk worden gemaakt welk geluid het dier maakt.
procedure WieBenIk;
procedure WatZegIk;override;
end;
TKat = class(TDier)
procedure WieBenIk;
procedure WatZegIk;
end;
Merk op dat bij THond de methode WatZegIk dynamisch is, maar bij TKat niet!
begin
ShowMessage(‘Ik ben een dier’);
end;
begin
ShowMessage(‘Ik maak geluid’);
end;
begin
ShowMessage(‘Ik ben een hond’);
end;
begin
ShowMessage(‘Ik blaf’);
end;
begin
ShowMessage(‘Ik ben een kat’);
end;
begin
ShowMessage(‘Ik mauw’);
end;
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;type
TDier = class
procedure WieBenIk;
procedure WatZegIk;virtual;
end;
THond = class(TDier)
procedure WieBenIk;
procedure WatZegIk;override;
end;
TKat = class(TDier)
procedure WieBenIk;
procedure WatZegIk;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;var
Form1: TForm1;implementation{$R *.DFM}procedure TDier.WieBenIk;
begin
ShowMessage(‘Ik ben een dier’);
end;procedure TDier.WatZegIk;
begin
ShowMessage(‘Ik maak geluid’);
end;procedure THond.WieBenIk;
begin
ShowMessage(‘Ik ben een hond’);
end;procedure THond.WatZegIk;
begin
ShowMessage(‘Ik blaf’);
end;procedure TKat.WieBenIk;
begin
ShowMessage(‘Ik ben een kat’);
end;procedure TKat.WatZegIk;
begin
ShowMessage(‘Ik mauw’);
end;procedure TForm1.Button1Click(Sender: TObject);
var
Dier : TDier;
Kat : TKat;
begin
Dier := TDier.Create;
Dier.WieBenIk;
Dier.WatZegIk;
Dier.Free; Dier := THond.Create;
Dier.WieBenIk;
Dier.WatZegIk;
Dier.Free; Kat := TKat.Create;
Kat.WieBenIk;
Kat.WatZegIk;
Kat.Free; Dier := TKat.Create;
Dier.WieBenIk;
Dier.WatZegIk;
Dier.Free;
end;Encapsulation
En hier onstaat, naar mijn mening, een probleem in Delphi, waarover zometeen meer.
private
protected
public
published
Verstoring scope in Delphi
Naar mijn mening is dit een ernstige tekortkoming in Delphi.Dynamische componenten
StdCtrls
procedure KnopKlik(Sender: TObject);
Knop: TButton;
Knop := TButton.Create(Self);
with Knop do
begin
Parent := Self;
Caption := ‘Klik me’;
OnClick := KnopKlik;
Top := 50;
Left := 150;
Hint := ‘This is what we call a button [‘+IntToStr(SizeOf(Knop))+’]’;
ShowHint := True;
end;
– Voeg in het code-venster in de event-handler FormClose de volgende regel toe:
Knop.Free;
Procedure TForm1.KnopKlik(Sender: TObject);
begin
showmessage(‘Knop geklikt’);
end;
– Run het project
Daarvoor kun je gebruik maken van de TList-class, die precies bijhoudt waar een control zich in het geheugen bevindt. Via een object van TList kun je dus altijd bij het gewenste control komen.
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure KnopKlik(Sender: TObject);
procedure ListBoxDubbelKlik(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
Knop : TButton;
LabelLijst : TList;
Labeltje : TLabel;
ListBox: TListBox;
public
{ Public declarations }
end;var
Form1: TForm1;implementation{$R *.DFM}procedure TForm1.FormCreate(Sender: TObject);
var a : Byte;
begin
LabelLijst := TList.Create;
ListBox := TListBox.Create(Self);
with ListBox do
begin
Parent := Self;
OnDblClick := ListBoxDubbelKlik;
for a := 0 to 9 do
Items.Add(‘Item ‘+IntToStr(a));
Top := 100;
Left := 150;
Hint := ‘This is a listbox: DubbleClick on Items [‘+IntToStr(SizeOf(ListBox))+’]’;
ShowHint := True;
end;
Knop := TButton.Create(Self);
with Knop do
begin
Parent := Self;
Caption := ‘Klik me’;
OnClick := KnopKlik;
Top := 50;
Left := 150;
Hint := ‘This is what we call a button [‘+IntToStr(SizeOf(Knop))+’]’;
ShowHint := True;
end;
for a := 1 To 10 do
begin
Labeltje := TLabel.Create(Self);
with Labeltje do
begin
Parent := Self;
Caption := ‘Dynamisch aangemaakt’;
AutoSize := True;
Top := a*30;
Hint := ‘This is dynamic label ‘+IntToStr(a)+’ [‘+IntToStr(SizeOf(Labeltje))+’]’;
ShowHint := True;
LabelLijst.Add(Labeltje);
end;
end;
Labeltje.Color := clRed;
Labeltje := LabelLijst.Items[4];
Labeltje.Color := clBlue;
end;procedure TForm1.KnopKlik(Sender: TObject);
var a : Byte;
begin
If LabelLijst.Count > 0 then
begin
ShowMessage(‘Labeltjes weghalen’);
For a := 0 To LabelLijst.Count – 1 do
begin
Labeltje := LabelLijst.Items[a];
Labeltje.Free;
LabelLijst.Items[a] := nil;
end;
LabelLijst.Free;
end;
end;procedure TForm1.ListBoxDubbelKlik(Sender: TObject);
begin
ShowMessage(‘U koos ‘+ListBox.Items[ListBox.ItemIndex]);
end;procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var a: Byte;
begin
If LabelLijst.Count > 0 then
begin
For a := 0 To LabelLijst.Count – 1 do
begin
Labeltje := LabelLijst.Items[a];
Labeltje.Free;
LabelLijst.Items[a] := nil;
end;
LabelLijst.Free;
end;
Knop.Free;
ListBox.Free;
end;Tot slot