Ylös Edellinen Seuraava Otsikkosivu Hakemisto Sisällys

2. Tapahtumapohjainen ohjelmointi

Edellisessä luvussa saimme tehtyä ensimmäisen oman komponentin. Komponentin teko ei ollut kovin vaikeaa. Mutta komponentteja tehdessä pitää ehkä sittenkin huomioida vähän enemmänkin. Itse asiassa HYVÄN komponentin tekeminen on jo taidetta sinänsä. Pitäisi pystyä ennakoimaan mitä mahdolliset komponentin käyttäjät haluavat komponentiltamme. Tai pitäisi ainakin jättää mahdollisuus periä komponenttiamme siten, että halutut muutokset on mahdollista toteuttaa itse. Esimerkki hyvästä komponenttihierarkiasta on Delphin VCL-kirjasto. Sen lähdekoodeihin kannattaa tutustua.

Tehdäänpä autolaskuriin pieni lisäys: Summa-laskuri, jossa on kaikkien muiden laskureiden summa. Tietysti ensin lisäämme Autolaskuri-lomakkeelle:

	LaskuriSumma : TLaskuri

seuraavaksi voi tulla mieleen tehdä nappuloihin painamisiin muutoksia:

	procedure TFormAutolaskuri.ButtonHAClick(Sender: TObject);
	begin
	  LaskuriHA.Inc(1);
	  LaskuriSumma.Count := LaskuriHA.Count + LaskuriKA.Count;
	end;

Tämä tietysti toimii, mutta sama koodimuutos pitää tehdä kuorma-auton nappulaan ja myös Nollaa-nappulaan. Ensimmäisessä autolaskurissamme lisäsimme lopulta myös Drag and Drop -ominaisuuksia. Näihinkin pitäisi tehdä em. koodimuutos. Asiaa voidaan tietysti kiertää tekemällä lisäämistä ja muuttamista varten sopivia aliohjelmia (tai metodeja), jotka pitävät huolta myös summalaskurista. Mutta ennenpitkää koodiin tulee kuitenkin jokin muutos, jossa LaskuriSumma unohtuu päivittää.

Mikä on parempi tapa hoitaa ongelma? Jos laskurit itse kertoisivat omasta muutoksestaan, voitaisiin summalaskuria päivittää aina kun jokin laskuri muuttuu. Siispä toimeen:

Lisätään TLaskuri-komponentin koodiin tapahtuma:

	TLaskuri = class(TLabel)
	  private
	. . .
	    FOnChange: TNotifyEvent;
	. . .
	  published  
	. . .
	    property OnChange : TNotifyEvent read FOnChange write FOnChange;
	end;

Tämä tapahtuma tulee komponentin Events-välilehdelle, koska ominaisuuden tyyppinä on metodi. Tämä lisäys ei vielä yksin riitä. Meidän täytyy myös pitää huoli siitä, että tapahtuman käsittelijää kutsutaan siellä missä on tarkoituskin. Jos TLaskuri komponentti on tehty hyvin, ei laskurin arvoa muuteta muualla kuin yhdessä paikassa. Onneksi meidän kohdallamme näin on:

	procedure TLaskuri.SetCount(const Value: integer);
	begin
	  FCount := Value;
	  inherited Caption := IntToStr(Count)+' '; { Välilyönti lop. on paremman näk. }
	  if ( Assigned(OnChange) ) then OnChange(self);
	end;

Eli jos joku on halunnut tapahtuman käsiteltäväksi ( Assigned(OnChange)), kutsutaan sitä metodi, johon OnChange -metodiosoitin osoittaa ( OnChange(self)).

Itse autolaskuri toimii vielä kuten ennenkin, mutta jos klikkaamme (muista kääntää komponentti uudelleen) LaskuriHA:n Events-välilehdellä OnChange -tapahtumaa ja kirjoitamme koodin:

	procedure TFormAutolaskuri.LaskuriHAChange(Sender: TObject);
	begin
	  LaskuriSumma.Count := LaskuriHA.Count + LaskuriKA.Count;
	end;

niin johan rupesi laskemaan. Sijoitetaan sama tapahtuma vielä LaskuriKA:n OnChange -tapahtumaksi.

Saavutettu hyöty on nyt siinä, että muutettiinpa laskureita millä tavalla tahansa, niin muutos tulee aina käsiteltyä. Pienenä miinuksena on se, että jos lisätään esim. LaskuriPP, niin tällöin summan laskukaavaa pitää muuttaa.

Voisimme tehdä myös erilaisen tapahtuman. Tapahtuman, jossa kerrotaan itse laskurin lisäksi muutoksen suuruus. Tällaista tapahtumaosoitinta ei ole tietenkään valmiina, mutta voimme sellaisen tehdä itsekin:

	type
	  TLaskuri = class;
	
	  TLaskuriEvent = procedure (sender:TLaskuri; diff:integer) of object;
	
	  TLaskuri = class(TLabel)
	  private
	    FCount : integer;
	    FOnChange: TLaskuriEvent;
	. . .
	  published  
	. . .
	    property OnChange : TLaskuriEvent read FOnChange write FOnChange;
	  end;

Ja tietysti muutos itse laskurin asettamiseen:

	procedure TLaskuri.SetCount(const Value: integer); 
	var diff : integer;
	begin
	  diff := Value - Count;
	  FCount := Value;
	  inherited Caption := IntToStr(Count)+' '; { Välilyönti lop. on paremman näk. }
	  if ( Assigned(OnChange) ) then OnChange(self,diff);
	end;

Nyt autolaskurissamme riittäisi koodi:

	procedure TFormAutolaskuri.LaskuriHAChange(sender: TLaskuri; diff: Integer);
	begin
	  LaskuriSumma.Inc(diff);
	end;

ja kun kaikki summaan halukkaat laskurit ilmoittavat saman tapahtuman, niin summalaskuri pysyy ajantasalla vaikka lisäisimme kuinka monta laskuria.

Tehtävä 2.10 Nollautuuko

Nollautuuko summalaskuri kuin painetaan Nollaa-nappulaa? Miksi?

Tehtävä 2.11 Yläraja

Lisää laskuriin yläraja-ominaisuus ja tapahtuma kun yläraja ylitetään.

Tehtävä 2.12 OnBeforeChange

Lisää laskuriin On BeforeChange -tapahtuma, joka suoritetaan ennenkuin arvoa muutetaan. Parametrinä tapahtumalle on mm. var Accept:boolean, johon tapahtuman käsittelijän tulee sijoittaa arvo sen mukaan sallitaanko muutos vaiko ei.

2.1 TLaskuri

	unit laskuri;
	
	interface
	
	uses SysUtils, Classes, Controls, Graphics,  StdCtrls;
	
	type
	  TLaskuri = class;
	
	  TLaskuriEvent = procedure (sender:TLaskuri; diff:integer) of object;
	
	  TLaskuri = class(TLabel)
	  private
	    FCount : integer;
	    FOnChange: TLaskuriEvent;
	    function GetCaption: string;
	    procedure SetCaption(const Value: string);
	  protected
	    procedure SetCount(const Value: integer);  virtual;
	  public
	    constructor Create(AOwner:TComponent); override;
	    function Inc(i:integer) : integer;     virtual;
	  published  { Published declarations } { Yleensä vain ominaisuudet.           }
	    property Count:integer read FCount write SetCount default 0;
	    property Caption:string read GetCaption write SetCaption stored false;
	    property OnChange : TLaskuriEvent read FOnChange write FOnChange;
	    { Seuraavat ominaisuudet isältä, ainoastaan oletusarvot erilaiset          }
	    property Alignment        default taRightJustify;
	    property AutoSize         default False;
	    property Color            default clAqua;
	    property ParentColor      default false;
	  end;
	
	procedure Register;
	
	implementation
	
	constructor TLaskuri.Create(AOwner:TComponent);
	begin
	  inherited Create(AOwner);            { Muista tämä!!! Muuten käy huonosti!!!}
	  Count := 0;                          { Defaulteissa luvatut arvot:          }
	  Alignment := taRightJustify;
	  Color := clAqua;
	  ParentColor := False;
	  Font.Height := -32;
	  Font.Style := [fsBold];
	  AutoSize := False;
	end;
	
	
	procedure TLaskuri.SetCount(const Value: integer);
	var diff : integer;
	begin
	  diff := Value - Count;
	  FCount := Value;
	  inherited Caption := IntToStr(Count)+' '; { Välilyönti lop. on paremman näk. }
	  if ( Assigned(OnChange) ) then OnChange(self,diff);
	end;
	
	
	function TLaskuri.Inc(i:integer) : integer;
	begin
	  Count := Count + i;
	  Result := Count;
	end;
	
	
	procedure Register;
	{ Komponentit pitää rekisteröidä komponenttisivulle }
	begin
	  RegisterComponents('Samples', [TLaskuri]);
	end;
	
	function TLaskuri.GetCaption: string;
	begin
	  Result := Trim(inherited Caption); { Varo rekursiota!!! }
	end;
	
	procedure TLaskuri.SetCaption(const Value: string);
	begin
	  Count := StrToIntDef(Trim(Value),0);
	end;
	
	
	end.


Ylös Edellinen Seuraava Otsikkosivu Hakemisto Sisällys