20, ## Documents ##, Delphi 4 dla każdego


Rozdział 20.
Tworzenie komponentów


Delphi udostępnia szeroki asortyment komponentów możliwych do wykorzystania w tworzonych aplikacjach. W skład oferowanych komponentów wchodzą proste kontrolki Windows, jak również specjalizowane komponenty nie wywodzące się z tego środowiska. Mimo to, może się zdarzyć tak, że będziesz musiał stworzyć komponent według własnego pomysłu, aby zrealizować zadanie, którego nie są w stanie wykonać komponenty preinstalowane. Aby skonstruować komponent należy wykonać następujące kroki:

  1. Rozpocząć proces tworzenia przy pomocy okna dialogowego nowego komponentu (New Component).

  2. Dodać właściwości, metody i zdarzenia do klasy komponentu.

  3. Przetestować komponent.

  4. Dodać komponent do Palety Komponentów.

W tym rozdziale poznasz zasady tworzenia komponentów. Podobnie jak w przypadku innych aspektów Delphi, również i ten okaże się w miarę prosty, po bliższym zapoznaniu się z nim. Zasady tworzenia poznasz budując komponent o nazwie TFlashingLight. Jest to zwykły komponent typu Label z migoczącym tekstem. Nim rozdział ten dobiegnie końca będziesz już wiedział, w jaki sposób tworzy się proste komponenty.

Tworzenie nowego komponentu

Tworzenie komponentów wymaga wyższego poziomu umiejętności programistycznych niż ten, który osiągnąłeś do tej pory. Po pierwsze trzeba stworzyć klasę dla nowego komponentu. Klasa ta musi być odpowiednio zaprojektowana tak, aby niektóre z jej właściwości były wyświetlane przez Inspektor Obiektów, podczas gdy inne będą dostępne tylko w czasie wykonania. Dodatkowo, niemal z całą pewnością trzeba będzie stworzyć klika metod dla komponentu. Niektóre z metod będą prywatnymi metodami komponentu; inne będą publiczne, dzięki czemu użytkownik komponentu będzie miał do nich dostęp. W końcu, niezbędne może okazać się również utworzenie kilku zdarzeń komponentu. Jak widać, z zagadnieniem tym wiąże się trochę pracy. Tworzenie komponentów opiera się na czystym programowaniu.

Okno dialogowe nowego komponentu

Okno dialogowe nowego komponentu (New Component) umożliwia szybkie rozpoczęcie procesu tworzenia. Aby wyświetlić to okno, wybierz polecenie File | New; kiedy otwarte zostanie Repozytorium, kliknij dwukrotnie na ikonie Component.

Okno New Component podczas tworzenia nowego komponentu przedstawione zostało na rysunku 20.1.

Rysunek 20.1.

Okno dialogowe nowego komponentu

0x01 graphic

Pole Ancestor type służy do określenia klasy macierzystej dla nowego komponentu. Klasy wszystkich zainstalowanych komponentów są dostępne na liście rozwijalnej tego pola. Tworząc nowy komponent należy wybrać klasę podstawową, która najlepiej odpowiada typowi komponentu, jaki chcemy stworzyć.

Przykładowo, komponent FlashingLabel jest po prostu etykietą, która miga. Do rozpoczęcia jego budowy w zupełności wystarczy standardowy komponent Label, dlatego jako klasę przodka można wybrać TCustomLabel. Z drugiej jednak strony, gdybyśmy chcieli zbudować komponent służący np. do tworzenia skrótów Windows, jako klasę podstawową należałoby wtedy przyjąć TComponent (klasę podstawową wszystkich komponentów) ponieważ nie istnieje żaden inny komponent VCL, który mógłby służyć jako lepsza podstawa do rozpoczęcia pracy.

0x01 graphic

Delphi udostępnia kilka klas, które można użyć jako klasy podstawowe dla nowych komponentów. Nazwy tych klas zaczynają się od frazy TCustom. Przykładowo - klasą podstawową dla TLabel jest TCustomLabel. Tworząc nowy komponent można użyć jednej z klas TCustom… jako klasy podstawowej. Klasy typu TCustom… posiadają właściwości, które najprawdopodobniej będą potrzebne dla danego typu komponentu, nie są one jednak publikowane (właściwości publikowane to właściwości wyświetlane przez Inspektor Obiektów w czasie projektowania). Wszystko co trzeba zrobić, aby właściwości zostały opublikowane to ponownie zadeklarować właściwości klasy podstawowej w sekcji published deklaracji klasy komponentu. Ma to znaczenie niebagatelne, gdyż klasa pochodna nie jest w stanie anulować opublikowania właściwości w klasie macierzystej.

Rozpoczęcie od klasy TCustom… umożliwia dokładne określenie właściwości, które mają być publikowane.

Podczas wyprowadzania nowego komponentu z komponentu istniejącego, wykorzystywana jest jedna z cech Object Pascala - dziedziczenie. O dziedziczeniu mowa była w rozdziale trzecim „Klasy i programowanie obiektowo zorientowane”. Dziedziczenie obiektu oznacza w rzeczywistości przejęcie wszelkich cech, jakimi dysponuje dany obiekt i dodanie własnej funkcjonalności. Klasa z której dziedziczone są własności, nazywana jest klasą macierzystą, podstawową, lub bazową (ang. base class), z kolei nową klasę nazywa się klasą pochodną lub potomną (ang. derived class). W poprzednim przykładzie klasą bazową była TCustomLabel, a klasą potomną - TFlashingLabel.

Po wybraniu klasy przodka w polu Class Name wpisz nazwę klasy tworzonego komponentu. Nazwa ta powinna zaczynać się od litery T i opisywać funkcję, jaką spełnia klasa. Komponent, którego budową zajmiesz się już niedługo w tym rozdziale, będzie nosił nazwę TFlashingLabel. Jeżeli wpisana zostanie nazwa klasy, która istnieje już w bibliotece komponentów, okno dialogowe dodania komponentu poinformuje o tym w chwili kliknięcia w przycisk OK. Kontynuacja procesu zależy więc od wpisania unikalnej nazwy klasy.

0x01 graphic

Nie ma żadnego szczególnego powodu, dla którego należy rozpoczynać nazwę klasy od litery T; jest to po prostu pewna konwencja przyjęta w nazewnictwie klas firmy Borland. (Tradycja stosowania litery T w nazwach klas firmy Borland wywodzi się z wczesnego okresu języka Turbo Pascal. Tego typu nazewnictwo było stosowane w bibliotece Turbo Vision, OWL, a obecnie w VCL.) Niektórzy programiści stosują literę T w przypadku klas dziedziczonych z klas firmy Borland, natomiast pomijają ją we własnych klasach. Decyzja należy do Ciebie.

0x01 graphic

Profesjonalni twórcy komponentów już dawno temu nauczyli się dbać o to, aby nazwy ich klas komponentów były unikalne. Wyobraź sobie problemy jakie mogłyby powstać, gdyby dwóch producentów komponentów nazwało swój produkt jednakową nazwą, np. TFancyLabel. W firmie TurboPower gdzie pracuję, nazwy naszych komponentów typu Async Professional zaczynają się od TApd, komponenty Orpheus rozpoczynają się od znaków TOr, komponenty Abbrevia od TAb itd. Chociaż nie ma tutaj gwarancji, iż nazwy te nie pokryją się z nazwami komponentów innych twórców, jest to jednak dosyć duża szansa gwarancja uniknięcia takiego zjawiska.

Pole Palette Page określa stronę Palety Komponentów, na której pojawić powinna się ikona komponentu. (W rzeczywistości ikona pojawi się na Palecie dopiero po zainstalowaniu pakietu środowiskowego zawierającego reprezentujący ją komponent.) Można wybrać istniejącą zakładkę Palety Komponentów lub wpisać nazwę nowej zakładki, jaką chcemy utworzyć dla danego komponentu.

Pole Unit file name służy do wyspecyfikowania nazwy pliku, który będzie przechowywał kod źródłowy komponentu. Nazwa tego pliku jest tworzona automatycznie przez Delphi w oparciu o nazwę komponentu, ale w miarę potrzeby można ją zmodyfikować. Pole Search path służy do wyspecyfikowania ścieżki poszukiwań, jaką Delphi powinno stosować przy odnajdywaniu pakietów komponentu. Zazwyczaj nie ma potrzeby modyfikowania tego pola.

Przycisk Install służy do rozpoczęcia fizycznej instalacji nowego komponentu w pakiecie. Nie musisz się teraz tym przejmować, ponieważ do instalacji użyjesz domyślnego pakietu Delphi, przeznaczonego dla różnego typu komponentów.

Tworzenie komponentu FlashingLabel

Teraz możemy już przystąpić do wykonania pierwszych czynności związanych z tworzeniem komponentu TFlashingLabel. Jak wspomniałem wcześniej, będzie to standardowy komponent Label wyświetlający na ekranie migoczący tekst. Mając to w pamięci, rozpocznij pracę:

  1. Poleceniem File | New otwórz Repozytorium.

  1. Kliknij dwukrotnie na ikonie Component; wyświetlone zostanie okno dialogowe nowego komponentu (New Component).

  2. Z pola listy rozwijalnej Ancestor type wybierz klasę podstawową TCustomLabel.

  3. W polu Class Name wpisz TFlashingLabel.

  4. W polu Palette Page pozostaw domyślną wartość Samples; w chwili instalacji nowy komponent zostanie dodany do strony Samples Palety Komponentów.

  5. Kliknij na przycisk OK, aby zamknąć okno nowego komponentu. Wyświetlony zostanie Edytor Kodu wraz z nowym modułem kodu źródłowego.

  6. Zapisz moduł pod nazwą FlashingLabel.pas.

Postać modułu na obecnym etapie prac przedstawiona została na listingu 20.1.

Listing 20.1. FlashingLabel.pas

unit FlashingLabel;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

StdCtrls;

type

TFlashingLabel = class(TCustomLabel)

private

{ Private declarations }

protected

{ Protected declarations }

public

{ Public declarations }

published

{ Published declarations }

end;

procedure Register;

implementation

procedure Register;

begin

RegisterComponents('Samples', [TFlashingLabel]);

end;

end.

Jak widać, klasa TFlashingLabel wywodzi się z klasy TCustomLabel. Pomijając słowa kluczowe określające kategorię dostępności (private, public, protected i published) deklaracja klasy jest pusta. Po omówieniu elementów, jakie składają się na komponent, będziesz mógł przystąpić do wypełniania pustych miejsc.

Okno nowego komponentu umożliwia szybkie rozpoczęcie pracy, przez wypełnienie pewnych podstawowych części modułu. Trudniejsza cześć pracy leży oczywiście w rękach programisty, chociaż stworzona została przynajmniej procedura Register oraz deklaracja samej klasy. Zboczmy na chwilę z głównego tematu, skupiając się na procedurze Register.

Procedura Register

Rejestrowanie komponentów jest niezbędne, aby Delphi mogło wiedzieć, jakie komponenty znajdują się w bibliotece komponentów i na jakiej stronie Palety Komponentów powinny się pojawiać. Typowa procedura Register wygląda następująco:

procedure Register;

begin

RegisterComponents('Samples', [TKomponent]);

end;

Aby zarejestrować komponent, procedura Register wywołuje procedurę RegisterComponents pobierającą dwa parametry. Pierwszym z nich jest nazwa strony Palety Komponentów, na której komponent pojawi się po zainstalowaniu; drugi parametr jest tablicą komponentów przeznaczonych do zarejestrowania. Jeżeli utworzona została cała biblioteka komponentów, wszystkie one mogą być zarejestrowane jednocześnie, przez umieszczenie w tablicy nazwy klasy komponentu. Przykład:

procedure Register;

begin

RegisterComponents('Essentials 1',

[TEsLabel, TEsScrollingMarquee, TEsCalendar,

TEsCalculator, TEsDateEdit, TEsNumerEdit, TEsMenuButton,

TEsColorComboBox, TEsTitle, TEsGradient, TEsRollUp]);

end;

Powyższa procedura Register rejestruje 11 komponentów i umieszcza je na stronie Palety Komponentów o nazwie Essentials 1. Zazwyczaj będziesz miał do czynienia tylko z jednym komponentem, ale jak widać w miarę potrzeby można również rejestrować większą ich ilość.

0x01 graphic

Procedura Register służy również do rejestrowania edytorów komponentów i edytorów właściwości. Są to edytory specjalnego typu, występujące zazwyczaj w postaci okien dialogowych, które pomagają w modyfikacji właściwości jednego lub kilku komponentów w czasie projektowania. Edytory komponentów i właściwości wykraczają poza zakres tej książki, dlatego też nie będą tutaj omawiane.

W tej chwili nasz komponent nie robi nic szczególnego, ale zanim przystąpisz do dalszej pracy powinieneś dowiedzieć się, z czego w ogóle składa się komponent. Kiedy będziesz już to wiedział, będziesz mógł powrócić do pracy nad komponentem TFlashingLabel. Pozostaw utworzony niedawno komponent otwarty (w obecnym stanie) w środowisku IDE Delphi, ponieważ już niedługo będzie on potrzebny.

Właściwości i metody komponentu

Dużą część procesu tworzenia komponentu stanowi pisanie jego właściwości i metod. Również zdarzenia biorą duży udział w trakcie pisania komponentu, ale tym zajmiemy się później.

Właściwości

Do tej pory bardzo często korzystałeś z właściwości; wiesz czym one są od strony użytkownika. Teraz musisz poznać i z rozumieć właściwości z perspektywy programisty komponentów. Zanim rozpoczniesz pisanie komponentów, musisz zrozumieć czym są (i czym nie są) właściwości.

Z całą pewnością, właściwości nie są polami danych. Naturalne wydaje się myślenie o właściwościach jako o polach danych klasy, do której należą. W końcu, są one traktowane tak jak pola danych podczas przeprowadzania operacji w stylu:

var

W : Integer;

begin

W := Width;

Height := W *2;

Mimo to właściwości nie są polami danych klasy i trzeba o tym pamiętać tworząc komponenty. Właściwości różnią się od pól danych na wiele sposobów, ale mają też przynajmniej jedną cechę wspólną z polami danych: posiadają specyficzny typ danych. Typem właściwości może być jeden z prostych typów danych (Integer, Word, Double, string itd.), klasa (TCanvas, TFont itd.) lub rekord (np. TRect).

Właściwości są więc specjalnym typem obiektu, który spełnia następujące kryteria:

Aby nabrało to większego sensu, przyjrzyjmy się każdej z tych cech z osobna.

Z właściwościami skojarzone są pola danych

Każda właściwość posiada skojarzone ze sobą pole danych; jest to pole, które przechowuje rzeczywistą wartość właściwości. Weźmy pod uwagę proste przypisanie:

Label.Caption := 'Pat McGarry';

Wyrażenie to przypisuje łańcuch do właściwości Caption komponentu Label. To co dzieje się w tle tej operacji jest czymś więcej niż tylko prostym przypisaniem. Ponieważ właściwość Caption jest typu string, skojarzone z nią pole jest polem typu string. Kiedy przypisanie następuje w sposób przedstawiony wyżej, polu temu nadawana jest wartość przypisywanego łańcucha. Stosowanie skojarzonych pól jest niezbędne, ponieważ właściwość nie posiada zdolności do samodzielnego przechowywania danych.

Skojarzonym polom danych można nadawać dowolne nazwy, jednak zgodnie z tradycją powinny one rozpoczynać się od litery F, po której następuje nazwa właściwości. Przykładowo, pole danych skojarzone właściwością Caption nosi nazwę FCaption.

0x01 graphic

Powiązanie między właściwością, a jej skojarzonym polem może być źródłem wielu nieporozumień podczas stawiania pierwszych kroków w pisaniu komponentów. Nie chodzi o to, że jest to trudne do zrozumienia; chodzi o to, że w trakcie pisania kodu komponentu łatwo można pomieszczać te dwie sprawy. Przykładowo, przez przypadek można napisać:

Left := 20;

podczas gdy naszą intencją było

FLeft := 20;

Rezultatem tego będzie szereg interesujących zachowań tworzonego komponentu. Dlaczego tak się stanie, przekonasz się kiedy omawiane będą metody write.

Skojarzone pole danych jest niemal zawsze deklarowane jako pole prywatne. Dzięki temu użytkownik może modyfikować pole poprzez właściwość lub przez wywołanie metody, ale nigdy w sposób bezpośredni. W ten sposób uzyskuje się maksymalną kontrolę nad polem danych, co z kolei prowadzi nas do kolejnej cechy właściwości.

Właściwości mogą posiadać metody write

Podczas przypisywania wartości właściwości, w tle może wydarzyć się wiele rzeczy. Dokładne zachowanie zależy od specyfiki określonej właściwości. Dla przykładu, następujący kod wygląda bardzo prosto:

Left := 20;

Jednak następstwem tego polecenia jest zaistnienie kilku zdarzeń. Po pierwsze, skojarzonemu polu danych - FLeft - nadawana jest nowa wartość. Następnie, formularz (przy założeniu, że przypisanie zostało wykonane w jego wnętrzu) zostaje przemieszczony na nową pozycję, przy pomocy funkcji Windows API - MoveWindow. W końcu, w wyniku wywołania funkcji Invalidate formularza następuje jego ponowne narysowanie.

W jaki sposób odbywają się te wszystkie operacje? Poprzez metodę write właściwości Left. Write jest metodą wywoływaną za każdym razem, gdy właściwości jest przypisywana wartość. Można ją wykorzystać do sprawdzania poprawności przypisanej wartości lub do wykonania specyficznych operacji.

Metoda write jest deklarowana w chwili deklaracji właściwości. Przykład typowej deklaracji właściwości znajduje się poniżej:

property FlashRate : Integer;

read FFlashRate write SetFlashRate;

Do tej pory nie spotkałeś jeszcze takiej składni deklaracji, ponieważ jest ona specyficzna dla właściwości. W deklaracji właściwości użyte zostało słowo kluczowe property, a za jej nazwą umieszczony został typ właściwości. Druga linia deklaracji informuje kompilator, że właściwość jest czytana bezpośrednio z pola danych FFlashRead (już niedługo będzie mowa na temat metod read) i że wywołuje metodę write o nazwie SetFlashRate. Metoda write może nosić dowolną nazwę, jednak tradycyjne przyjmuje się że jest nią połączenie nazwy właściwości ze słowem Set (ang. ustaw).

Kiedy właściwość jest zapisywana (przypisywana jest jej wartość), automatycznie wywoływana jest należąca do niej metoda write. Metoda write musi być procedurą posiadającą jeden parametr. Typ parametru musi być zgodny z typem samej właściwości. Przykładowo, deklaracja metody write dla właściwości FlashRate wyglądałaby następująco:

procedure SetFlashRate(AFlashRate : Integer);

Wartość przekazywana metodzie write jest wartością, która została przypisana właściwości. Stąd następująca linia:

FlashingLabel.FlashRate := 1000;

spowoduje przekazanie funkcji SetFlashRate wartości 1000. To co należy zrobić z tą wartością wewnątrz metody write zależy od szerokiego wachlarza czynników. W najprostszym przypadku, przekazaną wartość przypisuje się skojarzonemu polu danych - metoda write wygląda wtedy mniej więcej tak:

procedure TFlashingLabel.SetFlashRate(AFlashRate : Integer);

begin

FFlashRate := AFlashRate;

{ Inne operacje. }

end;

0x01 graphic

Kolejną tradycją nazewnictwa w Delphi jest stosowanie litery A na początku nazwy parametru metody write.

Niemal zawsze oprócz przypisania wartości do pola skojarzonego trzeba będzie wykonać również inne operacje. Jeżeli chcemy tylko przypisać wartość polu danych skojarzonemu z właściwością, nie trzeba tworzyć metody write. Wyjaśnienie tej sytuacji znajdzie się za chwilę. Wcześniej jednak przyjrzyjmy się metodom read.

Właściwości mogą posiadać metody read

Metoda read działa analogicznie do metody write - jest ona wywoływana podczas czytania wartości właściwości, a zwrócony przez nią wynik jest właśnie wartością właściwości.

Nazwa metody read odpowiada nazwie właściwości poprzedzonej słowem Get (ang. pobierz). Metoda read jest funkcją nie posiadającą żadnych parametrów i zwracającą dane typu właściwości. Przykładowo, jeżeli mielibyśmy skorzystać z metody read właściwości FlashRate, jej deklaracja wyglądałby następująco:

function GetFlashRate : Integer;

Metoda read może wykonać pewne zadanie, a następnie zwrócić wartość skojarzonego pola danych - w tym przypadku FFlashRate. Czytanie właściwości może odbywać się na wiele różnych sposobów. Czasami jest to rezultat przypisania do zmiennej:

Rate := FlashingLabel.FlashRate;

W innych przypadkach jest to część większego wyrażenia:

case FlashingLabel.FlashRate of

1000 : SpeedUp;

{itd. }

end;

Niezależnie od tego, w jaki sposób właściwość jest czytana, metoda read jest wywoływana za każdym razem gdy odczyt ten ma miejsce.

Zwróć uwagę, że trochę wcześniej padło stwierdzenie: jeżeli korzystamy z metody read. Często nie będziesz korzystał z metody read, a zamiast tego sięgniesz bezpośrednio do wartości pola skojarzonego z właściwością, aby odczytać jej wartość. Mechanizmowi dostępu bezpośredniego przyjrzymy się w następnej kolejności.

Właściwości mogą korzystać z dostępu bezpośredniego

Nie trzeba obowiązkowo korzystać z metod read i write. Przypisując wartość pola skojarzonego lub odczytując tę wartość można skorzystać z dostępu bezpośredniego. Deklaracja właściwości stosującej dostęp bezpośredni wygląda następująco:

property FlashRate : Integer

read FFlashRate write FFlashRate;

Ten fragment kodu informuje kompilator, że pole danych (FFlashRate) jest wykorzystywane przez oba specyfikatory read i write. Podczas zapisywania wartości, zmieniane jest pole danych i oprócz tego nic więcej się nie dzieje. Kiedy właściwość jest odczytywana, zwracana jest wartość pola skojarzonego. Tak właśnie wygląda mechanizm dostępu bezpośredniego.

0x01 graphic

Zazwyczaj przy odczytywaniu właściwości stosowany jest dostęp bezpośredni, natomiast przy zapisywaniu metoda write. Przyjrzyj się poprzedniemu przykładowi deklaracji właściwości:

property FlashRate : Integer

read FFlashRate write SetFlashRate;

W tym przypadku stosowany jest dostęp bezpośredni przy odczytywaniu, podczas gdy do zapisywania wykorzystywana jest metoda write. Zapisywanie właściwości często powoduje efekty uboczne, o czym była mowa w poprzedniej sekcji. Właśnie możliwość tworzenia efektów ubocznych jest źródłem potęgi właściwości. Do wymuszenia efektów ubocznych podczas zapisywania właściwości stosuje się metodę write. Odczytywanie właściwości sprowadza się zwykle do zwrócenia wartości skojarzonego pola danych. W tym przypadku dostęp bezpośredni wydaje się najlepszym rozwiązaniem.

Właściwości mogą być tylko do odczytu lub tylko do zapisu

Właściwość może być określona jako tylko do odczytu (ang. read-only) lub tylko do zapisu (ang. write-only). Użyteczną cechą jest nadawanie właściwościom atrybutu tylko do odczytu (VCL posiada wiele właściwości tego typu). W ten sposób można na przykład tworzyć właściwości, które użytkownik będzie mógł odczytywać, ale bez możliwości modyfikacji. Mogą zaistnieć sytuacje, kiedy modyfikacja pola odniesie negatywne skutki w stosunku do komponentu, dlatego należy temu przeciwdziałać.

Nadanie właściwości atrybutu tylko do odczytu jest prostą operacją - wystarczy w jej deklaracji pominąć specyfikator write:

property FlashRate : Integer read FFlashRate;

Jeżeli użytkownik spróbuje zapisać właściwość, która została zadeklarowana jako tylko do odczytu, otrzyma błąd kompilatora Cannot assign to a read-only property (nie można zapisywać właściwości tylko do odczytu). Jak więc widać, uczynienie z właściwości elementu tylko do odczytu jest bardzo proste.

Omijając specyfikator read można nadać właściwości atrybut tylko do zapisu. Trudno jednak sobie wyobrazić przeznaczenie właściwości którą można zapisywać, ale której nie można odczytywać, mimo to właściwość taka jest możliwa do utworzenia.

Właściwości mogą posiadać wartości domyślne

Kolejną użyteczną cechą właściwości są wartości domyślne. Jak mogłeś się już przekonać, po umieszczeniu komponentu w formularzu wiele z jego właściwości wyświetlanych w oknie Inspektora Obiektów posiada z góry ustalone wartości. Są wartości domyślne zdefiniowane przez twórcę komponentu. Jeżeli jest to możliwe, wszystkie właściwości powinny posiadać wartość domyślną. Dzięki temu użytkownik może zmienić tylko interesujące go właściwości, a resztę pozostawić bez zmian. Niektóre typy właściwości (np. łańcuchowe) nie mogą posiadać wartości domyślnych, jednak większość z nich jest do tego zdolna.

0x01 graphic

Właściwości łańcuchowe nie mogą posiadać wartości domyślnych.

Podobnie jak metody read i write, wartość domyślna jest ustawiana w chwili deklaracji właściwości. Wróćmy do właściwości FlashRate. Deklaracja tej właściwości z wartością domyślną wyglądałaby następująco:

property FlashRate : Integer

read FFlashRate write SetFlashRate default 800;

Teraz, kiedy komponent FlashingLabel zostanie wyświetlony w Inspektorze Obiektów, obok właściwości FlashRate pojawi się wartość 800 (oznaczająca tutaj liczbę milisekund).

0x01 graphic

Ustawienie domyślnej wartości właściwości spowoduje jedynie wyświetlenie jej w oknie Inspektora Obiektów. Nie zostanie natomiast ustawiona wartość pola skojarzonego właściwości. Programista musi oprócz tego przypisać wartość domyślną polu danych w konstruktorze komponentu. Dla przykładu, konstruktor komponentu FlashingLabel wyglądałby następująco:

constructor TFlashingLabel.Create(AOwner : TComponent);

begin

inheritance

FFlashRate := 800;

{ Inne operacje. }

end;

Zadbaj o ustawienie odpowiednich wartości wszystkich pól danych klasy, które odnoszą się do właściwości posiadających wartość domyślną.

Jeżeli nie chcesz stosować wartości domyślnej dla właściwości, w jej deklaracji pomiń specyfikator default.

Wartość domyślna właściwości istnieje w kontekście przechowywania komponentu w pliku zasobu (w przypadku formularza plikiem takim jest plik .DFM); jeżeli w momencie zapisu ma ona wartość równą domyślnej (tj. określonej w dyrektywie default) nie zostania zapisana do zasobu. W rezultacie tego późniejszy odczyt komponentu z zasobu nie zmieni nazwy właściwości, czyli pozostawi tę, którą nadał konstruktor (stąd wymaganie, aby w ramach konstruktora nadawane były właściwościom ich wartości domyślne).

Klasa pochodna może zmienić wartość domyślną właściwości z klasy macierzystej, może również anulować sam fakt posiadania wartości domyślnej przez właściwość - temu ostatniemu celowi służy dyrektywa nodefault.

Właściwości mogą być publikowane, publiczne lub prywatne

Niektóre właściwości są dostępne w czasie projektowania. Właściwości takie mogą być modyfikowane w czasie projektowania poprzez Inspektor Obiektów, ale również i w czasie wykonania programu. Są to właściwości publikowane. Ujmując to najprościej, właściwość publikowana to taka właściwość, która jest widoczna w oknie Inspektora Obiektów w czasie projektowania. Wszelkie właściwości zlokalizowane w sekcji published deklaracji klasy komponentu będą wyświetlane przez Inspektora Obiektów w czasie projektowania.

Inne właściwości, noszące miano publicznych, są dostępne wyłącznie w czasie wykonania programu (ang. runtime-only). Nie można uzyskać do nich dostępu w czasie projektowania (nie są widoczne w Inspektorze Obiektów). Właściwości tego typu są deklarowane w sekcji public deklaracji klasy komponentu.

Właściwości prywatne są wykorzystywane w sposób wewnętrzny przez komponent i dlatego są niedostępne dla użytkowników. Właściwości prywatne są deklarowane w sekcjach private lub protected deklaracji klasy komponentu.

Oto zapowiadany przykład właściwości obywającej się bez skojarzonego pola danych:

property UserName: string read GetuserName write SetUserName;

procedure SetUserName(AUserName:String);

var

F: TextFile;

begin

try

AssignFile(F,'USERNAME.DAT');

Rewrite(F);

Writeln(F,AUserName);

finally

CloseFile(F);

end;

end;

Function GetUserName : String;

var

F: TextFile;

WUSerName: String;

begin

GetUserName := '';

try

AssignFile(F,'USERNAME.DAT');

Reset(F);

Readln(F,WUserName);

GetUserName := WUserName;

finally

Close(F);

end;

end;

Jak łatwo zauważyć, wartość właściwości magazynowana jest w pliku tekstowym, nie w polu obiektu.

Pisanie metod dla komponentów

Pisanie metod dla komponentów nie różni się niczym od tworzenia metod dla dowolnych innych klas Object Pascala. Metody komponentu mogą być prywatne, chronione lub publiczne; opłaca się zwracać uwagę na poziomy dostępu w trakcie pisania komponentów.

Określenie, które z metod powinny być publiczne, jest bardzo proste. Metody publiczne przeznaczone są do wywoływania przez użytkowników, w celu wykonania określonej akcji przez komponent. Trudniejsza jest decyzja dotycząca wyboru między metodą chronioną (ang. protected), a prywatną (ang. private). Dopiero dalsza praktyka w programowaniu pozwoli na lepsze rozpoznawanie sytuacji, kiedy dostęp chroniony powinien być stosowany zamiast prywatnego.

Mówiąc ogólnie, metody prywatne przeznaczone są dla wewnętrznych zadań komponentu i nie powinny być dostępne dla klas pochodnych. Metody chronione stosuje się natomiast w przypadku zadań wykonywanych wewnątrz komponentu, które mogą jednak zostać zmodyfikowane w klasach pochodnych dla rozszerzenia funkcjonalności komponentu.

0x01 graphic

Metody read i write są zazwyczaj ustawiane jako chronione. W ten sposób zezwala się klasom pochodnym komponentu na ich modyfikacje przez przesłanianie.

Nadawanie funkcjonalności komponentowi TFlashingLabel

W dalszej części tego rozdziału omówione zostaną zdarzenia oraz sposób ich pisania, na razie jednak poznałeś wystarczająco wiele informacji, aby móc napisać swój pierwszy komponent. FlashingLabel posiada następujące cechy:

Najpierw przyjrzyj się kompletnemu modułowi komponentu FlashingLabel. Później przejdziemy do omówienia tego co dzieje się wewnątrz kodu. Moduł komponentu został przedstawiony na listingu 20.2.

Listing 20.2. FlashingLabel.pas

unit FlashingLabel;

interface

{$R Flashing.dcr}

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls,

Forms, Dialogs, StdCtrls, ExtCtrls;

type

TFlashingLabel = class(TCustomLabel)

private

{ Private declarations }

FFlashEnabled : Boolean;

FFlashLimit : Integer;

Timer : TTimer;

protected

{ Protected declarations }

{ Chronione metody write, przeznaczone dla właściwości }

procedure SetFlashEnabled(AFlashEnabled : Boolean);

procedure SetFlashRate(AFlashRate : Integer);

{ Funkcja obsługująca zdarzenie OnTimer }

procedure OnTimer(Sender : TObject); virtual;

public

{ Public declarations }

constructor Create(AOwner : TComponent); override;

published

{ Published declarations }

{ Właściwości komponentu. }

property FlashEnabled : Boolean

read FFlashEnabled write SetFlashEnabled default True;

property FlashRate : Integer

read FFlashRate write SetFlashRate default 800;

{ Deklaracje właściwości klasy TCustomLabel. }

property Align;

property Alignment;

property AutoSize;

property BiDiMode;

property Caption;

property Color;

property Constraints;

property DragCursor;

property DragKind;

property DragMode;

property Enabled;

property FocusControl;

property Font;

property ParentBiDiMode;

property ParentColor;

property ParentFont;

property ParentShowHint;

property PopupMenu;

property ShowAccelChar;

property ShowHint;

property Transparent;

property Layout;

property Visible;

property WordWrap;

property OnClick;

property OnDblClick;

property OnDragDrop;

property OnDragOver;

property OnEndDock;

property OnEndDrag;

property OnMouseDown;

property OnMouseMove;

property OnMouseUp;

property OnStartDock;

property OnStartDrag;

end;

procedure Register;

implementation

constructor TFlashingLabel.Create(AOwner : TComponent);

begin

inherited;

{ Przypisanie wartości domyślnych polom danych. }

FFlashEnabled := True;

FFlashRate := 800;

{ Inicjalizacja obiektu timera. }

Timer := TTimer.Create(Self);

{ Ustawienie przedziału timera przy użyciu wsp. migotania. }

Timer.Interval := FFlashRate;

{ Przypisanie własnej funkcji obsługującej zdarzenie OnTimer. }

{ do zdarzenia OnTimer. }

Timer.OnTimer := OnTimer;

end;

procedure TFlashingLabel.SetFlashEnabled(AFlashEnabled : Boolean);

begin

{ Ustawienie pola FFlashEnabled. }

FFlashEnabled := AFlashEnabled;

{ Nie uruchamiaj timera, jeżeli komponent w formularzu }

{ znajduje się w trybie projektowania. Powrót z procedury. }

if csDesigning in ComponentState then

Exit;

{ Uruchomienie timera. }

Timer.Enabled := FFlashEnabled;

{ Jeżeli migotanie zostało wyłączone trzeba upewnić się, }

{ że etykieta jest widoczna. }

if not FFlashEnabled then

Visible := True;

end;

procedure TFlashingLabel.SetFlashRate(AFlashRate : Integer);

begin

{ Ustawienie pola FFlashRate i przedziału czasowego timera. }

FFlashRate := AFlashRate;

Timer.Interval := AFlashRate;

end;

procedure TFlashingLabel.OnTimer(Sender : TObject);

begin

{ Jeżeli komponent znajduje się w formularzu w trybie }

{ projektowania, następuje zatrzymanie timera i powrót. }

if csDesigning in ComponentState then begin

Timer.Enabled := False;

Exit;

end;

{ Zmiana stanu właściwości Visible, za każdym razem kiedy }

{ pojawia się zdarzenie timera. }

Visible := not Visible;

end;

procedure Register;

begin

RegisterComponents('Samples', [TFlashingLabel]);

end;

end.

Deklaracja klasy

Analiza

Przyjrzyjmy się najpierw deklaracji klasy. W sekcji private deklarowane są trzy pola. Pierwsze dwa, FFlashEnabled i FFlashRate, są polami związanymi z właściwościami FlashEnabled i FlashRate. Trzecia deklaracja wygląda następująco:

Timer : TTimer;

Jest to deklaracja wskaźnika do obiektu klasy TTimer. Obiekt tej klasy jest wykorzystywany do regulowania częstotliwości migotania.

0x01 graphic

Do tej pory nie było jeszcze mowy o komponencie Timer, uosabiającym mechanizmy zegarowe systemu. Funkcjonowanie zegara systemowego przejawia się w ten sposób, że co pewien określony czas do określonego okna (będącego właścicielem komponentu zegarowego) wysyłany jest komunikat WM_TIMER. W Delphi mechanizm ten reprezentowany jest przez (niewidoczny) komponent klasy TTimer; częstotliwość wysyłania komunikatu WM_TIMER określa (w milisekundach) właściwość Interval, zaś reakcją na otrzymanie tegoż komunikatu jest zdarzenie OnTimer.

Należy zaznaczyć, że komunikat WM_TIMER posiada niski priorytet i może zostać odłożony na później, jeżeli system jest zajęty. Z tego powodu standardowe timery nie mogą być wykorzystywane do operacji silnie uwarunkowanych czasowo, jednak do zastosowań mniej krytycznych (jak właśnie sterowanie migotaniem tekstu) nadają się doskonale.

Sekcja protected deklaracji klasy zawiera deklaracje metod typu write dla właściwości FlashRate i FlashEnabled. W sekcji tej znajduje się również deklaracja procedury obsługi zdarzenia OnTimer. Procedura ta jest chroniona i zadeklarowana jako wirtualna (słowo kluczowe virtual), dzięki temu klasy potomne mogą przesłaniać ją w celu przeprowadzenia dowolnych innych operacji, kiedy pojawi się zdarzenie zegarowe. Przykładowo, w klasie pochodnej może zajść potrzeba zmiany koloru etykiety przy każdym jej mignięciu. Uczynienie metody wirtualną umożliwia jej przesłanianie w celu dodania niezbędnej funkcjonalności.

Sekcja published

Sekcja ta zwiera deklaracje właściwości FlashEnabled i FlashRate. W przypadku właściwości FlashEnabled specyfikator read korzysta z dostępu bezpośredniego, specyfikator write jest natomiast ustawiony na metodę SetFlashEnabled, a wartość domyślna jest ustawiana na True. Właściwość FlashRate oparta jest na podobnej konstrukcji.

Za tymi dwiema właściwościami zadeklarowane zostały wszystkie właściwości klasy TCustomLabel, które uznałem za warte publikacji. Pominięcie tego kroku oznaczałoby, że standardowe właściwości komponentu etykiety nie byłyby dostępne dla klasy w trakcie pracy programu, jak i później w czasie projektowania (po instalacji komponentu w Palecie Komponentów).

Sekcja implementacyjna

Przejdźmy teraz do sekcji implementacyjnej. Jej pierwszym elementem jest konstruktor Create klasy TFlashingLabel. Na samym jego początku widać przypisanie wartości domyślnych dwóm polom danych, reprezentujących właściwości FlashEnabled i FlashRate. Wcześniej zadeklarowane zostały wartości domyślne tych właściwości, ma to jednak wpływ jedynie na sposób prezentacji ich przez Inspektora Obiektów. Rzeczywistą wartość właściwości trzeba przypisać im w konstruktorze.

Przyjrzyj się następującym trzem liniom kodu (pozbawionym komentarzy):

Timer := TTimer.Create(Self);

Timer.Interval := FFlashRate;

Timer.OnTimer := OnTimer;

W pierwszej linii tworzony jest obiekt klasy TTimer. Następnie wartość pola danych FFlashRate jest przypisywana właściwości Interval komponentu Timer - jest to ustawienie przedziału czasowego zegara, którego „tyknięcia” sterować będą migotaniem tekstu na ekranie. Ostatnia linia przypisuje procedurę OnTimer do zdarzenia OnTimer obiektu. Dzięki temu mamy pewność, że w chwili pojawienia się zdarzenia zegarowego komponent zostanie o tym poinformowany.

Procedura SetFlashEnabled jest procedurą write dla właściwości FlashEnabled. Przypisuje ona polu FFlashEnabled wartość AFlashEnabled. Następnie właściwość Enabled obiektu Timer jest odpowiednio ustawiana w zależności od wartości parametru AFlashEnabled. Poprzez modyfikację właściwości FlashEnabled użytkownik komponentu może włączać lub wyłączać migotanie tekstu. W procedurze znajduje się również linia kodu, która ustawia właściwość Visible na wartość True kiedy migotanie tekstu jest wyłączone. Dzięki temu przeciwdziała się sytuacjom, kiedy migotanie jest wyłączone w przedziale czasu między kolejnymi „błyskami” tekstu, co w rezultacie sprawia że tekst pozostaje permanentnie niewidoczny.

Procedura SetFlashRate

Procedura SetFlashRate jest procedurą write dla właściwości FlashRate. Użytkownik modyfikując tę właściwość wpływa na szybkość migotania tekstu. (Wartość rzędu 1200 oznacza wolne migotanie, wartość 150 bardzo szybkie.) W procedurze znajdują się następujące linie kodu:

FFlashRate := AFlashRate;

Timer.Interval := AFlashRate;

Jak widać, procedura przypisuje po prostu wartość AFlashRate najpierw polu danych FFlashRate, a następnie właściwości Interval obiektu Timer. W ten sposób, kiedy użytkownik zmieni tempo migotania w czasie pracy programu, zmieni się odpowiednio szybkość migotania.

Metoda OnTimer nie wymaga wiele wyjaśnień. Jej wywołanie następuje w odpowiedzi na zdarzenie OnTimer. Nie robi ona nic poza przełączaniem stanu widzialności etykiety za każdym razem, kiedy pojawi się zdarzenie zegarowe. Inaczej mówiąc, jeżeli wartość FlashRate wynosiłaby 1000, zdarzenie to pojawiałoby się niemal co sekundę. W chwili pojawienia się pierwszego zdarzenia tekst zostaje ukryty. Sekundę później, następne zdarzenie powoduje wyświetlenie tekstu. Cykl taki powtarza się do momentu, kiedy właściwość FlashEnabled zostanie ustawiona na wartość False lub zakończona zostanie aplikacja.

Na samym końcu modułu następuje rejestracja komponentu.

Wpisz kod listingu 20.1 do pliku FlashingLabel.pas utworzonego przez Delphi (kiedy skorzystałeś z okna dialogowego nowego komponentu). Możesz pominąć wszelkie komentarze, jeśli chcesz. Za chwilę przekonasz się w jaki sposób można przetestować komponent, aby przekonać się czy działa on poprawnie.

0x01 graphic

Deklarowanie metod read i write jest znacznie ułatwione dzięki mechanizmowi uzupełniania klas (class completion). Załóżmy, dla przykładu, że wpisana została następująca deklaracja właściwości:

property FlashRate : Integer

read FflashRate write SetFlashRate

Następną operacją byłoby prawdopodobnie zdefiniowanie metody SetFlashRate. Okazuje się, że Delphi może zrobić to za nas. Wystarczy nacisnąć kombinację klawiszy Ctrl+Shift+C, a utworzone zostaną niezdefiniowane jeszcze metody read i write, oprócz tego Delphi doda również deklarację pola danych (w tym przykładzie FFlashRate).

0x01 graphic

Jeżeli posiadasz Delphi w wersji Professional lub Client/Server, możesz skopiować elementy wymagające ponownej deklaracji bezpośrednio z kodu źródłowego VCL. Otwórz plik \Delphi 4\Source\VCL\ StdCtrl.pas, skopiuj listę właściwości z deklaracji klasy TLabel i wklej ją do własnej deklaracji klasy.

Właściwość ComponentState

Wybiegam tutaj trochę w przyszłość, ale tylko dlatego, iż chcę wskazać fragment kodu pominięty podczas analizy listingu 20.2. We wnętrzu funkcji SetFlashEnabled znajduje się jedna ważna linia, która wymaga wyjaśnienia:

if csDesigning in ComponentSatate then

Exit;

Kod ten sprawdza, czy komponent jest wykorzystywany w formularzu na etapie projektowania. Jeżeli tak, trzeba zapobiec uruchomieniu timera. Po umieszczeniu komponentu w formularzu nie musi on posiadać koniecznie pełnej funkcjonalności - Projektant Formularzy nie jest w stanie w pełni odwzorować pracującego programu. W tym przypadku nie chcemy, aby timer pracował, gdy komponent jest modyfikowany w czasie projektowania.

0x01 graphic

Można napisać komponent TFlashingLabel w taki sposób, aby etykieta migotała zarówno w czasie wykonania, jak i w czasie projektowania. Jednak łatwiej jest w tej chwili zaniedbać to zagadnienie, co z kolei daje nam szansę rozważenia właściwości ComponentState.

Wszystkie komponenty posiadają właściwość o nazwie ComponentState. Właściwość ta jest zbiorem informującym między innymi o tym, czy komponent jest wykorzystywany w czasie projektowania. Jeżeli w zbiorze znajduje się wartość csDesigning, wiadomo na pewno, że komponent jest używany przez Projektanta Formularzy. W naszym przypadku, po stwierdzeniu iż komponent znajduje się w trybie projektowania, następuje opuszczenie procedury OnTimer bez uruchomienia timera.

W związku z tym powyższy fragment kodu sprawdza, czy komponent jest używany w formularzu przez Projektanta Formularzy. Jeżeli tak jest, timer jest wyłączany, a metoda kończy się z pominięciem pozostałego kodu. Domyślnie, timer zostanie uruchomiony w konstruktorze klasy TFlashingLabel, dlatego musi zostać niezwłocznie wyłączony, jeżeli komponent znajduje się w trybie projektowania.

Testowanie komponentu

Ostatnim krokiem będzie dodanie komponentu do Palety Komponentów. Wcześniej trzeba jednak przeprowadzić test, aby upewnić się, ze komponent kompiluje się poprawnie i funkcjonuje zgodnie z naszymi intencjami. Jest to istotny krok projektowania komponentu, często pomijany przez wielu twórców komponentów. Nie ma powodu do spieszenia się z dodaniem nowego komponentu do palety. Najpierw trzeba upewnić się, iż komponent zachowuje się zgodnie z naszymi oczekiwaniami, a dopiero potem martwić się o jego dodanie do Palety Komponentów.

Aby przetestować komponent, należy napisać aplikację, która będzie służyła jako środowisko testowe. Ponieważ komponentu nie da się dodać poprzez upuszczenie (z Palety Komponentów), trzeba utworzyć go w sposób ręczny. W omawianym przypadku, komponent FlashingLabel posiada dwie właściwości, w związku z tym należy upewnić się, że obie działają poprawnie.

Z tego powodu program testujący będzie musiał włączać i wyłączać tryb migotania. Dodatkowo program powinien umożliwić ustawienie kilku współczynników migotania, aby przekonać się czy właściwość FlashRate spełnia swoje zadanie. Pracujący program testujący został przedstawiony na rysunku 20.1. Jest to przykład tego co chcemy osiągnąć.

Rysunek 20.2.

Program testujący w  czasie pracy

0x01 graphic

Przekonałeś się już, jak powinien wyglądać program testujący, teraz możesz przystąpić do pracy nad nim. Jak zwykle, zacznij od pustego formularza, umieść w nim pole wyboru oraz ramkę grupującą przyciski opcji (tak jak na rysunku 20.2):

  1. Zmień właściwość Name formularza na MainForm oraz właściwość Caption na FlashingLabel Test Program.

  1. Korzystając z rysunku 20.2 jako wzoru, dodaj do formularza komponent CheckBox. Zamień jego właściwość Name na FlashBox, właściwość Caption na Flash i właściwość Checked na True.

  2. Kliknij podwójnie na pole wyboru, aby utworzyć procedurę obsługującą zdarzenie OnClick. We wnętrzu funkcji wpisz następujący kod (komponent FlasingLabel będzie nosił nazwę Flasher):

Flasher.FlashEnabled := FlashBox.Chceked;

Dzięki tej linii kodu migotanie etykiety będzie włączane lub wyłączane w zależności od stanu pola wyboru.

  1. Umieść w formularzu komponent RadioGroup. Zmień jego właściwość Name na Group oraz właściwość Caption na Flash Speed.

  2. Kliknij dwukrotnie na kolumnie Value obok właściwości Items. Po otwarciu Edytora Łańcuchów wpisz następujące wartości:

Slow

Medium

Fast

Light Speed

Zamknij Edytor Łańcuchów kliknięciem na przycisku OK; wpisane wartości pojawią się w ramce grupującej jako przyciski opcji.

  1. Właściwości ItemIndex nadaj wartość 1. Wybrany zostanie przycisk Medium.

  2. Kliknij podwójnie na komponencie ramki grupującej. Edytor Kodu wyświetli procedurę obsługującą zdarzenie OnClick tego komponentu. Od miejsca, w którym znajduje się kursor wpisz poniższy kod:

case Group.ItemIndex of

0 : Flasher.FlashRate := 1200;

1 : Flasher.FlashRate := 800;

2 : Flasher.FlashRate := 400;

3 : Flasher.FlashRate := 150;

end;

Dzięki temu właściwość FlashRate komponentu będzie odpowiednio ustawiana w zależności od wybranego przycisku opcji.

  1. Zapisz projekt w tym samym katalogu, w którym znajduje się projekt komponentu TFlashingLabel. Moduł głównego formularza zapisz pod nazwą FlashTstU, a sam projekt pod nazwą FlashTst. (Na potrzeby kodu książki stosowane są krótkie nazwy plików, nic nie stoi jednak na przeszkodzie abyś nadał plikom dowolne nazwy.)

Teraz można dodać sam komponent. Ponieważ nie jest on jeszcze komponentem wizualnym (nie można dodać go z Palety Komponentów), trzeba zrobić to w sposób manualny.

  1. Kliknij na przycisku Add to Project (dostępnym na paku narzędzi, w menu lub w menu kontekstowym Menedżera Projektów). Kiedy wyświetlone zostanie okno dialogowe dodania do projektu (Add to project), wybierz plik FlashingLabel.pas i kliknij na przycisk OK.

  1. Dodaj moduł FlashingLabel do listy modułów (uses) aplikacji.

  2. W sekcji prywatnej (private) deklaracji klasy MainForm dodaj:

Flasher : TFlashingLabel;

  1. Kliknij podwójnie na tle formularza, aby utworzyć dla niego procedurę obsługującą zdarzenie OnCreate - umieść wewnątrz niej poniższy fragment kodu:

Flasher := TFlashingLabel.Create(Self);

Flasher.Parent := Self;

Flasher.SetBounds(20, 20, 200, 20);

Flasher.Font.Size := 16;

Flasher.Caption := 'This is a test';

Flasher.FlashRate := 800;

Teraz jesteś już w pełni gotowy do przetestowania komponentu. Kliknięciem na przycisku Run skompiluj i uruchom program testujący. Jeżeli napotkasz jakiekolwiek błędy kompilatora, sprawdź dokładnie wpisany przez siebie kod i usuń usterki wskazane przez kompilator.

Po uruchomieniu programu kliknij kilkakrotnie na polu wyboru Flash, włączając i wyłączając w ten sposób migotanie etykiety. Zmień współczynnik migotania wybierając dowolny z przycisków opcji. Jak widać - wszystko działa. Moje gratulacje, właśnie stworzyłeś swój pierwszy komponent.

Dodawanie komponentu
do Palety Komponentów

Kiedy wiemy już że komponent działa poprawnie i jesteśmy zadowoleni ze sposobu jego działania, możemy dodać go do Palety Komponentów. Aby dodać komponent do palety, należy wybrać polecenie Component | Install Component. Na ekranie pojawi się okno dialogowe instalacji komponentu, umożliwiające dodanie komponentu do pakietu (rysunek 20.3).

Rysunek 20.3.

Okno dialogowe instalacji komponentu

0x01 graphic

Aby zainstalować swój komponent wykonaj następujące kroki:

  1. Wybierz polecenie Component | Install Component. Wyświetlone zostanie okno instalacji komponentu.

  1. Kliknij na przycisk Browse znajdujący się po prawej stronie pola edycji Unit file name. Zlokalizuj plik FlashingLabel.pas i kliknij w przycisk Open.

  2. Spójrz teraz na pole Package file name - powinno ono zawierać nazwę DCLUSR40.DPK; jeżeli tak nie jest, kliknij na przycisk rozwinięcia i wybierz ten plik z listy. Jeżeli pliku DCLUSR40.DPK nie ma na liście, użyj przycisku Browse, aby go zlokalizować (szukaj w katalogu \Delphi 4\Lib).

  3. Używając przycisku OK zamknij okno instalacji komponentu. Delphi wyświetli komunikat informujący o zamiarze zbudowania i zainstalowania pakietu. Kliknij przycisk Yes, aby kontynuować.

  4. Delphi zbuduje i zainstaluje pakiet. Zakończenie procesu zostanie zasygnalizowane wyświetleniem okna informującego o zarejestrowaniu komponentu TFlashingLabel.

Zainstalowany komponent pojawi się na stronie Samples Palety Komponentów. Przejdź na tę stronę, a zobaczysz przycisk ze standardową ikoną dodany przez Delphi. Jeżeli zatrzymasz na chwilę kursor myszy nad tym przyciskiem, wyświetlona zostanie podpowiedź o treści FlashingLabel.

Rozpocznij nowy projekt i przetestuj komponent umieszczając go w formularzu. Zauważ, że wszystkie standardowe właściwości komponentu Label są dostępne w Inspektorze Obiektów razem z właściwościami FlashRate i FlashEnabled. Ponadto dla tych dwóch właściwości wyświetlane są wartości domyślne, które wyspecyfikowałeś wcześniej.

Wyjaśnijmy jeszcze co zrobiłeś w kroku trzecim. Delphi posiada pakiet domyślny o nazwie DCLUSR40, który może zostać użyty do instalowania indywidualnych komponentów. Nakazałem Ci zainstalowanie komponentu TFlashingLabel w tym pakiecie głównie z tego powodu, iż jest to komponent pojedynczy (nie będący częścią całej biblioteki komponentów), a właśnie do tego służy ten pakiet. Mógłbyś stworzyć nowy pakiet zamiast korzystać z DCLUSR40, ale lepiej jest korzystać z pakietu dostępnego.

Dodawanie własnej bitmapy do przycisku komponentu

Być może zwróciłeś uwagę na drobny mankament nowo dodanego komponentu: przycisk reprezentujący go na Palecie Komponentów wyposażony jest w standardową bitmapę Delphi. Na szczęście można samemu określić bitmapę dla nowego komponentu. W tym celu trzeba utworzyć bitmapę i umieścić ją w skompilowanym pliku zasobów (.dcr).

0x01 graphic

Czasami można wziąć przycisk reprezentujący klasę bazową i zmodyfikować go odrobinę tak, aby mógł reprezentować nowo utworzony komponent. W takim przypadku, należy uruchomić Edytor Graficzny i otworzyć jeden z plików .dcr rezydujących w katalogu \Delphi 4\Lib\Obj. Znalezienie interesującej nas bitmapy wymaga przeprowadzenia drobnych poszukiwań. Dla przykładu bitmapa komponentu Label znajduje się w pliku Stdreg.dcr. Otwórz ten plik, skopiuj bitmapę TLABEL do Schowka, utwórz nowy plik zasobów i wklej do niego bitmapę znajdującą się w Schowku. Nową bitmapę nazwij TFLASHINGLABEL. Zmodyfikuj nową bitmapę według własnego uznania, a następnie zapisz projekt zasobów.

Bitmapa reprezentująca komponent musi posiadać rozmiar 24 × 24 piksele. W większości wypadków optymalnym rozwiązaniem będzie bitmapa 16-kolorowa. Projektując bitmapę należy pamiętać, że kolor piksela znajdującego się w lewym dolnym rogu jest traktowany przez Delphi jako kolor przezroczysty. (W bitmapach Delphi jako kolor przezroczysty stosowany jest ciemnożółty, więc również i Ty możesz przestrzegać tej konwencji.)

0x01 graphic

Tworząc nowy zasób nie pomyl bitmapy z ikoną. Przyciski znajdujące się na Palecie Komponentów są często nazywane ikonami, ale reprezentujące je obrazy są bitmapami, nie ikonami.

Po utworzeniu pliku zasobów Delphi automatycznie doda bitmapę komponentu do palety w chwili instalowania pakietu. Aby tak się jednak stało, trzeba przestrzegać specyficznej konwencji nazewnictwa bitmapy.

Plik zasobów musi zawierać zasób w postaci bitmapy który dokładnie odpowiada nazwie klasy komponentu. Przykładowo, aby stworzyć bitmapę dla przycisku reprezentującego komponent FlashingLabel, trzeba w Edytorze Graficznym zbudować plik zasobów, w którym znajdzie się bitmapa o nazwie TFLASHINGLABEL. Nazwa pliku zasobów może być dowolna.

Kiedy plik zasobów jest już gotowy, trzeba nakazać kompilatorowi połączenie go z kodem komponentu. W tym celu trzeba dodać następującą linię kodu do kodu źródłowego komponentu:

{$R Flashing.res}

Dyrektywa kompilatora $R poleca kompilatorowi włączenie zawartości pliku zasobów do skompilowanego kodu modułu. Teraz trzeba ponownie zbudować pakiet. Jeżeli wszystko wykonałeś w sposób prawidłowy, stworzona przez Ciebie bitmapa pojawi się na Palecie Komponentów.

0x01 graphic

Zwróć uwagę na rozszerzenie pliku zastosowane w powyższej linii kodu - jest nim .res. Rozszerzenie .res jest stosowane zamiennie z .dcr. Niektórzy dostawcy komponentów stosują unikalną konwencję nazewnictwa dla swoich skompilowanych zasobów i nie używają żadnego z rozszerzeń .res, czy .dcr. Rozszerzenie pliku nie ma znaczenia dla dyrektywy kompilatora $R. Ważne jest, aby plik zawierał zasoby w prawidłowej postaci.

Alternatywnie można również dodać dyrektywę $R bezpośrednio do kodu źródłowego pakietu. Jednak w większości przypadków nie jest to konieczne.

0x01 graphic

Skompilowane zasoby mogą być dodane tylko w jednym miejscu. Dyrektywa $R może znaleźć się albo w kodzie źródłowym pakietu, albo w module komponentu, nie możne jednak znaleźć się w obu tych miejscach jednocześnie. Jeżeli te same zasoby zostaną dodane więcej niż raz, kompilator wygeneruje błąd, a pakiet nie zostanie zainstalowany.

0x01 graphic

Jeżeli w bibliotece znajduje się kilka komponentów, wystarczy utworzyć jeden plik zasobów dla wszystkich bitmap komponentów. Nie trzeba tworzyć oddzielnych plików zasobów dla każdego komponentu w bibliotece.

Obsługa zdarzeń komponentów

Pisanie zdarzeń wymaga zaplanowania kilku rzeczy. Mówiąc o zdarzeniach, mam na myśli dwie możliwości. Czasami zdarzenie pojawia się jako rezultat komunikatu Windows, a czasami jako rezultat zmian zachodzących w komponencie. Nad zdarzeniami powstającymi w wyniku komunikatów Windows istnieje niemal zerowy stopień kontroli. Można na nie odpowiadać, ale generalnie nie można ich inicjować. Drugi typ zdarzeń to zdarzenia generowane samodzielnie przez komponent. Innymi słowy, twórca komponentu posiada kontrolę nad tym, kiedy tego typu zdarzenia będą się pojawiać.

Pracowanie ze zdarzeniami na tym poziomie może być mylące. Postaram się rozjaśnić to zagadnienie i pokazać, w jaki sposób zdarzenia mogą być używane na poziomie pośrednim. W tym celu dodamy zdarzenie do klasy TFlashingLabel. Wcześniej jednak omówimy pewne podstawowe zagadnienia.

Przegląd zdarzeń

Na początek powinieneś zrozumieć, że zdarzenia są właściwościami i jako takie posiadają wszystkie cechy zwykłych właściwości. Podobnie jak inne właściwości, zdarzenia korzystają z prywatnych pól danych do przechowywania swoich wartości. W przypadku zdarzenia, pole skojarzone zawiera adres procedury, która zostanie wywołana w chwili wystąpienia zdarzenia. Podobnie jak właściwości, zdarzenia mogą być publikowane lub niepublikowane. Zdarzenia publikowane są widoczne w Inspektorze Obiektów, podobnie zresztą jak publikowane właściwości.

Po drugie, zdarzenia są wskaźnikami metod. Wskaźniki metod przypominają doskonałą formę wskaźników funkcji: mogą wskazywać nie tylko funkcje w wystąpieniu klasy, ale również funkcje znajdujące się w obiekcie klasy niezwiązanej. O ile deklaracje funkcji będą zgodne (pod względem zwracanego typu oraz przyjmowanych parametrów), wskaźnik metody bez problemu wywoła funkcję niezależnie od jej położenia. Przykładowo, zdarzenie OnClick obiektu klasy TLabel może wskazywać metodę obsługującą zdarzenie w obiekcie klasy TEdit, TForm, TListBox lub innej. Stopień skomplikowania wskaźników metod jest większy od przedstawionego tutaj, nie będziemy jednak zagłębiać się w szczegóły.

0x01 graphic

Podprogramy obsługujące zdarzenia muszą być zawsze procedurami. Zdarzenie może przekazać jeden lub więcej parametrów w zależności od swojego typu, nie może jednak zwrócić wartości. Uzyskanie informacji zwracanej przez funkcję obsługującą zdarzenie jest możliwe poprzez użycie jednego lub więcej parametrów zmiennych (var) i zezwolenie użytkownikowi na ich modyfikację w celu osiągnięcia określonego zachowania.

Zdarzenia mogą być obsługiwane na jednym z kilku poziomów. Przykładowo, można przesłonić procedurę klasy podstawowej, obsługującą określone zdarzenie, w celu uzyskania dodatkowej funkcjonalności. Załóżmy, że chcemy aby w efekcie kliknięcia na komponent zachował się on w pewien szczególny sposób. Nie ma wtedy sensu tworzyć nowego zdarzenia dla kliknięcia myszą, ponieważ zdarzenie takie istnieje już w klasie podstawowej i nosi nazwę OnClick. Wystarczy więc z niego skorzystać, zamiast tworzyć nowe zdarzenie.

Innym sposobem jest utworzenie zdarzenia wywoływanego z wnętrza komponentu. Opisem tego typu zdarzenia zajmiemy się w pierwszej kolejności. Jak zostało to wspomniane wcześniej, Twoim zadaniem będzie dodanie zdarzenia do utworzonego wcześniej komponentu TFlashingLabel. Dodanie tego zdarzenia wymaga również dodania nowej właściwości. Zdarzenie będzie nosiło nazwę OnLimitReached, a nowa właściwość FlashLimit. Zdarzenie będzie wywoływane po określonej liczbie błysków wskazywanej przez właściwość FlashLimit. Jeżeli FlashLimit wynosi 0 (domyślnie), zdarzenie OnLimitReached nie zostanie w ogóle wywołane.

Pisanie zdarzenia użytkowego dla komponentu można podzielić na pięć prostych zadań:

  1. Określenie typu zdarzenia.

  1. Deklarowanie skojarzonego pola danych.

  2. Deklarowanie zdarzenia.

  3. Stworzenie metody wirtualnej, która będzie wywoływać zdarzenie.

  4. Napisanie kodu wywołującego zdarzenie.

Omówimy teraz kolejno każdy z tych kroków, aby przekonać się jakie zagadnienia są z nimi związane.

Określenie typu zdarzenia

Podczas wcześniejszej dyskusji na temat zdarzeń padło stwierdzenie, iż właściwość cechuje się specyficznym typem. Ta sama prawda odnosi się do zdarzeń. Jednak w przypadku zdarzeń, typem tym jest wskaźnik metody zawierający opis parametrów funkcji obsługującej zdarzenie. Wskaźniki funkcji zostały wspomniane w poprzednim rozdziale, ”Tworzenie i użytkowanie bibliotek DLL”.

Istnieją dwa podstawowe typy zdarzeń. Jednym z nich jest zdarzenie powiadomienia (ang. notification event). Informuje ono o zaistnieniu określonej sytuacji, ale oprócz tego nie przekazuje żadnych szczegółów. Deklaracja funkcji obsługującej zdarzenie powiadomienia wygląda następująco:

procedure Clicked(Sender : TObject);

Jedyną informacją, jaką można uzyskać na podstawie zdarzenia powiadomienia, jest jej nadawca. Dla zdarzeń tego typu Delphi udostępnia typ TNotifyEvent. Dowolne zdarzenia powiadomień, jakie będziesz tworzył, powinny być typu TNotifyEvent.

Drugi typ zdarzenia, to zdarzenie posiadające więcej niż jeden parametr i w rzeczywistości przekazujące informacje do obsługującej je procedury. Również tego typu zdarzenia można tworzyć dla własnych komponentów. Załóżmy, że chcesz użyć procedury obsługującej zdarzenie, której prototyp wygląda następująco:

procedure LimitReached(Sender : TObject; var Stop : Boolean);

Użycie tego typu zdarzenia pozwoli użytkownikowi na modyfikację parametru Stop, co stanowi informację wysłaną z powrotem do komponentu. Chcąc tworzyć zdarzenia z parametrami, trzeba zadeklarować własny typ metody.

Chcemy dla przykładu napisać typ zdarzenia dla powyższej deklaracji metody i nazwać go TLimitReachedEvent. Typ ten wyglądałby następująco:

TLimitReachedEvent = procedure(Sender : TObject; var Stop : Boolean) of object;

Chociaż jest to trochę niezrozumiałe, wystarczy skopiować ten wzór, a następnie dodać lub usunąć parametry według własnych potrzeb. Po zdefiniowaniu typu zdarzenia, można zadeklarować samo zdarzenie, którego typem będzie TLimitReachedEvent. (Nie będzie to miało większego sensu dopóki nie przejdziesz przez cały ten proces osobiście.)

0x01 graphic

Deklarację nowego typu zdarzenia umieść w sekcji typów (type) modułu, tuż nad deklaracją klasy. Przykład:

unit FlashingLabel;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls,

Forms, Dialogs, StdCtrls, ExtCtrls;

type

TLimitReachedEvent =

procedure(Sender : TObject; var Stop : Boolean) of object

TFlashingLabel = class(TCustomLabel)

private

Spróbuj (najlepiej jak potrafisz) określić, jakie typy zdarzeń będą potrzebne (o ile w ogóle będą potrzebne) dla tworzonego przez Ciebie komponentu. Jeżeli potrzebne będą tylko zdarzenia powiadomień, do ich stworzenia posłuż się typem TNotifyEvent. Jeżeli zdarzenia będą przekazywały dane do procedury zdarzeniowej, będziesz musiał zdefiniować własny typ zdarzenia.

Deklarowanie skojarzonego pola danych

Deklarowanie skojarzonego pola danych jest prostą operacją. Wystarczy zadeklarować prywatne pole danych o typie zgodnym z typem zdarzenia. Wygląda to następująco:

FOnLimitReached : TLimitReachedEvent;

Podobnie jak w przypadku właściwości, pole nosi taką samą nazwę jak nazwa zdarzenia z literą F na początku.

Deklarowanie zdarzenia

Po określeniu typu zdarzenia, można zadeklarować samo zdarzenie komponentu. Deklaracja zdarzenia wygląda niemal identycznie jak deklaracja dowolnej innej właściwości. Typowo deklaracja zdarzenia wygląda następująco:

property OnSEvent : TNotifyEvent read FOnSEvent write FOnSEvent;

Większa część tego kodu wyglądem przypomina deklaracje właściwości, z którymi miałeś do czynienia wcześniej. Zauważ, że nie ma tutaj specyfikatora default, a ponadto oba specyfikatory write i read wskazują na to samo pole FOnSEvent. Wynika stąd, że zdarzenia korzystają z dostępu bezpośredniego, a nie z metod read i write. Zauważ również, że typem zdarzenia w tym przykładzie jest TNotifyEvent.

Zdarzenie OnLimitReached będzie przekazywało parametry, dlatego trzeba zdefiniować specjalny typ i użyć go dla zdarzenia. Biorąc to wszystko pod uwagę, deklaracja zdarzenia OnLimitReached wygląda następująco:

property OnLimitReached : TLimitReachedEvent read FOnLimitReached write FOnLimitReached;

Niedługo przedstawiony zostanie cały moduł, abyś mógł zobaczyć jak to wszystko wygląda razem po złożeniu (już teraz możesz zajrzeć do listingu 20.3).

Tworzenie metody wirtualnej generującej zdarzenie

Tworzenie metody wirtualnej, której zadaniem będzie generowanie zdarzenia, wymaga wyjaśnienia. Zdarzenie będzie generowane jako rezultat pewnej zmiany zachodzącej w komponencie. W tym przypadku, zdarzenie będzie generowane gdy liczba błyśnięć etykiety osiągnie wartość wyznaczoną przez właściwość FlashLimit. Zdarzenie jest generowane przez jego wywołanie:

var

Stop : boolean;

begin

Stop := False;

FOnLimitReached(Self, Stop);

Zdarzenie może zostać wygenerowane w jednym z kilku miejsc komponentu, w zależności od różnych czynników. Aby scentralizować punkt generacji, tworzona jest metoda wirtualna, która wywołuje zdarzenie. Metoda wirtualna będzie posiadać taką samą nazwę jak zdarzenie, bez części On, zamiast której wystąpi Do.

0x01 graphic

Istnieją dwie popularne konwencje nazewnictwa metod generujących zdarzenia. Pierwsza z nich usuwa przedrostek On i zastępuje go słowem Do. W przypadku zdarzenia OnLimitReached, metoda wirtualna generująca zdarzenie nazwana zostałby DoLimitReached.

Druga konwencja wyrzuca z nazwy przedrostek On i pozostawia nazwę w takim stanie. Oba sposoby są dobre, ale mimo wszystko preferowana jest pierwsza z nich.

Zacznij od deklaracji metody w deklaracji klasy:

procedure DoLimitReached; virtual;

Następnie, napisz metodę która w rzeczywistości odpowiada za generowanie zdarzenia. Będzie ona wyglądać następująco:

procedure TFlashingLabel.DoLimitReached;

var

Stop : Boolean;

begin

Stop := False;

if Assigned(FOnLimitReached) then

FOnLimitReached(Self, Stop);

FlashEnabled := not Stop;

end;

Na początku ustawiana jest wartość domyślna parametru Stop. Jeżeli użytkownik nie zmodyfikuje tego parametru, wartością zmiennej Stop będzie False. Parametr Stop określa, czy migotanie powinno zostać przerwane po osiągnięciu wartości FlashLimit. Użytkownik może ustawić parametr Stop na wartość True we wnętrzu procedury zdarzeniowej (należącej do aplikacji, która korzysta z komponentu), aby w ten sposób wymusić zatrzymanie migotania lub pozostawić ten parametr bez zmian i umożliwić ciągłe migotanie etykiety. Przez pierwszy parametr (identyfikujący nadawcę) przekazywany jest wskaźnik komponentu Self.

Przyjrzyj się teraz wyrażeniu, które generuje zdarzenie:

if Assigned(FOnLimitReached)

then

FOnLimitReached(Self, Stop);

Jeżeli użytkownik komponentu dołączy do zdarzenia obsługującą go funkcję, będzie ona wywoływana. Jeżeli funkcja nie została dołączona, zdarzenie zostanie obsłużone w sposób domyślny. (W naszym przypadku domyślnie nie jest podejmowana żadna akcja.) Użytkownik powinien mieć możliwość zignorowania zdarzenia, jeżeli tego zechce. Powyższy kod dopuszcza taką możliwość.

0x01 graphic

Rzeczą trudną do pojęcia podczas pisania zdarzeń jest to, że komponent sam z siebie nie udostępnia procedur obsługujących zdarzenia. Za dostarczenie tych procedur odpowiedzialna jest aplikacja. Zadaniem programisty jest jedynie dostarczenie mechanizmu, który umożliwi wywołanie procedury obsługującej zdarzenie w chwili jego wywołania.

0x01 graphic

Jeżeli użytkownik nie zdefiniował funkcji do obsłużenia określonego zdarzenia, będzie ono wskazywało nil (brak przypisanej wartości). Nigdy nie wolno wywoływać funkcji obsługujących zdarzenia bez wcześniejszego upewnienia się, że zdarzeniu przypisana została wartość. Próba wywołania zdarzenia, któremu nie została przypisana funkcja zaowocuje błędem naruszenia ochrony dostępu (Access Violation) w komponencie.

Jak wspomniałem wcześniej, DoLimitReached jest metodą wirtualną, ponieważ w klasach potomnych może zajść potrzeba przedefiniowania zachowania się zdarzenia. Dzięki temu, że uczyniliśmy tę metodę wirtualną, w dowolnym komponencie potomnym wystarczy jedynie przesłonić procedurę DoLimitReached, aby zmienić standardowy sposób obsługi zdarzenia. Możemy w łatwy sposób wpłynąć na sposób obsługi zdarzenia bez potrzeby przeszukiwania kodu w poszukiwaniu każdego z miejsc, gdzie zdarzenie to jest wywoływane. Zdarzenie jest wywoływane tylko w jednym miejscu, czyli w metodzie DoLimitReached.

Pisanie kodu wywołującego zdarzenie

Gdzieś w komponencie musi znaleźć się kod, który będzie wywoływał metodę DoLimit­Reached (która z kolei generuje zdarzenie). W przypadku komponentu TFlashingLabel metoda DoLimitReached jest wywoływana z wnętrza procedury OnTimer. Oto jak wygląda ta procedura po modyfikacji mającej na celu wywoływanie zdarzenia:

procedure TFlashingLabel.OnTimer(Sender : TObject);

begin

{ Jeżeli komponent znajduje się w formularzu w trybie }

{ projektowania, następuje zatrzymanie timera i powrót. }

if csDesigning in ComponentState then begin

Timer.Enabled := False;

Exit;

end;

{ Zmiana stanu właściwości Visible, za każdym razem kiedy }

{ pojawia się zdarzenie timera. }

Visible := not Visible;

{ W miarę potrzeby wywołanie zdarzenia. Zwiększenie licznika }

{ jeżeli etykieta jest widoczna. }

if (FFlashLimit <> 0) and Visible then begin

{ Zwiększenie pola FlashCount. }

Inc(FlashCount);

{ Jeżeli pole FlashCount jest większe lub równe }

{ wartości właściwości FlashLimit, następuje wyzerowanie }

{ wartości FlashCount i wywołanie zdarzenia. }

if FlashCount >= FFlashLimit then begin

FlashCount := 0;

DoLimitReached;

end;

end;

end;

Jak widać, po osiągnięciu wartości FlashLimit następuje wywołanie metody DoLimit­Reached i wygenerowanie zdarzenia. Liczenie następuje tylko podczas co drugiego zdarzenia OnTimer - zlicza się tylko “błyski”, nie zaś wygaszenia. Zmienna Count jest polem klasy, natomiast FlashLimit właściwością.

Przesłanianie zdarzeń klasy podstawowej

Dyskusja w poprzedniej sekcji wiąże się z innym zagadnieniem, o którym chcę wspomnieć pokrótce. Jeżeli chcesz zmodyfikować domyślne zachowanie któregoś ze zdarzeń zdefiniowanych w klasie podstawowej, wystarczy że przesłonisz wywołującą go procedurę tak, jak zostało to opisane wyżej. Załóżmy, że chcesz zmienić zachowanie procedury OnClick tak, aby przy każdym kliknięciu na komponencie generowany był dźwięk. W tym celu wystarczy przesłonić procedurę klasy bazowej o nazwie Click w następujący sposób:

procedure TFlashingLabel.Click;

begin

{ Wydanie dźwięku, a następnie wywołanie metody Click klasy }

{ podstawowej w celu przeprowadzenia domyślnej obsługi zdarzenia. }

MessageBeep(-1);

inherited;

end;

Ponieważ powyższa procedura została zadeklarowana w klasie podstawowej jako dynamiczna, będzie ona wywoływana automatycznie przy każdym kliknięciu na komponent. Funkcja będzie działać tylko gdy komponent jest widoczny - pamiętaj o tym próbując kliknąć na komponent w trakcie jego migotania.

Złożenie w jedną całość

Do tej pory przyglądałeś się poszczególnym nowym i modyfikowanym elementom komponentu TFlashingLabel, ale nie miałeś jeszcze okazji spojrzeć na cały komponent. Listing 20.3 przedstawia plik kodu źródłowego ukończonego komponentu TFlashingLabel. Przestudiuj implementację zdarzenia OnLimitReached, aby zrozumieć w jaki sposób należy programować zdarzenia w komponencie. Listing 20.4. przedstawia moduł zmodyfikowanego programu testującego. Metoda MainFormLimitReached przedstawia sposób wykorzystania zdarzenia OnLimitReached.

Listing 20.3. FlashingLabel.pas (zmodyfikowany)

unit FlashingLabel;

interface

{$R Flashing.dcr}

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls,

Forms, Dialogs, StdCtrls, ExtCtrls;

type

TLimitReachedEvent =

procedure(Sender : TObject; var Stop : Boolean) of object;

TFlashingLabel = class(TCustomLabel)

private

{ Private declarations }

FFlashEnabled : Boolean;

FFlashLimit : Integer;

FFlashRate : Integer;

FOnLimitReached : TLimitReachedEvent;

FlashCount : Integer;

Timer : TTimer;

protected

{ Protected declarations }

{ Chronione metody write, przeznaczone dla właściwości }

procedure SetFlashEnabled(AFlashEnabled : Boolean);

procedure SetFlashRate(AFlashRate : Integer);

procedure DoLimitReached; virtual;

procedure Click; override;

{ Funkcja obsługująca zdarzenie OnTimer }

procedure OnTimer(Sender : TObject); virtual;

public

{ Public declarations }

constructor Create(AOwner : TComponent); override;

published

{ Published declarations }

{ Właściwości komponentu. }

property FlashEnabled : Boolean

read FFlashEnabled write SetFlashEnabled default True;

property FlashRate : Integer

read FFlashRate write SetFlashRate default 800;

property FlashLimit : Integer

read FFlashLimit write FFlashLimit default 0;

property OnLimitReached : TLimitReachedEvent

read FOnLimitReached write FOnLimitReached;

{ Deklaracje właściwości klasy TCustomLabel. }

property Align;

property Alignment;

property AutoSize;

property BiDiMode;

property Caption;

property Color;

property Constraints;

property DragCursor;

property DragKind;

property DragMode;

property Enabled;

property FocusControl;

property Font;

property ParentBiDiMode;

property ParentColor;

property ParentFont;

property ParentShowHint;

property PopupMenu;

property ShowAccelChar;

property ShowHint;

property Transparent;

property Layout;

property Visible;

property WordWrap;

property OnClick;

property OnDblClick;

property OnDragDrop;

property OnDragOver;

property OnEndDock;

property OnEndDrag;

property OnMouseDown;

property OnMouseMove;

property OnMouseUp;

property OnStartDock;

property OnStartDrag;

end;

procedure Register;

implementation

constructor TFlashingLabel.Create(AOwner : TComponent);

begin

inherited;

{ Przypisanie wartości domyślnych polom danych. }

FFlashEnabled := True;

FFlashRate := 800;

FFlashLimit := 0;

FlashCount := 0;

{ Inicjalizacja obiektu timera. }

Timer := TTimer.Create(Self);

{ Ustawienie przedziału timera przy użyciu wsp. migotania. }

Timer.Interval := FFlashRate;

{ Przypisanie własnej funkcji obsługującej zdarzenie OnTimer. }

{ do zdarzenia OnTimer. }

Timer.OnTimer := OnTimer;

end;

procedure TFlashingLabel.SetFlashEnabled(AFlashEnabled : Boolean);

begin

{ Ustawienie pola FFlashEnabled. }

FFlashEnabled := AFlashEnabled;

{ Nie uruchamiaj timera, jeżeli komponent w formularzu }

{ znajduje się w trybie projektowania. Powrót z procedury. }

if csDesigning in ComponentState then

Exit;

{ Uruchomienie timera. }

Timer.Enabled := FFlashEnabled;

{ Jeżeli migotanie zostało wyłączone trzeba upewnić się, }

{ że etykieta jest widoczna. }

if not FFlashEnabled then

Visible := True;

end;

procedure TFlashingLabel.SetFlashRate(AFlashRate : Integer);

begin

{ Ustawienie pola FFlashRate i przedziału czasowego timera. }

FFlashRate := AFlashRate;

Timer.Interval := AFlashRate;

end;

procedure TFlashingLabel.OnTimer(Sender : TObject);

begin

{ Jeżeli komponent znajduje się w formularzu w trybie }

{ projektowania, następuje zatrzymanie timera i powrót. }

if csDesigning in ComponentState then begin

Timer.Enabled := False;

Exit;

end;

{ Zmiana stanu właściwości Visible, za każdym razem kiedy }

{ pojawia się zdarzenie timera. }

Visible := not Visible;

{ W miarę potrzeby wywołanie zdarzenia. Zwiększenie licznika }

{ jeżeli etykieta jest widoczna. }

{ Wygenerowanie zdarzenia w miarę konieczności. Zwiększnie

if (FFlashLimit <> 0) and Visible then begin

{ Zwiększenie pola FlashCount. }

Inc(FlashCount);

{ Jeżeli pole FlashCount jest większe lub równe }

{ wartości właściwości FlashLimit, następuje wyzerowanie }

{ wartości FlashCount i wywołanie zdarzenia. }

if FlashCount >= FFlashLimit then begin

FlashCount := 0;

DoLimitReached;

end;

end;

end;

procedure TFlashingLabel.DoLimitReached;

var

Stop : Boolean;

begin

Stop := False;

if Assigned(FOnLimitReached) then

FOnLimitReached(Self, Stop);

FlashEnabled := not Stop;

end;

procedure TFlashingLabel.Click;

begin

{ Wydanie dźwięku, a następnie wywołanie metody Click klasy }

{ podstawowej w celu przeprowadzenia domyślnej obsługi zdarzenia. }

MessageBeep(0);

inherited;

end;

procedure Register;

begin

RegisterComponents('Samples', [TFlashingLabel]);

end;

end.

Listing 20.4. FlashTstU.pas

unit FlshTstU;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

StdCtrls, ExtCtrls, Flashing;

type

TMainForm = class(TForm)

FlashBox: TCheckBox;

Group: TRadioGroup;

procedure FormCreate(Sender: TObject);

procedure GroupClick(Sender: TObject);

procedure FlashBoxClick(Sender: TObject);

private

{ Private declarations }

Flasher : TFlashingLabel;

procedure OnLimitReached(Sender : TObject; var Stop : Boolean); public

{ Public declarations }

end;

var

MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.FormCreate(Sender: TObject);

begin

Flasher := TFlashingLabel.Create(Self);

Flasher.Parent := Self;

Flasher.SetBounds(20, 20, 200, 20);

Flasher.Font.Size := 16;

Flasher.Caption := 'This is a test';

Flasher.FlashRate := 800;

Flasher.FlashLimit := 5;

Flasher.OnLimitReached := OnLimitReached;

end;

procedure TMainForm.OnLimitReached(Sender : TObject; var Stop : Boolean);

begin

{ Funkcja obsługująca zdarzenie OnLimitReached. Ustawienie }

{ parametru Stop na True powoduje zatrzymanie migotania, }

{ pozostawienie parametru bez zmian powoduje kontynuację }

{ migotania. }

Stop := True;

end;

procedure TMainForm.GroupClick(Sender: TObject);

begin

case Group.ItemIndex of

0 : Flasher.FlashRate := 1200;

1 : Flasher.FlashRate := 800;

2 : Flasher.FlashRate := 400;

3 : Flasher.FlashRate := 150;

end;

end;

procedure TMainForm.FlashBoxClick(Sender: TObject);

begin

Flasher.FlashEnabled := FlashBox.Checked;

end;

end.

Komponent FlashingLabel i program testujący FlashTst wchodzą w skład dyskietki dołączonej do książki. Różnica występuje w nazwie pliku kodu źródłowego komponentu, którą jest Flashing.pas.

Uruchom program testujący, aby przekonać się, że zdarzenie działa zgodnie z tym, co zostało powiedziane. Poeksperymentuj z programem, aby lepiej zrozumieć w jaki sposób działa zdarzenie i obsługująca go procedura. Zwróć uwagę na charakterystyczny dźwięk wydawany podczas klikania na etykietę (w chwili gdy jest ona widoczna). Ma to miejsce dzięki przesłonięciu metody dynamicznej o nazwie Click. Procedura ta ma zobrazować sposób przesłaniania zdarzeń klasy podstawowej.

0x01 graphic

Jeżeli chcesz zainstalować na Palecie Komponentów zmodyfikowany komponent FlashingLabel, otwórz pakiet DCLUSR40 i kliknij na przycisk Compile Package. Stara wersja komponentu zostanie uaktualniona do wersji nowej.

Opanowanie zagadnienia tworzenia zdarzeń wymaga czasu. Umiejętności pisania zdarzeń nie da się zdobyć inaczej niż przez doświadczenie. Trzeba po prostu „wgryźć się” w zagadnienie i pracować. Prawdopodobnie nauka nie obędzie się bez kilku wstrząsów, będzie to jednak dobre doświadczenie, po którym poczujesz się lepszym programistą.

Podsumowanie

Przeszedłeś na wyższy stopień zaawansowania. Pisanie komponentów nie jest szczególnie proste, ale po opanowaniu podstawowych wiadomości z tej dziedziny wszystko zależeć będzie od własnych inwencji. Jeżeli choć trochę przypominasz mnie, czas spędzony na programowaniu niewizualnym będzie sprawiał Ci równie dużą przyjemność jak programowanie wizualne. Być może zagadnienia z tego rozdziału nie są całkowicie zrozumiałe - nie przejmuj się tym. Zrób sobie kilka dni przerwy, aby wiedza mogła się odpowiednio ułożyć, a następnie wróć do zagadnienia. Warto przeczytać również kilka innych opracowań dotyczących pisania komponentów, aby w pełni zrozumieć ten temat. Skup się nad tym, a w końcu wszystko okaże się sensowne.

Warsztat

Warsztat składa się z pytań kontrolnych oraz ćwiczeń utrwalających i pogłębiających zdobytą wiedzę. Odpowiedzi do pytań możesz znaleźć w dodatku A.

Pytania i odpowiedzi

Nie. W swoich aplikacjach możesz korzystać ze standardowych komponentów oferowanych przez Delphi. Być może nigdy nie będziesz musiał tworzyć własnych komponentów.

Tak. Istnieje wiele źródeł udostępniających biblioteki komponentów. Biblioteki te są sprzedawane przez firmy specjalizujące się w tworzeniu komponentów VCL. Oprócz tego istnieje wiele komponentów typu shareware i freeware dostępnych w sieci. Poszukaj komponentów VCL w Internecie.

Tak. C++ Builder posiada możliwość kompilowania i instalowania komponentów Delphi.

Nie. Do zapisania wartości właściwości w skojarzonym z nim polu skojarzonym można wykorzystać mechanizm dostępu bezpośredniego.

Zastosowanie metody write umożliwia wykonanie innych operacji podczas zapisywania właściwości. Zapis do właściwości często związany jest z wykonaniem określonych zadań przez komponent - ich wykonanie umożliwia właśnie metoda write.

Właściwość publikowana jest wyświetlana w Inspektorze Obiektów w czasie projektowania. Właściwości, które nie posiadają interfejsu etapu projektowania powinny być właściwościami publicznymi. W ten sposób użytkownik będzie mógł je modyfikować lub odczytywać w czasie wykonania, ale nie będzie miał do nich dostępu w czasie projektowania.

Napisz program testujący, dodaj do niego plik kodu źródłowego komponentu. W konstruktorze głównego formularza stwórz egzemplarz testowanego komponentu. Przed jego wyświetleniem ustaw wszelkie niezbędne właściwości. W programie testującym poeksperymentuj z właściwościami publicznymi komponentu, aby przekonać się że wszystko działa poprawnie.

Niekoniecznie. Niektóre komponenty korzystają ze zdarzeń, a inne nie. Nie należy zadawać sobie zbędnej pracy przy tworzeniu zdarzeń dla własnych komponentów, ale jednocześnie nie należy również stronić od tej możliwości, kiedy jest ona naprawdę wymagana.

Quiz

  1. Czy właściwość musi korzystać z metody write? Uzasadnij swoją odpowiedź.

  1. Czy właściwość musi posiadać skojarzone pole danych? Uzasadnij swoją odpowiedź.

  2. Czy można zbudować komponent przez rozbudowę komponentu istniejącego?

  3. Co się stanie jeżeli w deklaracji właściwości nie zostanie określony specyfikator write (w postaci metody lub dostępu bezpośredniego)?

  4. Co oznacza dostęp bezpośredni?

  5. Czy właściwości muszą posiadać wartości domyślne? Uzasadnij swoją odpowiedź?

  6. Czy ustawienie wartości domyślnej dla właściwości powoduje automatyczne przypisanie jej do skojarzonego pola danych?

  7. W jaki sposób komponent jest instalowany w Palecie Komponentów?

  8. W jaki sposób określa się bitmapę przycisku, która będzie reprezentowała komponent w Palecie Komponentów?

  9. W jaki sposób wywoływane są zdarzenia definiowane przez użytkownika?

Ćwiczenia

  1. Przejrzyj kod źródłowy komponentu FlashingLabel, zawarty w listingu 20.3. Przestudiuj dokładnie kod, aby zrozumieć co się w nim dzieje.

  1. Usuń komponent FlashingLabel z biblioteki komponentów, a następnie zainstaluj go ponownie.

  2. Napisz program testujący, który korzysta z trzech komponentów FlashingLabel o różnych częstotliwościach migotania.

  3. Zmień bitmapę reprezentującą komponent FlashingLabel w Palecie Komponentów na bitmapę własnego projektu.

  4. Napisz metodę write dla właściwości FlashLimit komponentu FlashingLabel. W jej wnętrzu umieść kod, który nie pozwoli użytkownikowi na wpisanie wartości większej niż 100.

  5. Zmień zdarzenie OnLimitReached komponentu FlashingLabel na zwykłe zdarzenie powiadomienia (Podpowiedź: użyj typu TNotifyEvent.)

  6. Ćwiczenie dodatkowe: Napisz komponent według własnego projektu.

  7. Ćwiczenie dodatkowe: Przetestuj stworzony przez siebie komponent i zainstaluj go w Palecie Komponentów.

Niestety, autor oryginału nie ma tutaj 100% racji - wrócimy do tej kwestii w dalszej części rozdziału (przyp. red.)

ang. Field - pole (przyp. red.)

Ściślej - jest tak w przypadku właściwości prostych, omawianych w niniejszym rozdziale; kiedy ma się do czynienia z właściwościami tablicowymi i indeksowanymi, metoda write może posiadać dwa lub więcej parametrów (przyp. red.)

ang. Argument - parametr (przyp. red.)

Ściślej - jest tak w przypadku właściwości prostych, omawianych w niniejszym rozdziale; kiedy ma się do czynienia z właściwościami tablicowymi i indeksowanymi, metoda read może posiadać jeden lub więcej parametrów (przyp. red.)

800 Część III

800 C:\Dokumenty\Roboczy\Delphi 4 dla kazdego\20.doc

C:\Dokumenty\Roboczy\Delphi 4 dla kazdego\20.doc 761

Rozdział 20. Tworzenie komponentów 799



Wyszukiwarka

Podobne podstrony:
16, ## Documents ##, Delphi 4 dla każdego
22, ## Documents ##, Delphi 4 dla każdego
07, ## Documents ##, Delphi 4 dla każdego
13, ## Documents ##, Delphi 4 dla każdego
12, ## Documents ##, Delphi 4 dla każdego
19, ## Documents ##, Delphi 4 dla każdego
skoro, ## Documents ##, Delphi 4 dla każdego
Części, ## Documents ##, Delphi 4 dla każdego
11, ## Documents ##, Delphi 4 dla każdego
a, ## Documents ##, Delphi 4 dla każdego
Delphi 4 dla każdego, 01
Delphi 7 dla każdego
B, Informatyka, Delphi 4 dla każdego
Delphi 4 dla każdego, 03
Delphi 4 dla każdego, 04

więcej podobnych podstron