1. Miksi omia komponentteja
  2. Kohtaan kolme (3) ensin yksinkertaisempi versio:

    Ja vinkki:

    Kirjoittamisvinkki Delphi 4.0:

    1) Lisää ensin rivi: property Count : integer;

    2) Paina Shift+Ctrl+C (Complete Class at Cursor)

    3) Täydennä syntynyt SetCount -metodi.

    4) Kirjoita rivi: constructor Create(...

    5) Paina Shift+Ctrl+C

    6) Täydennä Create-metodi

    7) Täydennä puuttuvat virtual -määritykset

     

    unit laskuri;

     

    interface

     

    uses SysUtils, Classes, Controls, Graphics, StdCtrls;

     

    type

    TLaskuri = class(TLabel)

    private

    FCount : integer;{ Ominaisuuksien attribuutit nimetään yleensä F-alkuisiksi}

    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. }

    { Ominaisuuksiin ilmoitetaan miten niitä luetaan ja miten asetetetaan sekä }

    { mahdollinen oletusarvo. Oletusarvo on ainoastaan ohje siitä, ettei }

    { arvoa talleteta resurssitiedostoon, jos arvo on sama kuin oletus. }

    { Luku/kirjoitusohje voi olla saman tyyppisen attribuutin nimi tai }

    { aliohjelma asettamiseen ja funktio lukemiseen }

    property Count:integer read FCount write SetCount default 0;

    end;

     

    procedure Register;

     

    implementation

     

     

    constructor TLaskuri.Create(AOwner:TComponent);

    begin

    inherited Create(AOwner); { Muista tämä!!! Muuten käy huonosti!!!}

    Count := 0;

    end;

     

     

    procedure TLaskuri.SetCount(const Value: integer);

    begin

    FCount := Value;

    inherited Caption := IntToStr(Count)+' '; { Välilyönti lop. on paremman näk. }

    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;

     

    end.

     

    Ennen Komponentin ikonin piirtäminen:

    1. Väärinkäytön poistaminen:
    2. Komponentissamme on vielä pieni vika: voidaan tehdä esimerkiksi sijoitus:

      lask.Caption := 10;

       

      ja kun tämän jälkeen lisätään laskurin arvoa, ei seuraava arvo olekaan 11, vaan yksi suurempi kuin FCount-attribuuttiin on jäänyt edelliseltä kerralta.

      Miten vika voidaan poistaa? Suositeltavin vaihtoehto olisikin periä luokka TCustomLabel. Ero TLabel-luokkaan on siinä, että TCustomLabelissa on kaikki samat ominaisuudet kuin TLabelissa, mutta protected-osassa, eli niitä ei voi ulkopuolinen käyttää. Tällöin em. sijoitus ei olisi mahdollista. Kuitenkin moni muukin TLabelin kiva ominaisuus jäisi suojatuksi. Ominaisuuksia voidaan siirtää kyllä julkaistulle puolelle kirjoittamalla:

      published // ks mallia TLabel-komponentin lähdekoodista StdCtrl.pas

      property Align;

      property Alignment;

      property Anchors;

      property AutoSize;

      property BiDiMode;

      // property Caption; // Tätä ei julkaista

      property Color;

      . . .

       

      Yleensä kaikista komponenteista on ensin TCustom… -versio joka on tarkoitettu perittäväksi.

      Toinen mahdollisuus on tehdä uusi Caption-ominaisuus, joka korvaa TLabelin Caption-ominaisuuden:

      Lisää

      published

      . . .

      property Caption:string read GetCaption write SetCaption stored false;

      ja tee vastaavat metodit (käytä luokkatäydennintä):

      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;

       

      Huomaa inherited avainsanan käyttö edeltäjäluokan ominaisuuden käyttämiseksi.

    3. Oletusarvojen muuttaminen
    4. Jos haluamme laskurimme tulevan valmiiksi isommalla fontilla oikeaan reunaan, voimme tehdä vielä pieniä parannuksia:

      Lisää valmiiden ominaisuuksien uudet oletusarvot:

      published

      . . .

      property Alignment default taRightJustify;

      property AutoSize default False;

      property Color default clAqua;

      property ParentColor default false;

       

       

      Tämä on vasta tieto siitä, että jos ominaisuuden arvo sattuu olemaan sama kuin oletus, niin arvoa ei tarvitse tilan säästämisen vuoksi tallettaa lomakkeelle. Varsinainen arvon asetus pitää tehdä itse:

      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;

       

    5. Komponentin ikonin piirtäminen
    6. Komponentin lisääminen komponenttikirjastoon

    Tehtävä 35: Uusi uljas autolaskuri

    Tee uusi Autolaskuri-ohjelma ja käytä siinä Laskuri-komponentteja.

    Tehtävä .1 TLisaaButton

    Tee uusi komponentti TLisaaButton, jolla on

    ominaisuutena

    Laskuri : TLaskuri - laskuri, jonka arvoa lisätään aina

    kun nappia painetaan

    Lisays : Integer - paljollako laskuria lisätään

    Tehtävä .2 Autolaskuri vähällä koodaamisella

    Tee autolaskuri em. komponenteilla niin, että tarvitsee kirjoittaa vain yksi rivi koodia: Nollaa-nappulalle

    Tehtävä .3 TNollaa

    Tee vielä komponentti TNollaa, joka nollaa kaikki samalla lomakkeella olevat laskurit (ks. ComponentCount ja Components).

    Tehtävä .4 Autolaskuri koodaamatta

    Tee autolaskuri kirjoittamatta riviäkään koodia. Jos lisätään uusi laskuri-lisää -pari, niin tarvitaanko vieläkään koodia?

    Tehtävä .5 Uusi TNollaa

    *Modifioi TNollaa -komponenttia siten, että sille voidaan antaa ominaisuutena kaikki komponentit, jotka halutaan nollata.

    Tehtävä .6 Laskuri panelista

    Peri TLaskuri panelista ja aseta oletuksena sopivat reunukset päälle.

    Tehtävä .7 Laskuri TCustom???-komponentista

    Peri TLaskuri TCustomLabelista tai TCustomPanelista ja pidä huoli ettei Caption-ominaisuutta voi käyttää .

    Tehtävä .8 LiikkuvaAuto-komponentti

    Tee uusi komponentti LiikkuvaAuto, jolla on oma nopeus ja joka tutkii itse, milloin tulee seinä vastaan.

     

     

     

     

  3. Tapahtumapohjainen ohjelmointi
  4. 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ä .1 Nollautuuko

    Nollautuuko summalaskuri kuin painetaan Nollaa-nappulaa? Miksi?

    Tehtävä .2 Yläraja

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

    Tehtävä .3 OnBeforeChange

    Lisää laskuriin OnBeforeChange –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.

    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.