Rozdział 14. Komponenty VCL i CLX
Adam Boduch
Zanim przejdziemy do właściwego procesu tworzenia komponentów, konieczne będzie dokładne zapoznanie się z zasadami ich działania. Co prawda zarówno o klasach, jak i o VCL mówiłem już w rozdziale 3., lecz tamta wiedza była konieczna do dalszego projektowania aplikacji w Delphi. Tym razem poznasz architekturę komponentów VCL oraz CLX ze strony projektanta dowiesz się, jak to wszystko działa i jak jest ze sobą połączone.
1 Czym jest komponent?
2 VCL
3 CLX
3.1 Tworzenie aplikacji opartych na CLX
3.2 CLX a VCL
3.3 Architektura CLX
4 Windows a Linux
4.4 Kompilacja warunkowa
5 Rodzaje komponentów
5.5 Komponenty wizualne
5.6 Komponenty niewizualne
5.7 Komponenty graficzne
6 Hierarchia komponentów
6.8 TObject
6.8.1 Metody klasy TObject
6.8.1.1 ClassName
6.8.1.2 ClassNameIs
6.8.1.3 ClassInfo
6.8.1.4 ClassParent
6.8.1.5 ClassType
6.8.1.6 FieldAddress
6.8.1.7 MethodAddress
6.9 TPersistent
6.9.2 Metoda Assign
6.9.3 Metoda AssignTo
6.9.4 Metoda DefineProperties
6.10 TComponent
6.10.5 Właściwości klasy TComponent
6.10.5.8 ComponentCount
6.10.5.9 ComponentIndex
6.10.5.10 Components
6.10.5.11 ComponentState
6.10.5.12 Name
6.10.5.13 Owner
6.10.6 Metody klasy TComponent
6.11 TControl
6.11.7 Właściwości klasy TControl
6.12 TWinControl i TWidgetControl
6.12.8 Właściwości klas TWinControl i TWidgetControl
6.12.9 Zdarzenia
6.13 Klasy TCustom
6.14 TGraphicControl
7 Budowa komponentu
7.15 Właściwości
7.15.10 Rodzaje właściwości
7.16 Zdarzenia
7.17 Metody
8 RTTI
8.18 Właściwości obiektu
8.19 Dokładniejsze informacje o obiekcie
9 Podsumowanie
W tym rozdziale:
*dowiesz się, jak funkcjonuje klasa VCL;
*dowiesz się, czym jest biblioteka CLX;
*poznasz elementarne metody i właściwości podstawowych klas.
Czym jest komponent?
Delphi jako środowisko wizualne udostępnia różne kontrolki tzw. komponenty, służące do szybkiego projektowania aplikacji. Komponenty są klockami, którymi posługuje się programista w procesie budowania aplikacji.
Patrząc na komponenty ze strony projektanta aplikacji, mamy do czynienia z wygodnymi i łatwymi w obsłudze narzędziami. Nie interesuje nas, jak one działają i jaki kod znajduje się w środku. Ważne są zdarzenia, metody, właściwości i zadania, jakie spełniają. W rzeczywistości wiele komponentów jest ze sobą powiązanych i korzysta z tych samych modułów, klas i bibliotek.
Kluczem do poznania VCL oraz CLX jest zrozumienie zasady działania różnych typów danych, poznanie hierarchii klas itp.
VCL
VCL to Visual Component Library (wizualna biblioteka komponentów). Programowanie w Delphi nie byłoby takie łatwe dla początkujących, gdyby nie VCL. Poprzednikiem VCL była biblioteka OWL (Object Windows Library), znajdująca się w Turbo Pascalu. Dla użytkownika wywołanie konstruktora Create
to tylko jeden wiersz kodu, powodujący utworzenie komponentu (obiektu). W rzeczywistości na podstawie kodu VCL wykonywany jest szereg innych procedur, które ostatecznie dają efekt w postaci utworzenia komponentu.
CLX
Opublikowanie Delphi 6 było czymś niezwykłym ze względu na wprowadzenie biblioteki CLX (Component Library for Cross-Platform), czyli międzyplatformowej biblioteki komponentów. Pojawienie się CLX wiązane jest z wydaniem nowego środowiska programistycznego Kylix, przeznaczonego dla systemu operacyjnego Linux. Programowanie w Kyliksie jest niezwykle podobne do programowania w Delphi nawet interfejs jest ten sam. Kylix również tak jak Delphi wykorzystuje język Object Pascal, co mogło trochę dziwić w momencie edycji produktu. W końcu Linux jest platformą, na której króluje C++.
W każdym razie w związku z pojawieniem się Kyliksa oraz biblioteki CLX przekroczona została pewna bariera- od tej pory możliwe jest tworzenie aplikacji zgodnych zarówno z systemem Windows, jak i Linuks.
Tworzenie aplikacji opartych na CLX
Aby stworzyć aplikację działającą w oparciu o CLX, wystarczy z menu File wybrać polecenie New/CLX Application. Wówczas zmieniona zostanie zawartość palety komponentów oraz edytora kodu.
CLX a VCL
Sam akronim VCL może dawać do zrozumienia, że mamy do czynienia z całkowicie wizualną biblioteką. W rzeczywistości tak nie jest, bo VCL zawiera również komponenty, które nie są widoczne, ale jednak odpowiadają za wykonywanie jakichś czynności. Pisząc programy działające w systemie Windows, korzystamy ze standardowych kontrolek tego systemu (chociażby kontrolka TButton
), zawartych w bibliotece User32.dll lub CommCtrl32.dll. Natomiast w przypadku CLX komponenty z tej biblioteki są oparte na tzw. widgetach, zawartych w bibliotece Qt. Owa biblioteka zawiera niezależne klasy, które swoim wyglądem oraz funkcjonalnością przypominają standardowe kontrolki Windows.
Słowo widget jest skrótem słów Window Gadget.
Architektura CLX
Architektura CLX jest nieco bardziej złożona niż VCL. Mamy tu bowiem kilka grup komponentów. Oprócz grupy wizualnej VisualCLX istnieją jeszcze komponenty BaseCLX (moduły wspólne dla Delphi i Kyliksa), DataCLX (komponenty zapewniające dostęp do technologii dbExpress będziemy o tym mówić w kolejnej części książki) oraz NetCLX (technologie internetowe).
VCL jest zbudowany na bazie kontrolek WinAPI, zawartych m.in. w bibliotekach User32.dll oraz ComCtrl32.dll. W przypadku CLX grupa komponentów VisualCLX jest oparta na bibliotece Qt.
Hierarchia klas jest ona bardzo podobna do hierarchii komponentów VCL. Dodano parę nowych klas, inne zamieniono miejscami, lecz starano się zachować maksymalną kompatybilność pomiędzy CLX i VCL tak, aby przenoszenie kodu odbywało się z jak najmniejszym nakładem pracy.
Windows a Linux
Można by przypuszczać, że dla programisty tworzącego swoje aplikacje w języku Object Pascal nie ma znaczenia, na jakiej platformie będzie uruchamiany ów program. Oczywiście należy jednak pamiętać o pewnych elementach charakterystycznych dla danego systemu. Przykładem może być konieczność zwracania uwagi na nazewnictwo plików w Linuksie w systemie tym rozróżniane są np. nazwy modułów na liście Uses.
*W systemie Linux używany jest inny znak separatora katalogów. W Windows jest to znak backslash (), a w Linuksie slash (/). Właściwy dla konkretnej platformy znak można odczytać ze stałej PathSeparator
.
*Pamiętasz, jak podczas omawiania bibliotek DLL wspominałem o konwencjach wywołania Stdcall, Safecall i Cdecl W Kyliksie należy zmienić tę klauzulę na Cdecl, gdyż tylko taka forma jest prawidłowa.
*Tworząc programy w Kyliksie, należy unikać modułów oraz funkcji charakterystycznych dla Windows np. modułów Windows
, ActiveX
, ComServ, ComObj itp. gdyż nie odnajdziemy tam takich technologii jak BDE, COM czy ActiveX.
*Bardzo ważne jest zapamiętanie, że w Linuksie nie możemy korzystać z komunikatów. Niedopuszczalne jest zatem wysyłanie komunikatów czy ich przechwytywanie np. WM_CHAR
itp.
*Większość klas nazywa się tak samo. Pamiętaj jednak, że klasa TWinControl
w CLX nazywa się TWidgetControl
. Ze względów kompatybilności zachowano jednak również klasę TWinControl
, która jest równoważna klasie TWidgetControl
.
*Unikaj stosowania funkcji typowych dla Windows: Win32Check, RaiseLastWin32Error.
*W systemie Linux nie ma Rejestru, nie możesz więc korzystać z klasy TRegistry
.
*Nazwy modułów CLX rozpoczynają się od litery Q np. QControls
.
*System Linux nie używa liter do określania dysków, tak jak ma to miejsce w Windows. Wszystko opiera się na charakterystycznym systemie plików, gdzie istnieją tylko katalogi główne np. /usr
Kompilacja warunkowa
Zarówno w Delphi, jak i w Kyliksie istnieje możliwość wprowadzenia charakterystycznego dla danej platformy kodu. Wszystko dzięki tzw. symbolom kompilacji warunkowej.
{$IFDEF LINUX}
// kod dla Linuksa
{$ENDIF}
Dla systemu Linux takim symbolem jest LINUX
, a dla Windows może to być WIN32
i MSWINDOWS
:
{$IFDEF WIN32}
// kod dla Windows
{$ENDIF}
Dzięki takiemu prostemu rozwiązaniu można wprowadzać charakterystyczne dla danej platformy elementy kodu.
Rodzaje komponentów
Komponenty w Delphi dzielą się na wizualne oraz niewizualne ? bynajmniej nie chodzi tutaj o właściwość Visible
, której zmiana na False
powoduje ukrycie komponentu.
Komponenty wizualne
Nieprzypadkowo dla określenia komponentu wizualnego często używam w tej książce słowa kontrolka (control). Należy zdać sobie sprawę z tego, że kontrolka to obiekt wizualny, będący w stanie odbierać komunikaty od użytkownika. Kontrolki mają właściwości mogące określić ich położenie względem projektanta formularzy. Mogą być także aktywowane (focus). Komponenty wizualne wywodzą się z klasy TControl
. Przykładami kontrolek wizualnych są TButton
, TLabel
, TListView
, TComboBox
itp.
Komponenty niewizualne
Komponentem jest każdy obiekt, mogący znaleźć się w palecie komponentów. Niektóre z tych obiektów są widoczne po uruchomieniu programu, niektóre jednak nie. Komponenty niewidoczne wywodzą się z klasy TComponent
, lecz mimo tego, że ich nie widać, pełnią inną funkcję, często o wiele ważniejszą niż rola obiektów wizualnych. Przykładem komponentów niewidocznych jest komponent TTimer
, TOpenDialog
(ogólnie wszystkie komponenty z palety Dialogs) oraz komponenty z palety Indy.
Komponenty graficzne
Niektóre komponenty Delphi mimo swego charakteru wizualnego nie posiadają zdolności interakcji z użytkownikiem ? nie mogą więc stać się aktywne. Nie posiadają także uchwytu, a ich klasą bazową jest TGraphicControl
.
Przykładem takich komponentów są TPaintBox
i TImage
.
Hierarchia komponentów
Na rysunku 14.1 przedstawiona została hierarchia komponentów VCL. Naturalnie jest to tylko fragment ogromnej pajęczyny klas.
Rysunek 14.1. Hierarchia komponentów VCL
W CLX ze względów kompatybilności starano się zachować identyczne nazewnictwo klas. Różnica pojawia się w klasie TWinControl
, która w CLX nosi nazwę TWidgetControl
. Pamiętaj o tym, że w CLX nie ma klasy TRegistry
, lecz możesz korzystać z klasy TRegINIFile
.
TObject
Klasa TObject
jest podstawowym fundamentem całej struktury VCL oraz CLX. Zawiera wiele podstawowych metod, obecnych we wszystkich klasach. Wszystko dzięki dziedziczeniu ? w czasie, gdy pozostałe klasy dziedziczą po klasie TObject
, przejmują jej wszystkie metody. Klasa ta odpowiada za tak podstawowe czynności, jak:
*tworzenie i usuwanie instancji komponentu,
*alokację i zwalnianie potrzebnej pamięci,
*obsługę komunikatów,
*zwracanie informacji na temat rodzaju klasy, nazwy itp.
Metody klasy TObject
Klasa TObject
definiuje jedynie metody, które mogą być później używane przez wszystkie inne kontrolki VCL. Spis najważniejszych metod klasy TObject
znajduje się poniżej.
ClassName
class function ClassName: ShortString;
Owa funkcja zwraca nazwę klasy ? przykład:
ShowMessage(Button1.ClassName);
Powyższy kod spowoduje wyświetlenie rzeczywistej nazwy klasy, czyli TButton
.
ClassNameIs
class function ClassNameIs(const Name: string): Boolean;
Funkcja sprawdza, czy podana w parametrze klasa odpowiada tej, z której jest wywoływana funkcja. Najlepiej objaśnić to na przykładzie:
Button1.ClassNameIs(?TButton?); // funkcja zwróci True
Button1.ClassNameIs(?TObject?); // funkcja zwróci False
ClassInfo
class function ClassInfo: Pointer;
Funkcja zwraca wskaźnik do tablicy zawierającej informacje takie jak typ obiektu, właściwości itp. Wrócimy do tego nieco później.
ClassParent
class function ClassParent: TClass;
Informacja o klasie bazowej dla naszego komponentu jest zwracana przez funkcję ClassParent
. Dzięki owej funkcji możemy odwołać się do innych właściwości klasy bazowej, nie znając jej podczas tworzenia programu.
ClassType
function ClassType: TClass;
Funkcja działa podobnie jak ClassParent
. Jedyna różnica polega na tym, że zwraca ona informacje na temat klasy, z której jest wywoływana ? np.:
ShowMessage(Button1.ClassType.ClassName);
Po wykonaniu takiego kodu w okienku wyświetlony zostanie napis TButton
. Inna sprawa, że taki sam efekt uzyskamy, korzystając z funkcji ClassName
.
FieldAddress
function FieldAddress(const Name: ShortString): Pointer;
Funkcja FieldAddress
zwraca wskaźnik do właściwości znajdującej się w pamięci. Nazwa właściwości musi zostać podana w parametrze.
MethodAddress
class function MethodAddress(const Name: ShortString): Pointer;
Funkcja działa podobnie jak FieldAddress
, tyle że ta zwraca wskaźnik do metody umieszczonej w pamięci ? nie zaś właściwości.
Więcej opisów metod klasy TObject
możesz znaleźć w systemie pomocy Delphi lub (w języku polskim) pod adresem Delphi
TPersistent
Klasą drugą po TObject
w całej hierarchii jest TPersistent
. Oczywiście większość metod dziedziczy po TObject
, ale wprowadza także swoje metody.
Klasy TPersistent
możesz użyć jako klasy bazowej w momencie, gdy deklarujesz klasy, które nie mają być komponentem. Jaka jest więc różnica między TObject
a TPersistent
? Otóż klasa TPersistent
może być przodkiem dla wszystkich klas mogących zapisywać wartości do właściwości, które następnie mogą być przechowywane w pliku *.dfm (lub *.xfm ? dla Kyliksa).
Metoda Assign
procedure Assign(Source: TPersistent); virtual;
Metoda Assign
, obecna w klasie TPersistent
, powoduje skopiowanie danych (właściwości) z jednego obiektu do drugiego.
Metoda AssignTo
procedure AssignTo(Dest: TPersistent); virtual;
AssignTo
działa odwrotnie niż procedura Assign
. Powoduje skopiowanie danych do innego obiektu, określonego w parametrze Dest
.
Metoda DefineProperties
procedure DefineProperties(Filer: TFiler); virtual;
Procedura DefineProperties
umożliwia przechowanie w pliku *.dfm dodatkowych danych ? tym pojęciem zajmiemy się kolejnym rozdziale.
TComponent
Klasa TComponent
, jak można wywnioskować z nazwy, jest przodkiem wszystkich komponentów. Komponenty dziedziczące po TComponent
mogą:
*być zintegrowane z IDE Delphi, w tym mogą być umieszczone na palecie komponentów;
*jeden obiekt typu TComponent
może posiadać inny obiekt (być jego rodzicem);
*komponenty mogą być konwertowane na obiekty COM lub kontrolki ActiveX.
Obiekty COM są wynalazkiem firmy Microsoft, stąd obecne są jedynie na platformie Windows.
Klasa TComponent
wprowadza szereg nowych właściwości oraz metod, obecnych później we wszystkich komponentach (w końcu wszystkie komponenty pochodzą od klasy TComponent
).
Właściwości klasy TComponent
Klasa TComponent
zawiera wiele ciekawych właściwości, które przedstawiłem poniżej.
ComponentCount
property ComponentCount: Integer;
Właściwość ComponentCount
zwraca liczbę obiektów umieszczonych na danym komponencie (rysunek 14.2.):
procedure TMainForm.Button1Click(Sender: TObject);
begin
Application.MessageBox(PChar('Na formularzu znajdują się ' + IntToStr(ComponentCount) + ' komponenty),
'Tak', MB_OK)
end;
Rysunek 14.2. Program demonstrujący znaczenie właściwości ComponentCount
ComponentIndex
property ComponentIndex: Integer;
Inna właściwość klasy TComponent
? Components[]
to tablica komponentów. Do konkretnego obiektu można się odwołać, podając jego indeks. Taki indeks natomiast jest zwracany przez właściwość ComponentIndex
.
ShowMessage(IntToStr(
Button1.ComponentIndex));
W powyższym przypadku w okienku pojawi się cyfra 1 jako numer identyfikacyjny dla komponentu Button1
.
Components
property Components[Index: Integer]: TComponent;
Jak wspomniałem powyżej, jest to tablica wszystkich elementów (komponentów) umieszczonych na danym obiekcie. Do konkretnej kontrolki można się odwołać, podając w nawiasie kwadratowym jej indeks:
Components[1].Free;
Powyższy kod spowoduje zniszczenie obiektu określonego numerem 1.
ComponentState
property ComponentState: TComponentState;
Sygnalizuje stan komponentu. Może zwrócić wartości takie, jak przedstawione w tabeli 14.1.
Tabela 14.1. Możliwe elementy typu TComponentState
Element | Opis |
---|---|
csDesigning | Komponent jest w trakcie projektowania, np. jego właściwości są zmieniane |
csDestroying | Komponent za chwilę zostanie zniszczony |
csFixups | Komponent jest podłączony do innego komponentu znajdującego się na innym formularzu |
csLoading | Komponent jest właśnie ładowany (tworzony) |
csReading | Komponent odczytuje swoje właściwości ze strumienia (pliku *.dfm) |
csWriting | Komponent zapisuje właściwości do strumienia |
Name
property Name: TComponentName;
Definiuje unikatową w ramach formularza nazwę dla komponentu. Wartość jest przeznaczona zarówno do zapisu, jak i do odczytu.
Owner
property Owner: TComponent;
Właściwość Owner
jest wskazaniem obiektu-rodzica. Jeżeli przykładowo komponent kkjest umieszczony na formularzu Form1
, to dzięki takiej konstrukcji:
Button1.Owner...
można odwołać się do formularza Form1
.
Metody klasy TComponent
Nowych metod wprowadzonych wraz z klasą TComponent
jest bardzo wiele. Większość z nich być może nigdy nie zostanie przez Ciebie użyta, gdyż służą np. do określenia pewnego stanu, w jakim ma znajdować się komponent.
Na pewno najważniejszą metodą klasy TComponent
jest konstruktor, który w odróżnieniu od klasy TObject
posiada dodatkowy parametr ? rodzica, czyli obiekt, na którym ma zostać utworzony nowy komponent.
constructor Create(AOwner: TComponent); virtual;
Utworzony w ten sposób konstruktor powinien zostać zwolniony metodą Free
.
Do zwalniania komponentu nigdy nie używaj metody Destroy. Procedura Free przed rzeczywistym zwolnieniem sprawdza, czy obiekt nie został już wcześniej zwolniony, co zapobiega pojawieniu się błędu typu Access Violation.
Oto przykład utworzenia komponentu typu TButton w sposób dynamiczny:
procedure TForm1.btnMakeClick(Sender: TObject);
var
MyButton : TButton;
begin
MyButton := TButton.Create(Form1);
MyButton.Parent := Form1; // określenie rodzica
MyButton.Caption := 'Przycisk 1';
end;
Rodzicem komponentu ustanawiamy formularz Form1
. Jeżeli nie określiliśmy dokładniejszych danych ? np. położenia komponentu ? zostanie on umieszczony w lewym, górnym rogu formularza.
Zwróć uwagę, że w powyższym kodzie nie ma instrukcji zwalniającej komponent ? Free
. Wszystko dzięki temu, że rodzicem nowego obiektu został formularz ? Form1
. Przy zwalnianiu (zamykaniu) okna program najpierw zwolni obiekty znajdujące się na nim, czyli m.in. nasz przycisk.
Ciekawą metodą klasy TComponent
jest procedura FindComponent
. Wspominam tu o niej, gdyż być może często będziesz korzystał z jej zalet. Umożliwia ona bowiem odnalezienie jakiegoś komponentu na formularzu i odwołanie się do konkretnych jego metod ? oto przykład:
procedure TMainForm.btnMakeClick(Sender: TObject);
begin
Randomize;
TEdit(FindComponent('Edit' + IntToStr(Random(3)+1))).Text := 'Nowa wartość';
end;
Powyższy kod spowoduje losowe odwołanie się do którejś z trzech umieszczonych na formularzu kontrolek typu TEdit
ki zmianę jej właściwości Text
(rysunek 14.3).
Rysunek 14.3. Losowe odwołanie się do której z kontrolek TEdit
TControl
Kolejną klasą w hierarchii obiektów VCL jest klasa TControl
. Reprezentuje ona komponenty wizualne (jest podstawową klasą dla tego rodzaju komponentów). Każdy obiekt typu TControl
posiada dodatkowe właściwości określające położenie komponentu: Top
(położenie w pionie), Left
(położenie w poziomie), Width
(szerokość) i Height
(wysokość).
Właściwości klasy TControl
Najważniejsze z właściwości (których nazwy możesz już pamiętać, gdyż są one wyświetlane w Inspektorze Obiektów) przedstawiłem w tabeli 14.2.
Tabela 14.2. Najważniejsze właściwości klasy TControl
Właściwość | Opis |
---|---|
Align | Określa wyrównanie komponentu względem obiektu-rodzica |
AutoSize | Określa wyrównanie komponentu względem obiektu-rodzica |
Caption | Tytuł komponentu (tekst wyświetlany na obiekcie) |
ClientHeight | Rozmiar obszaru roboczego (wysokość) |
ClientWidth | Rozmiar obszaru roboczego (szerokość) |
Color | Kolor tła obiektu |
Cursor | Kursor wyświetlany po umieszczeniu wskaźnika myszy nad obiektem |
Enabled | Określa, czy obiekt jest aktywny czy też nie |
Font | Czcionka używana przez komponent |
Hint | Wskazówka (etykietka podpowiedzi), pokazywana po umieszczeniu kursora nad obiektem |
ShowHint | Właściwość określa, czy podpowiedzi mają być wyświetlane |
Visible | Właściwość określa, czy komponent ma być widoczny podczas działania programu |
Procedury i funkcje klasy TControl
mają na celu przede wszystkim ustawienie danej wartości komponentu lub odczytanie jej. Owa klasa posiada także wiele metod nakazujących w konsekwencji wystąpienie pewnego zdarzenia (klasa TControl
wprowadza zdarzenia, dzięki którym jesteśmy w stanie zareagować na konkretne sytuacje).
TWinControl i TWidgetControl
Klasy TWinControl
(VCL) oraz TWidgetControl
(CLX) są bazowymi klasami dla kontrolek takich, jak przycisk (TButton
) i lista rozwijalna (TComboBox
). Owe klasy są klasami bazowymi dla bardzo wielu kontrolek w Delphi. Charakteryzują się następującymi właściwościami:
*Umożliwiają wyświetlanie tekstu (np. TMemo
).
*Umożliwiają odbieranie od użytkownika zdarzeń, reagowanie np. na naciśnięcie przycisku czy wpisywanie znaków za pomocą klawiatury.
*Kontrolki mogą być rodzicem innych obiektów.
*Kontrolki pochodzące z tych klas posiadają uchwyt (VCL) lub widget (CLX).
Właściwości klas TWinControl i TWidgetControl
Obie klasy definiują wiele właściwości, mających na celu zmodyfikowanie wyglądu danej kontrolki. Za przykład może tu posłużyć właściwość BevelKind
, która określa styl rysowania ramki dla komponentu. Do podobnych właściwości tego typu należą także: BevelEdges
, BevelInner
, BevelOuter
, BevelWidth
i BorderWidth
.
Ciekawą właściwością jest także Brush
, która określa kolor i styl wypełniającego wnętrze komponentu.
Zdarzenia
Zdarzenia klasy TWinControl
są związane z uzyskiwaniem dostępu do obiektu (aktywacja) czy też wprowadzaniem tekstu z klawiatury. Są to zdarzenia: OnDockDrop
, OnDockOver
, OnEnter
, OnExit
, OnKeyDown
, OnKeyPress
i OnKeyUp
.
Klasy TCustom
Wiele klas VCL ? np. TMemo
? posiada odpowiedniki w postaci klas bazowych, które rozpoczynają się przedrostkiem Custom, np. TCustomMemo
. Takie klasy pełnią wyłącznie rolę klas bazowych dla nowych komponentów i nie są w praktyce wykorzystywane. Np. komponent TDBMemo
także wywodzi się z klasy TCustomMemo
? dzięki temu rozwiązaniu programiści oszczędzają wiele wierszy kodu, dziedzicząc w zwykły sposób potrzebne metody i właściwości.
TGraphicControl
Jest to raczej specyficzny rodzaj kontrolek, a mianowicie komponent graficzny. Komponenty takie są oczywiście komponentami wizualnymi, ale nie mogą być aktywne. Nie mogą także pełnić roli rodzica innych komponentów. Śmiało można powiedzieć, że kontrolki tego typu są komponentami nieaktywnymi.
Natomiast każda kontrolka typu TGrpaphicControl
ma przyporządkowane płótno (Canvas
), którego odświeżenie następuje za pomocą wywołania metody Repaint
.
Budowa komponentu
Komponent w rzeczywistości jest klasą. Jest jednak klasą dość specyficzną, posiadającą określoną budowę, bez której taka klasa nie byłaby po prostu komponentem.
Właściwości
W klasie właściwości służą do przechowywania w pamięci pewnej wartości. W przypadku komponentów wartości standardowo wprowadzone w Inspektorze Obiektów są zapisywane w pliku *.dfm. Jednakże aby właściwości były widoczne w Inspektorze Obiektów, należy użyć specjalnej sekcji ? Published. O klasach mówiłem już w rozdziale 3., lecz o sekcji published
jedynie wówczas wspomniałem. Jeśli tworzysz komponenty i chcesz dodać do nich właściwości, kod w sekcji published musi wyglądać tak:
TMojKomponent = class(TLabel)
private
FText : String;
procedure DoIt(Value : String);
published
properties Text : String read FText write DoIt;
end;
Właściwości w klasie musimy poprzedzać słowem kluczowym properties. Powyższy kod oznacza, że właściwość Text
będzie typu String
, a jej odczytanie będzie równoważne z odczytaniem danych ze zmiennej FText
. Wszystko dzięki klauzuli read
, po której następuje nazwa zmiennej umieszczonej w sekcji Private (nie jest konieczne umieszczenie zmiennej FText
w sekcji Public). Natomiast w przypadku, gdy użytkownik zechce przypisać jakąś wartość właściwości Text
, wywołaprocedurę DoIt
. Nowa wartość zostanie przekazana do procedury DoIt
w parametrze Value
.
Warto, abyś przyzwyczaił się do takiej konstrukcji w przypadku komponentów, gdyż w przyszłości często będziemy ją stosowali.
Chcąc uczynić daną właściwość przeznaczoną tylko do zapisu lub tylko do odczytu, wystarczy pominąć jedną z klauzul ? albo read
, albo write
.
Istnieje możliwość dodania na samym końcu słowa kluczowego default
, które powoduje ustawienie domyślnej wartości właściwości.
Rodzaje właściwości
Pracując już jakiś czas z Delphi, zapewne zauważyłeś, że nie wszystkie właściwości w Inspektorze Obiektów są przedstawione tak samo. Niektóre proste właściwości służą jedynie do wpisywania nowej wartości i odczytywania jej. Z kolei inne właściwości są przedstawione w postaci listy rozwijalnej. Tabela 14.3 przedstawia różne typy właściwości.
Tabela 14.3. Typy właściwości
Typy właściwości | Opis |
---|---|
Wyliczeniowa | Właściwość wyliczeniowa prezentowana jest w Inspektorze Obiektów w formie listy rozwijalnej (łączenie z typem `Boolean`) ? rysunek 14.4 |
Zbiorowa | Właściwość zbiorowa (`set`) jest prezentowana w formie listy rozwijalnej. Wartości w zbiorze można zmieniać na `True `lub `False`, w zależności od tego, czy właściwość przynależy do zbioru czy też nie (rysunek 14.5) |
Obiektowa | Czasami zdarza się, że właściwość danego komponentu jest konkretnym obiektem (klasą). Tak jest np. z właściwością Lines komponentu `TMemo`, która jest właściwością typu `TStringList`. Wówczas przy próbie edycji otwierany zostaje nowy edytor |
Rysunek 14.4. Właściwość wyliczeniowa
Rysunek 14.5. Właściwość zbiorowa
Zdarzenia
Zdarzenia służą do zaprogramowania określonej reakcji w momencie zajścia jakiegoś wydarzenia (np. kliknięcia myszą). Podczas projektowania własnych komponentów dość często będziesz zmuszony do przypisywania w kodzie programu jakiegoś zdarzenia, tzw. procedury zdarzeniowej. Procedura zdarzeniowa musi mieć określone parametry, zależne od rodzaju zdarzenia. Przykładowo procedura zdarzeniowa dla zdarzenia OnClick
musi mieć parametr Sender
typu TObject
. Zatem deklaracja takiej procedury będzie wyglądać tak:
private
procedure MyOnClick(Sedner: TObject);
end;
...
procedure TForm1.Button1Click(Sender: TObject);
var
MyButton : TButton;
begin
MyButton := TButton.Create(Form1);
MyButton.Parent := Form1;
MyButton.OnClick := MyOnClick; // przypisanie procedury zdarzeniowej
end;
...
W przypadku, gdyby procedura MyOnClick
nie posiadała parametru Sender, próba kompilacji programu zakończyłaby się komunikatem o błędzie: [Error] Unit1.pas(32): Incompatible types: 'Parameter lists differ'.
Metody
Przypominam, że metody to inaczej procedury i funkcje zawarte w klasie. Aby owe metody były (w przypadku komponentów) widoczne dla użytkownika komponentu, należy umieścić je w sekcji public
.
RTTI
Zajmiemy się teraz pojęciem zwanym RTTI (Run Time Type Information), które umożliwia nam dostęp do informacji na temat właściwości, zdarzeń poszczególnych komponentów ? wszystko podczas działania skompilowanej aplikacji.
Właściwości obiektu
Funkcje, z których będziemy korzystać, znajdują się w pliku TypInfo.pas ? dodaj więc do listy uses moduł TypInfo
. W gruncie rzeczy pobieranie listy właściwości danego obiektu jest proste ? wystarczy skorzystać z funkcji GetPropList
. W rzeczywistości okazuje się, że czeka nas wiele pracy z wskaźnikami.
Funkcja GetPropList w module TypInfo jest zadeklarowana następująco:
function GetPropList(TypeInfo: PTypeInfo; TypeKinds: TTypeKinds; PropList: PPropList): Integer;
Pierwszy parametr tej funkcji może zawierać wskazanie komponentu, którego będzie dotyczyć operacja ? w to miejsce możemy np. wstawić instrukcję Button1.ClassInfo
. Drugi parametr jest pewnego rodzaju filtrem. Określa, jakie właściwości chcemy wyświetlić. Jest to właściwość typu zbiorowego, zadeklarowana następująco:
type
TTypeKind = (tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat,
tkString, tkSet, tkClass, tkMethod, tkWChar, tkLString, tkWString,
tkVariant, tkArray, tkRecord, tkInterface, tkInt64, tkDynArray);
TTypeKinds = set of TTypeKind;
Możemy podać, jakie właściwości chcemy wyświetlić. Najlepszym rozwiązaniem jest wstawienie jako drugiego parametru słowa tkProperties
, które obejmuje wszystkie właściwości. Podsumowując, wywołanie funkcji GetPropList może wyglądać tak:
GetPropList(btnGet.ClassInfo, tkProperties, PropList);
Zakładamy, że ostatni parametr ? PropList
? jest typu PPropList
. Z kolei typ PPropList
jest zadeklarowany następująco:
PPropList = ^TPropList;
TPropList = array[0..16379] of PPropInfo;
A zatem okazuje się, że PPropList
jest wskazaniem typu TPropList
, który z kolei jest tablicą PPropInfo
:
PPropInfo = ^TPropInfo;
TPropInfo = packed record
PropType: PPTypeInfo;
GetProc: Pointer;
SetProc: Pointer;
StoredProc: Pointer;
Index: Integer;
Default: Longint;
NameIndex: SmallInt;
Name: ShortString;
end;
Wiem, wiem, że to wszystko jest zagmatwane ? jeden typ jest wskazaniem drugiego typu itp., ale musisz się przyzwyczaić, że całe VCL w ten właśnie sposób jest zbudowane. Pełny kod programu został przedstawiony w listingu 14.1, a program w trakcie działania ? na rysunku 14.6.
Listing 14.1. Pełny kod źródłowy modułu
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Grids, ValEdit;
type
TMainForm = class(TForm)
btnGet: TButton;
vleValue: TValueListEditor;
procedure btnGetClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
uses TypInfo;
procedure TMainForm.btnGetClick(Sender: TObject);
var
PropList : PPropList;
i : Integer;
begin
GetMem(PropList, SizeOf(PropList^)); // zarezerwuj pamięć
try
// pobierz listę właściwości
GetPropList(btnGet.ClassInfo, tkProperties, PropList);
for I := 0 to High(PropList^) ?1 do
begin
if (PropList^[i] <> nil) then
vleValue.InsertRow(PropList^[i].Name, PropList^[i].PropType^.Name, True)
end;
finally
FreeMem(PropList);
end;
end;
end.
Rysunek 14.6. Właściwości komponentu TButton
Wywołując funkcję GetPropList w ten sposób:
GetPropList(btnGet.ClassInfo, tkProperties + [tkMethod], PropList);
spowodujemy, że pod uwagę zostaną wzięte także metody danego obiektu.
Dokładniejsze informacje o obiekcie
W poprzednim podpunkcie omówiłem zagadnienie związane z pobieraniem informacji na temat właściwości danego obiektu. Tym razem zajmiemy się pobraniem informacji na temat samego obiektu (nazwa klasy, nazwa modułu itp.). Możemy to zrobić za pomocą funkcji GetTypeData, która zdefiniowana jest następująco:
function GetTypeData(TypeInfo: PTypeInfo): PTypeData;
Funkcję można wywołać np. w ten sposób:
ClassInfo := GetTypeData(btnGet.ClassInfo);
Dzięki temu rekord ClassInfo
będzie zawierał informacje na temat komponentu btnGet
(typu TButton
). Rekord ClassInfo
jest rekordem typu PTypeData
, wyglądającym następująco:
PTypeData = ^TTypeData;
TTypeData = packed record
case TTypeKind of
tkUnknown, tkLString, tkWString, tkVariant: ();
tkInteger, tkChar, tkEnumeration, tkSet, tkWChar: (
OrdType: TOrdType;
case TTypeKind of
tkInteger, tkChar, tkEnumeration, tkWChar: (
MinValue: Longint;
MaxValue: Longint;
case TTypeKind of
tkInteger, tkChar, tkWChar: ();
tkEnumeration: (
BaseType: PPTypeInfo;
NameList: ShortStringBase));
tkSet: (
CompType: PPTypeInfo));
tkFloat: (
FloatType: TFloatType);
tkString: (
MaxLength: Byte);
tkClass: (
ClassType: TClass;
ParentInfo: PPTypeInfo;
PropCount: SmallInt;
UnitName: ShortStringBase;
{PropData: TPropData});
tkMethod: (
MethodKind: TMethodKind;
ParamCount: Byte;
ParamList: array[0..1023] of Char
{ParamList: array[1..ParamCount] of
record
Flags: TParamFlags;
ParamName: ShortString;
TypeName: ShortString;
end;
ResultType: ShortString});
tkInterface: (
IntfParent : PPTypeInfo; { ancestor }
IntfFlags : TIntfFlagsBase;
Guid : TGUID;
IntfUnit : ShortStringBase;
{PropData: TPropData});
tkInt64: (
MinInt64Value, MaxInt64Value: Int64);
end;
Program wykorzystujący powyższy rekord przedstawiony jest na rysunku 14.7. Sam kod źródłowy znajduje się w listingu 14.2.
Rysunek 14.7. Informacje na temat obiektu TButton
Listing 14.2. Kod źródłowy modułu
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Grids, ValEdit;
type
TMainForm = class(TForm)
btnGet: TButton;
vleValues: TValueListEditor;
procedure btnGetClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
uses TypInfo;
procedure TMainForm.btnGetClick(Sender: TObject);
var
ClassInfo : PTypeData;
begin
ClassInfo := GetTypeData(btnGet.ClassInfo);
vleValues.InsertRow('Nazwa klasy', ClassInfo.ClassType.ClassName, True);
vleValues.InsertRow('Klasa bazowa', ClassInfo.ClassType.ClassParent.ClassName, True);
vleValues.InsertRow('Moduł', ClassInfo.UnitName, True);
vleValues.InsertRow('Liczba właściwości', IntToStr(ClassInfo.PropCount), True);
vleValues.InsertRow('Liczba parametrów', IntToStr(ClassInfo.ParamCount), True);
vleValues.InsertRow('Rozmiar', IntToStr(btnGet.InstanceSize), True);
end;
end.
Podsumowanie
Niniejszy rozdział poświęcony był bardziej teoretycznym zagadnieniom, związanym z komponentami i z biblioteką VCL. Przybliżyłem w nim budowę samej biblioteki VCL, wspomniałem też o podstawowych klasach, na których opiera się owa biblioteka. Nie zapomniałem także o CLX. Praktycznym tworzeniem komponentów w Delphi zajmiemy się w kolejnym rozdziale.
Załączniki:
Więcej informacji Delphi 2005. Kompendium programisty Adam Boduch Format: B5, stron: 1048 oprawa twarda Zawiera CD-ROM |
"AutoSize Określa wyrównanie komponentu względem obiektu-rodzica"
Hehe..dobre :D
Pojawiło się mnóstwo '?' w kodzie, warto by go poprawić.
mam kłopot z funkcją FindComponent.
zrobiłem sobie nową klasę w innym unicie ( jak w tym tekście o klasach w kompendium ).
dodałem wszystkie potrzebne unity w USES - między innymi Classes, a i tak nie rozpoznaje funkcji FindComponent :?
Undeclared identifier: 'FindComponent'.
Dziwne jest to, że w głównym module ( ten z formami ), ta funkcja jest rozpoznawalna.
POMOCY!!!