background image

 

 

Wykład XXI

Object Pascal – Tworzenie 

komponentów

Łatwość tworzenia nowych 
komponentów i ich adaptacji jest 
jednym z największych atutów Delphi.

Podstawy informatyki
Semestr II Transport

background image

 

 

Kiedy tworzenie nowego 

komponentu jest 

uzasadnione ?

jeżeli  zastosowanie tworzonego komponentu nie 

ograniczy się do jednej aplikacji to praca włożona w jego 

tworzenie zwraca się wielokrotnie
stworzenie nowych komponentów może przyczynić się do 

bardziej przejrzystego i przyjaznego dla użytkownika 

sposobu aplikacji, gdyż logiczny podział jej funkcji  (z 

punktu widzenia użytkownika) znajdzie odzwierciedlenie 

w strukturze użytych komponentów
dla nietypowych zastosowań, gdy brak w środowisku 

odpowiednich komponentów – stworzenie ich staje się 

warunkiem koniecznym dla stworzenia aplikacji
sprzedaż uniwersalnych komponentów innym 

projektantom aplikacji
może stanowić wyzwanie dla projektanta, być 

sprawdzianem jego umiejętności

background image

 

 

Etapy tworzenia nowego 

komponentu

Szczegółowe określenie funkcji komponentu
Zaprojektowanie algorytmu działania komponentu
Sprecyzowanie oraz hierarchiczne i chronologiczne 
uporządkowanie poszczególnych czynności 
projektowych
Tworzenie komponentu (kodowanie) – powinno 
odzwierciedlać logikę komponentu 
Testowanie komponentu w ramach projektów 
testowych
Zainstalowanie komponentu na palecie 
komponentów

background image

 

 

Programowe etapy tworzenia 

komponentów

Wybór klasy bazowej
Stworzenie modułu źródłowego komponentu
Uzupełnienie definicji dziedziczonych z klasy 
bazowej o charakterystyczne dla nowego 
właściwości, metody i zdarzenia
Testowanie komponentu
Rejestracja komponentu w środowisku 
programistycznym
Stworzenie plików pomocy opisującego 
działanie i sposób korzystania z komponentu

background image

 

 

Wybór klasy bazowej

Właściwy wybór klasy bazowej ma 

decydujący wpływ na postać finalnego 

komponentu i wymagany do jego powstania 

nakład pracy.
Należy starać się dobrać klasę bazową tak, 

by w jak największym stopniu wykorzystać 

istniejące właściwości i metody.
Nie ma gotowej recepty na właściwy wybór 

jest od wynikiem wiedzy, umiejętności i 

doświadczenia programisty, czasem może 

być nawet polem eksperymentów.

background image

 

 

Tworzenie modułu 

źródłowego

Nieodłącznym elementem każdego komponentu jest jego definicja w 

kodzie źródłowym, domyślnie każdemu komponentowi odpowiada 

oddzielny moduł źródłowy (unit). Definiowanie nowego komponentu 

rozpoczyna się od wybrania opcji File|New|Component – 

wyświetlenia eksperta komponentów. Umożliwia on wybór klasy 

bazowej, określenie nazwy tworzonej klasy i jego modułu źródłowego, 

wybór strony na palecie komponentów na której komponent zostanie 

umieszczony. W rezultacie powstanie jedynie szkielet modułu 

zawierający definicję klasy i procedurę rejestracyjną.

Delph

i 4

Delph

i 2

background image

 

 

Definiowanie właściwości 

prostych

TPrzykladowaKlasa=class(TCustomControl)

private
   FIntegerProp: Interer;
   FStringProp: String;
   FCharProp: Char;
published
   property IntererProp read FIntegerProp write 

FIntegerProp;
   property StringProp read FStringProp write 

FStringProp;
   property CharProp read FCharProp write FCharProp;

end;

Właściwości reprezentują liczby, wartości lub znaki

background image

 

 

Definiowanie właściwości 

wyliczeniowych

     TPoryRoku=(prWiosna,prLato,prJesien,prZima);

     TPrzykladowaKlasa=class(TCustomControl)
private 

{pola wewnętrzne}

  

  FPoryRoku: TPoryRoku;

  

  FBooleanProp: Boolean;

published 

{właściwości wyliczeniowe}

  

  property PoryRoku read FPoryRoku write FPoryRoku;

      property BooleanProp read FBooleanProp write 

FBooleanProp;
     end;
Wymaga zwykle uprzedniego zdefiniowania typu 

wyliczeniowego, edycja w Object Inspectorze 

pozwala wybierać wartość właściwości z listy

background image

 

 

Definiowanie właściwości zbiorowych

       

TDni=(Poniedziałek,Wtorek,Środa,Czwartek,Piątek,Sobota,Nie

dziela);
  TSetDni=Set of TDni;

  TPrzykladowaKlasa=class(TCustomControl)
     private 

{pola wewnętrzne}

  

FDyzury: TSetDni;

     published 

{właściwości wyliczeniowe}

      property Dyzury read FDyzury write FDyzury;
  end;

Należy zdefiniować bazowy typ wyliczeniowy i oparty na nim 

typ zbiorowy, a następnie można zdefiniować odpowiednią 

właściwość zbiorową.

background image

 

 

Definiowanie właściwości 

obiektowych

Nic nie stoi na przeszkodzie, 
by właściwością klasy, była 
sama klasą lub komponentem. 
Za przykład można podać 
właściwość Font. 

Definicja obiektu, który ma być właściwością innej 
klasy powinna (bezpośrednio lub pośrednio) 
wywodzić się z klasy TPersistent. Kreowanie 
egzemplarza obiektu zawierającego właściwości 
obiektowe musi tworzyć obiekt związany z 
właściwością obiektową.Podobnie należy 
postępować podczas niszczenia obiektu – należy 
niszczyć  obiekty podrzędne przed zniszczeniem 
obiektu nadrzędnego. Konieczne jest zatem 
przedefiniowanie konstruktora i destruktora.

background image

 

 

Przykładowy obiekt 

podrzędny

TJakisObiekt=class(TPersistent)

private
  FProp1: Integer;
  FProp2: String;
published
  property Prop1: Integer read FProp1 write 
FProp1;
  property Prop2: String read FProp2 write 
FProp2;
end;

background image

 

 

Przykład definiowania 

właściwości obiektowych

TObiektNadrzedny=class(TCustomControl)
private
  FJakisObiekt: TJakisObiekt;
  procedure SetJakisObiekt(Value: TJakisObiekt);
public
  constructor Create(AOwner: TComponent);override;
  destructor Destroy;override;
published
  property JakisObiekt: TJakisObiekt read FJakisObiekt

        write SetJakisObiekt;

end;

background image

 

 

Tworzenie obiektów 

wewnętrznych

obiekt podrzędny tworzymy w konstruktorze 
po utworzeniu obiektu rodzica

constructor TObiektNadrzedny.Create(AOwner: 

TComponent);

begin
  inherited Create(Aowner);
  FJakisObiekt:=TJakisObiekt.Create;
end;

background image

 

 

Zwalnianie obiektów 

wewnętrznych

obiekt dziecko niszczymy przed zwolnieniem 
obiektu nadrzędnego w destruktorze

destructor TObiektNadrzedny.Destroy;
begin
  FJakisObiekt.Destroy;
  inherited Destroy;
end;

background image

 

 

Przypisanie wartości 

własności obiektowej

Należy sprawdzić czy przypisany jest jakiś 
egzemplarz obiektu. Jeśli tak należy go zwolnić i 
dopiero wówczas przypisać nową wartość 
właściwości obiektowej

procedure TObiektNadrzedny.SetJakisObiekt(Value: 

TJakisObiekt);

begin
  if Assigned(FJakisObiekt) then FJakisObiekt.Free;
  if Assigned(Value) then FJakisObiekt.Assign(Value)
end;

background image

 

 

Redefinicja metody 

Assign

Należy tu powiedzieć, że należałoby przedefiniować 

metodę Assign obiektu dziecka tak aby obsługiwała 

nowe właściwości obiektu

procedure TJakisObiekt.Assign(Source: TPersistant);
begin

if Source is TJakisObiekt then
  begin
    FProp1:=TJakisObiekt(Source).Prop1;
    FProp2:=TJakisObiekt(Source).Prop2;
    inherited Assign(Source);
  end;

end;

background image

 

 

Definiowanie właściwości 

tablicowych

Jeśli chcielibyśmy je umieścić w Object 

Inspektorze to należy zdefiniować specjalny 

edytor właściwości
Właściwość tablicowa musi posiadać jeden lub 

więcej indeksów (mogą być one dowolnego 

typu)
Klauzule read i write w definicji właściwości 

muszą określać metody dostępowe – nie jest 

dozwolone specyfikowanie pól
Liczba i kolejność indeksów w odwołaniu do 

właściwości być zgodna z jej deklaracją

background image

 

 

Przykład typu z 

właściwościami 

tablicowymi

TPlanety=class(TComponent)
private
  function GetPlanetName(const AIndex: Integer): String;
  function GetPlanetPosition(const APlanetName: String): 

Integer;

public
  property PlanetName[const AIndex: Integer]: String

read GetPlanetName; default;

  property PlanetPosition[const  APlanetName: String]: 

Integer
read GetPlanetPosition;

end;

background image

 

 

Właściwości tablicowe - 

Metody dostępowe

 - 

Przykład

function TPlanety.GetPlanetName

(const AIndex: Integer): String;

begin
  if (AIndex<=0) or (AIndex>9) then
    raise Exception.Create(‘Niepoprawny 

numer planety. ‘+
        ’Wprowadź liczbę od 1 do 9’)

  else Result::=PlanetNames[AIndex];
end;

background image

 

 

Właściwości tablicowe - 

Metody dostępowe – 

Przykład

 cd.

function TPlanety.GetPlanet Position

(const APlanetName: String): Integer;

var I: Integer;
begin
  Result:=0; i:=0;
  repeat
    inc(i);
  until (i>9) or  

(ANSICompareStr(ANSIUpperCase(APlanetName), 

ANSIUpperCase(PlanetNames[i]))=0);

  if i<=9 then Result:=i;
end;

background image

 

 

Metody dostępowe -

zmiana wartości właściwości

TJakisObiekt=class(TComponent)
{...} FWlasnosc:Integer;
procedure SetWlasnosc(Value: Integer);
{...}
property Wlasnosc: Integer read FWlasnosc write 

SetWlasnosc;

{...}
procedure TJakisObiekt.SetWlasnosc(Value: Integer);
begin {...}
  Wlasnosc:=Value; 

  {tu  nieskończona rekursja – błąd – winno być np. 

FWlasnosc:=Value} 

{...}

end;

background image

 

 

Zdarzenia

Zdarzenie jest wynikiem oddziaływania użytkownika, , 
systemu operacyjnego, bądź samej aplikacji
Reakcją obiektu na wystąpienie zdarzenia jest 
wywołanie związanej z nim procedury zdarzeniowej 
(event handler);
Każdemu zdarzeniu, na które zdolny jest reagować 
obiekt danej klasy, odpowiada określona właściwość 
zdarzeniowa (event property). Zapewnia ona dostęp 
do pola, będącego wskaźnikiem do określonej 
procedury zdarzeniowej.

background image

 

 

Definiowanie zdarzeń

TJakasKontrolka=class(TComponent);
private 
  FOnClick: TNotifyEvent;
protected
  procedure Click; dynamic;
  property OnClick: TNotifyEvent read FOnClick write 

FOnClick;

end;

Procedury zdarzeniowe w zależności od zdarzenia mają 

na ogół różny zestaw parametrów. Typ TNotifyEvent 

jest zdefiniowany następująco: 

TNotifyEvent=procedure(Sender: TObject) of object

background image

 

 

Procedura zdarzeniowa

procedure TJakasKontorlka.Click;
begin
 if Assigned(FOnClick) then 

FOnClick(Self);

end;

procedura sprawdza czy z obsługą 

zdarzenia powiązano jakąś procedurę 

(Assigned) i jeśli tak wywołuje ją.

background image

 

 

Definiowanie metod

Podczas dodawania i przedefiniowywania metod 
komponentów posługujemy się schematem przyjętym 
dla klas Object Pascala.
Staramy się unikać wzajemnych powiązań między 
metodami (unikać wymogu stosowania metody w 
towarzystwie innych metod lub co gorsza,  w określonej 
sekwencji).
Żadna metoda nie powinna wprowadzać obiektu w stan, 
w którym pewne zdarzenia lub właściwości nie są 
właściwie obsługiwane.
Należy nadawać metodom nazwy sugerujące 
wykonywane przez nie akcje
Właściwie dobieramy stopień widoczności metody

background image

 

 

Przedefiniowywanie 

konstruktorów

wymaga użycia klauzuli override;

constructor Create(AOwner: TComponent);override;

regułą powinno być również wywołanie konstruktora 

klasy macierzystej (bazowej). Przedefiniowywany 

konstruktor w klasie pochodnej musi mieć taką samą 

strukturę parametryczną jak konstruktor oryginalny. 

Klasa może mieć jednak kilka konstruktorów.

constructor TMojKomp.Create(AOwner: TComponent);
begin
 inherited Create(AOwner);
  {...}
end;

background image

 

 

Przedefiniowywanie 

destruktorów

wymaga również użycia klauzuli override;

destructor Destroy; override;

wewnątrz destruktora wywołanie destruktora klasy 

bazowej jest obowiązkowe i powinno być wykonane 

jako czynność ostatnia, np.

destructor TMojKomp.Destroy;
begin
  FTimer.Free;
  MyString.Free;
  inherited Destroy;
end;

background image

 

 

Zachowanie się 

komponentu na etapie 

budowania aplikacji

Konstruktor Create(...) komponentu 
wywoływany jest nie tylko w czasie wykonywania 
programu, lecz również w momencie wstawiania 
go do formularza na etapie budowy aplikacji.
Wynika stąd czasem konieczność rozróżnienia 
tych dwu etapów. Rozróżnienie jest możliwe 
dzięki właściwości zbiorowej ComponentState
Poszczególne elementy i ich znaczenie 
przedstawia tabela

background image

 

 

Znaczenie flag właściwości 

ComponentState

csAncesto
r

komponent został odziedziczony z formularza 
nadrzędnego

csDesigni
ng

aplikacja znajduje się na etapie projektowania

csFixups

do komponentu istnieje odwołanie z formularza, 
który nie jest aktualnie załadowany; po 
załadowaniu wszystkich formularzy flaga jest 

wyłączana

csLoading odczytywanie danych komponentu ze strumienia
csReading flaga jest ustawiana w momencie utworzenia 

komponentu (w wyniku odczytu ze strumienia) i 
kasowania wówczas, gdy dany komponent oraz 
wszystkie komponenty, od których jest on 
zależny, mają już ustalone parametry

csUpdatin
g

komponent jest uaktualniany ze względu na 
zmianę definicji z formularzu nadrzędnym

csWriting

trwa zapis komponentu do strumienia

background image

 

 

Rejestracja komponentu

Rejestracja komponentu jest czynnością, powodującą 
umieszczenie go na Palecie komponentów. Wykonuje ją 
procedura o nazwie RegisterComponents():

procedure RegisterComponents(const Page: string;

    

   ComponentClasses: array of TComponentClass);
Pierwszy parametr oznacza nazwę palety komponentów
Drugi zawiera nazwy klas rejestrowanych komponentów
Powyższa procedura musi być wywołana w treści 
procedury o nazwie Register, zadeklarowanej także w 
części publicznej modułu

background image

 

 

Przykład rejestrowania 

komponentów

unit MojComp;
interface
type 
TMojKomp=class(TComponent)

{...} end;

TInnyKomp=class(Tcomponent) {...} end;

procedure Register;
implementation{...}
procedure Register;
begin
  RegisterComponents(‘MojeKomponenty’,[TMojKomp, 

TInnyKomp]);

end;
end.

background image

 

 

Testowanie 

komponentu

Najczęstszym sposobem testowania 
komponentu jest stworzenie projektu w 
ramach którego obiekt będzie dynamicznie 
tworzony i zwalniany.
Projekt ten powinien również pozwalać na 
sprawdzenie wszystkich funkcji komponentu.
Należy pamiętać, że dobrze wykonany 
komponent powinien być gotowy do 
normalnego funkcjonowania już po 
wykonaniu konstruktora.

background image

 

 

Wybór ikony 

komponentu

Komponent nie będzie kompletny, dopóki nie 

zostanie opatrzony ikoną identyfikującą go w 

palecie komponentów. Do tworzenia ikon można 

wykorzystać Image Editor lub dowolny inny 

edytor obsługujący pliki BMP.
Ikona musi mieć rozmiary 24x24 i musi być 

przechowywana w pliku o takiej samej nazwie jak 

nazwa modułu z komponentem i znajdować się w 

tym samym co komponent katalogu.
Format pliku jest pod względem struktury 

powinien być zgodny z plikami zasobów Windows 

(.RES) lecz powinien mieć rozszerzenie .DCR.


Document Outline