5. Form met events naar Observers.
In hoofdstuk 4 hebben we gezien hoe je met een event een andere form kunt verwittigen dat er instellingen gewijzigd worden. Maar wat als er twee of meer forms 'interesse hebben' in het feit dat een instelling gewijzigd wordt? Bijvoorbeeld, je Mainform wil graag de label aanpassen, en een form die spelsituaties logt in een memo, wil dat ook graag weten! Je kunt maar één procedure aan het OnKaartKleurGewijzigd-event koppelen. Dus als twee forms elk een procedure aan dit event willen koppelen, dan heb je een probleem!
De oplossing ligt voor de hand: zorg dat je een lijst van procedures kunt koppelen aan één event, en voer die procedures één na één uit, telkens het event getriggerd wordt. Deze oplossing heeft ook een naam: het observer-patroon. De uitwerking is wat complexer dan 'een eenvoudig lijstje van procedures', maar als je het stap voor stap uitvoert, geraak je er zeker wel! Dit zijn de 6 stappen:
- Definieer een event-type.
- Maak een observerklasse, die gebruikt gaat worden in elke form die geïnformeerd wil worden.
- Maak een subjectklasse, die de rol van event-dispatcher op zich neemt.
- Pas InstellingenForm aan door een fSubject te gebruiken.
- In elke form die geïnformeerd wil worden voeg je een fObserver toe.
- In de Mainform link je de observers aan het subject van de InstellingenForm.
Om het effect duidelijk te zien in een voorbeeld, maken we eerst een nieuwe form, LogForm genaamd, met een Tmemo, die elke kleurwijziging gaat registreren. Zo zijn dus de Mainform en de LogForm geïnteresseerd in kleurwijzigingen.
Stap 1: Definieer een event-type.
De definitie van een event die aanduidt dat er een kaartkleur veranderd is, hebben we reeds in hoofdstuk 4 gezien. Deze definitie kunnen we hier gewoon overnemen:
Delphi Code:
type
TKaartKleur = (kkHarten, kkRuiten, kkKlaveren, kkSchoppen) ;
TKaartKleurEvent = procedure ( NieuweKleur: TKaartKleur ) of object ;
Dit was het voor stap 1. Tot hier niks nieuws, niks moeilijks.
Stap 2: De observerklasse.
De observerklasse is een klasse die 'observeert'.Hij vormt een onderdeel van de Form waaraan hij toegevoegd is, en voert de code uit die hem toegekend is. Eigenlijk speelt de observer de rol van de code in het notify-event, zoals we in voorbeeld 4 gezien hebben.
Hieronder volgt de volledige code van de observer. Merk op dt we er alle definities van Kaartkleuren aan hebben toegevoegd. Verder bevat een TKaartKleurObserver welgeteld één event (fOnUpdate), en één public procedure: Update. Zoals te verwachten, dient de property als plek om de event-code in op te slaan, en wordt het event uitgevoerd met de procedure Update.
Niet moeilijk, maar op dit ogenblik wellicht een beetje eigenaardig. Hoe deze observer percies in werking treedt, zie je later.
Delphi Code:
unit ObserverUnit;
interface
type
TKaartKleur = (kkHarten, kkRuiten, kkKlaveren, kkSchoppen) ;
const
KaartKleurString: array[TKaartKleur] of string =
('Rode Harten','Rode Ruiten','Zwarte Klaveren','Zwarte Schoppen') ;
type
TKaartKleurEvent = procedure (NieuweKleur: TKaartKleur) of object ;
TKaartKleurObserver = class
private
fOnUpdate: TKaartKleurEvent ;
public
property OnUpdate: TKaartKleurEvent read fOnUpdate write fOnUpdate;
procedure Update (NieuweKleur: TKaartKleur);
end;
implementation
{ TKaartKleurObserver }
procedure TKaartKleurObserver.Update(NieuweKleur: TKaartKleur);
begin
if Assigned(FOnUpdate) then
FOnUpdate(NieuweKleur)
end;
end.
Stap 3: De subjectklasse.
De subjectklasse zorgt ervoor dat we de events in een lijstje bijhouden, en telkens het event plaatsvindt, er niet één maar een hele reeks procedures uitgevoerd worden.
Niet te verwonderen dus dat de kern van een subjectklasse een TObjectList is.
Eigenlijk is het nog iets leuker: er staan niet gewoon procedures in het lijstje van een subjectklasse, maar observers!
Hier zien we de code voor de subjectklasse:
Delphi Code:
unit SubjectUnit;
interface
uses Contnrs {voor TObjectList}, ObserverUnit;
type
TKaartKleurSubject = class
private
fObservers: TObjectList;
public
constructor Create ;
destructor Destroy; override;
procedure Attach (Observer: TKaartKleurObserver);
procedure Detach (Observer: TKaartKleurObserver);
procedure Notify (KaartKleur: TKaartKleur);
end;
implementation
{ TSubject }
procedure TKaartKleurSubject.Attach(Observer: TKaartKleurObserver);
begin
fObservers.Add (Observer)
end;
constructor TKaartKleurSubject.Create;
begin
fObservers := TObjectList.Create (false)
end;
destructor TKaartKleurSubject.Destroy;
begin
fObservers.Free;
inherited;
end;
procedure TKaartKleurSubject.Detach(Observer: TKaartKleurObserver);
begin
fObservers.Remove (Observer)
end;
procedure TKaartKleurSubject.Notify (KaartKleur: TKaartKleur);
var
i: integer ;
begin
for i:=0 to fObservers.Count - 1 do
TKaartKleurObserver(fObservers[i]).Update(KaartKleur)
end;
end.
We hebben hierin allereerst fObservers, het lijstje met observers. Deze moet in een constructor gecreate worden, en in een destructor vernietigd worden.
De Attach en de Detach zorgen ervoor dat een observer zich kan melden als 'geïnteresseerd in het event'. Meer nog, de obeserver zal uitgevoerd worden als het event plaatsvindt. Merk op dat de Detach in ons voorbeeld niet gebruikt wordt, en er enkel bij staat ter volledigheid: hiermee zou je in de loop van je programma observers kunnen weghalen uit de lijst met uit te voeren observers, als je dat wil.
Dan blijft er enkel de procedure Notify uit te leggen. Nu, dat is, zoals ook te verwachten was, in een lusje alle 'aangesloten' observers één na één uitvoeren.
Nu moeten we de observers en de subject nog juist weten te gebruiken. Dat doen we in stappen 4, 5 en 6.
Stap 4: Subject inbouwen.
In de Instellingenform bouwen de een subject in. En daar waar je normaal een event oproept (zie voorbeeld 4), roep je nu 'fSubject.Notify' op.
Concreet: declareer een private fSubject, en maak er een public property van.
Delphi Code:
private
{ Private declarations }
fSubject: TKaartKleurSubject ;
public
{ Public declarations }
property Subject: TKaartKleurSubject read fSubject;
Deze fSubject moet je nog creëren bij de creatie van de form, en vernietigen bij de destroy. En zoals reeds gemeld, roep je de fSubject.Notify op daar waar je het event wil uitvoeren. Bij ons is dit in de OnClick van de RadioGroup.
In code ziet dat er als volgt uit:
Delphi Code:
procedure TInstellingenForm.KaartKleurRadioClick(Sender: TObject);
begin
fSubject.Notify (TKaartKleur(KaartKleurRadio.ItemIndex))
end;
procedure TInstellingenForm.FormCreate(Sender: TObject);
begin
FSubject := TKaartKleurSubject.Create ;
end;
procedure TInstellingenForm.FormDestroy(Sender: TObject);
begin
fSubject.Free
end;
Stap 5: De observers inbouwen.
In elke form dat we willen laten reageren op een event van onze Instellingen, bouwen we een observer in. Een fObserver wordt private gedeclareerd, en een public property verwijst naar deze fObserver. Verder maken we een private procedure ToonKaartKleur, waarin de code staat die we wensen uit te voeren als het event in de InstellingenForm gemeld wordt.
Samen geeft dit:
Delphi Code:
private
{ Private declarations }
fObserver: TKaartKleurObserver ;
procedure ToonKaartKleur (NieuweKleur: TKaartKleur);
public
{ Public declarations }
property Observer: TKaartKleurObserver read fObserver ;
In de FormCreate creëren we de fObserver, en de ToonKaart koppelen we er aan de fObserver.OnUpdate. In de FormDestroy vernietigen we de fObserver.
Delphi Code:
procedure TMainform.FormCreate(Sender: TObject);
begin
fObserver := TKaartKleurObserver.Create ;
fObserver.OnUpdate := ToonKaartKleur
end;
procedure TMainform.FormDestroy(Sender: TObject);
begin
fObserver.OnUpdate := nil;
fObserver.Free
end;
De implementatie van ToonKaartKleur hebben we hier in het voorbeeld, net als in alle voorgaande voorbeelden, eenvoudig gehouden. De ToonKaartKleur zet gewoon de overeenkomstige string in Label2.
Delphi Code:
procedure TMainform.ToonKaartKleur(NieuweKleur: TKaartKleur);
begin
Label2.Caption := KaartKleurString[NieuweKleur]
end;
De observer moeten we inbouwen in ELKE form die wil reageren op het event. Dus doen we hetzelfde in de onze nieuwe form LogForm.
Delphi Code:
procedure TLogForm.FormCrHij voert de eate(Sender: TObject);
begin
fObserver := TKaartKleurObserver.Create ;
fObserver.OnUpdate := ToonKaartKleur
end;
procedure TLogForm.FormDestroy(Sender: TObject);
begin
fObserver.OnUpdate := nil;
fObserver.Free
end;
procedure TLogForm.ToonKaartKleur(NieuweKleur: TKaartKleur);
begin
Memo1.Lines.Add(DateTimeTostr(Now)+ ' - Nieuwe kleur: ' + KaartKleurString[NieuweKleur])
end;
Als reactie op een verandering van kleur in onze InstellingenForm, willen we in de LogForm dit melden in Memo1, samen met het tijdstip. Dit is wat we in de ToonKaartKleur van TLogForm doen.
We hebben nog één ding te doen:
Stap 6: Op de observers aan de subject koppelen.
We moeten nog enkel zeggen welke observers geïnteresseerd zijn in welke subject. In ons voorbeeld is dat eenvoudig: er zijn maar twee observers, en zelfs maar één subject! De koppeling gebeurt door de oproep van de Attach-method van de subject.
Waar gaan we dit doen? Wel v????r we onze InstellingenForm voor de eerste maal openen in MainForm.
Delphi Code:
procedure TMainform.InstellingenButtonClick(Sender: TObject);
begin
InstellingenForm.Subject.Attach (fObserver) ; {{Eigen observer toevoegen}
InstellingenForm.Subject.Attach (LogForm.Observer); {{De observer van de de Logform toevoegen}
LogForm.Show;
InstellingenForm.Show
end;
We zijn er!
Toegegeven, wat meer werk dan een gewoon event, maar het effect is spectaculair!
Je klikt op de InstellingenButton, en de Logform en de InstellingenForm openen zich.
Telkens je een andere kaartkleur kiest, wordt dat onmiddellijk gemeld aan de Mainform, die de Label2 aanpast, én aan de Logform die het in de Memo logt.
Werking.
Nog even kort de werking van het geheel uitleggen in 4 stappen:- In de InstellingenForm wordt van kaartkleur veranderd. De InstellingenForm wil deze blijde gebeurtenis aan de 'Gehele Wereld' verkondigen. Alleen, weet de instellingenform niet in welke applicatie hij zit, laat staan wie er nu geïnsteresseerd is in dit heuglijke nieuws. Daarom stuurt hij maar een een Notify aan zijn Subject, die zich wel over de blijde boodschap zal kommeren.
Delphi Code:
fSubject.Notify (TKaartKleur(KaartKleurRadio.ItemIndex))
- De subject gaat alle ingeschreven observers verwittigen. Da's gemakkelijk, al deze observers behoren tot dezelfde klasse, en hebben de Update method. Dus moet de subject gewoon in een lusje de Update methods oproepen.
Delphi Code:
for i:=0 to fObservers.Count - 1 do
TKaartKleurObserver(fObservers[i]).Update(KaartKleur)
- De Update van een observer doet niets anders dan zijn eigen event fOnUpdate starten.
Delphi Code:
if Assigned(FOnUpdate) then
FOnUpdate(NieuweKleur)
- Vermits een procedure ToonKaartKleur (had evengoed helemaal anders kunnen heten) aan de OnUpdate gekoppeld is, wordt deze ToonKaartKleur uitgevoerd.
Delphi Code:
Memo1.Lines.Add(DateTimeTostr(Now)+ ' - Nieuwe kleur: ' + KaartKleurString[NieuweKleur])
Besluit.
Communicatie tussen twee forms is heel eenvoudig als het van de MainForm naar een Subform gaat:
Delphi Code:
SubForm.DoeIets( MetDezeParameter )
Wil de Mainform speciaal een SubForm openen, om van die Subform iets te weten te komen, dan gebruiken we een dialog:
Delphi Code:
NieuweWaarde := SubForm.GeefMijEenWaarde ( MetDezeParameter )
Wil de SubForm de Mainform verwittigen, telkens Subform iets interessants te melden heeft, dan gebruiken we een event:
Delphi Code:
if assigned(fNotify) then
fNotify ( MetDezeNieuweWaarde )
Wil de SubForm de hele wereld verwittigen van elke interessante gebeurtenis, dan gebruiken we een subject uit een Observer-Pattern:
Delphi Code:
fSubject.Notify ( MetDezeNieuweWaarde )
Deze technieken kun je niet alleen tussen forms gebruiken, maar zo kun je ook allerlei componenten en klassen met mekaar laten communiceren. Laat je fantasie maar de vrije loop.
Artikel uitknippen en boven je bed hangen.
Sam Witse
Bookmarks