Rozdział 3. Programowanie obiektowe
Adam Boduch
Z pewnością ten rozdział niniejszej książki będzie dla Ciebie bardziej interesujący. Nauczysz się bowiem korzystać z dobrodziejstw, jakie oferuje Delphi, czyli komponentów i klas, dzięki którym programowanie daje lepsze, a co najważniejsze szybsze efekty.
1 VCL
2 Podstawowy kod modułu
2.1 Plik DFM formularza
3 Umieszczanie komponentów na formularzu
3.2 Umieszczanie komponentów
3.3 Zmiana właściwości komponentu
3.3.1 Szerokość i wysokość
3.4 Zdarzenia komponentów
3.4.2 Lista wygenerowanych zdarzeń w Inspektorze obiektów
3.4.3 Generowanie pozostałych zdarzeń
4 Kod generowany automatycznie
5 Klasy
5.5 Czym właściwie są klasy?
5.6 Tworzenie klas
5.7 Definicja metod
5.8 Tworzenie klasy
5.9 Poziomy dostępu do klasy
5.9.4 Sekcja private
5.9.5 Sekcja protected
5.9.6 Sekcja public
5.10 Dziedziczenie
5.10.7 Klasa domyślna
5.11 Typy metod
5.11.8 Metody wirtualne kontra metody dynamiczne
5.12 Konstruktory i destruktory
6 Przykład użycia klas
6.13 Ogólne założenia
6.14 Tworzenie modułu Engine
6.14.9 Szablon
6.14.10 Wygląd klasy
6.14.11 Kod źródłowy modułu
6.14.11.1 Konstruktor i destruktor
6.14.11.2 Dokonywanie zmian w szablonie
6.15 Interfejs programu
6.16 Kod źródłowy formularza głównego
6.17 Uruchamianie programu
7 Parametr Sender procedury zdarzeniowej
7.18 Przechwytywanie informacji o naciśniętym klawiszu
7.18.12 Obsługiwanie zdarzeń przez inne komponenty
7.19 Obsługa parametru Sender
8 Operatory is i as
9 Parametr Self
10 Łańcuchy tekstowe
10.20 ShortString
10.21 AnsiString
10.22 WideString
10.23 Łańcuchy z zerowym ogranicznikiem
11 Operacje na łańcuchach
11.24 Łączenie łańcuchów
11.25 Wycinanie łańcucha
11.26 Uzyskiwanie fragmentów łańcucha
11.27 Wstawianie danych do łańcucha
11.28 Wyszukiwanie danych w łańcuchu
11.29 Pozostałe funkcje
11.29.13 AnsiMatchStr
11.29.14 AnsiReverseString
11.29.15 DupeString
11.29.16 SearchBuf
11.29.17 LowerCase
11.29.18 UpperCase
11.29.19 Trim
11.29.20 WrapText
12 Typ wariantowe
13 Właściwości
13.29.21 Align
13.29.22 Anchors
13.29.23 Constraints
13.29.24 Cursor
13.29.25 DragCursor, DragKind, DragMode
13.29.26 Font
13.29.27 HelpContex, HelpKeyword, HelpType
13.29.28 Hint, ShowHint
13.29.29 Visible
13.29.30 Tag
14 Zdarzenia
14.29.31 OnClick
14.29.32 OnContextPopup
14.29.33 OnDblClick
14.29.34 OnActivate, OnDeactivate
14.29.35 OnClose, OnCloseQuery
14.29.36 OnPaint
14.29.37 OnResize
14.29.38 OnShow, OnHide
14.29.39 OnMouseDown, OnMouseMove, OnMouseUp, OnMouseWheel, OnMouseWheelDown, OnMouseWheelUp
14.29.40 Zdarzenia związane z dokowaniem
14.29.40.3 OnDockDrop
14.29.40.4 OnDockOver
14.29.40.5 OnStartDock
14.29.40.6 OnStartDrag
14.29.40.7 OnEndDrag, OnEndDock
14.29.40.8 OnDragDrop
14.29.40.9 OnDragOver
14.29.40.10 Przykładowy program
15 Wyjątki
15.30 Słowa kluczowe try..except
15.31 Słowa kluczowe try..finally
15.32 Słowo kluczowe raise
15.33 Klasa Exception
15.34 Selektywna obsługa wyjątków
15.35 Zdarzenie OnException
15.35.41 Obsługa wyjątków
16 Klasa TApplication
16.36 Właściwości klasy TApplication
16.36.42 Active
16.36.43 ExeName
16.36.44 ShowMainForm
16.36.45 Title
16.37 Metody klasy TApplication
16.37.46 CreateForm
16.37.47 Minimize
16.37.48 Terminate
16.37.49 MessageBox
16.37.50 ProcessMeessages
16.37.51 Restore
16.38 Zdarzenia klasy TApplication
17 Podsumowanie
W tym rozdziale:
*nauczysz się obsługiwać komponenty i zmieniać wartości właściwości;
*dowiesz się, w jaki sposób generować zdarzenia;
*nauczysz się projektować własne klasy;
*nauczysz się przechwytywania wyjątków.
VCL
Skrót VCL pochodzi od słów Visual Component Library, co można przetłumaczyć jako wizualną bibliotekę komponentów. „Rewolucyjność” Delphi polegała właśnie na zastosowaniu biblioteki VCL, która znacznie ułatwiała programistom szybkie tworzenie aplikacji. VCL znacznie upraszcza tworzenie programów, udostępniając funkcje, dzięki którym za pomocą np. jednego wiersza kodu wykonać można skomplikowane na pozór czynności. Przykładowo załadowanie i wyświetlenie obrazka realizowane jest za pomocą jednego wiersza:
Image.Picture.LoadFromFile('C:\obrazek.bmp');
Programowanie w Delphi opiera się właśnie w dużym stopniu na zastosowaniu komponentów (obiektów) i odwołaniu się do ich metod lub właściwości.
Metoda to procedura lub funkcja danej klasy, która wykonuje jakieś czynności. Szerzej omówię tej temat w dalszej części rozdziału.
Podstawowy kod modułu
Stwórz nowy projekt, wybierając z menu File polecenie New/Application. Naciśnij klawisz F12, co spowoduje przełączenie się do Edytora kodu, którego zawartość widnieje w listingu 3.1.
Listing 3.1. Podstawowy kod formularza, wygenerowany przez Delphi
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
end.
Moduł ten składa się z podstawowych instrukcji, jakie powinny się w nim znajdować — nie powinny być one już dla Ciebie obce.
To, co może przykuć Twoją uwagę, to duża ilość modułów włączona w ramach nowego projektu oraz nietypowe instrukcje, zadeklarowane jako nowy typ. Przedstawiony poniżej kod nazywamy klasą.
type
TForm1 = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;
Klasa (nie mylić z komponentem!) może zawierać metody (procedury i funkcje) stale ze sobą współpracujące w celu wykonania pewnych czynności.
O klasach będzie mowa w dalszej części rozdziału — na razie nie zaprzątaj sobie tym głowy.
Plik DFM formularza
Kolejna niespotykana wcześniej instrukcja to dyrektywa:
{$R *.dfm}
Nakazuje ona włączenie do modułu pliku formularza, który zawiera informacje dotyczące zarówno samego formularza, jak i komponentów w nim się znajdujących.
Podgląd formularza uzyskuje się poprzez kliknięcie prawym przyciskiem myszy w obszarze formularza i wybranie z menu podręcznego polecenia View as Text. Wówczas w edytorze kodu pojawi się kod, taki jak w listingu 3.2.
Listing 3.2. Kod formularza
object Form1: TForm1
Left = 192
Top = 107
Width = 696
Height = 480
Caption = 'Form1'
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
end
Na razie plik ten zawiera tylko informacje o formularzu, gdyż nie umieściliśmy jeszcze w projekcie żadnych komponentów.
Nie jest zalecane bezpośrednie modyfikowanie pliku *.dfm. Lepiej dokonać zmian jedynie w Inspektorze Obiektów, a Delphi automatycznie uaktualni wówczas plik *.dfm.
Ponowne przełączenie się na podgląd formularza realizowane jest poprzez kliknięcie prawym przyciskiem myszy w obszarze Edytora kodu i wybranie z menu podręcznego polecenia View As Form.
Umieszczanie komponentów na formularzu
Komponenty dzielą się na wizualne i niewidoczne. Komponenty wizualne to takie komponenty, które pomagają ulepszyć interfejs (wygląd) naszego programu — są to różne przyciski, kontrolki edycyjne czy listy rozwijalne. Natomiast komponenty niewidoczne realizują swoje, określone zadania, lecz po uruchomieniu programu nie są widoczne na formularzu.
Umieszczanie komponentów
O umieszczeniu komponentów na formularzu wspominałem już w rozdziale 1., ale kilka słów przypomnienia nie zaszkodzi.
Na zakładce Standard palety komponentów odszukaj przycisk TButton; komponent ten jest oznaczony ikonką symbolizującą przycisk. Kliknij tę ikonę, a następnie przemieść kursor nad formularz — ponowne kliknięcie spowoduje umieszczenie obiektu w danym punkcie.
Komponent umieszczony na formularzu można swobodnie przemieszczać, rozciągać i zwężać — wszystko to można czynić za pomocą myszy, ale równie dobrze można zmieniać odpowiednie właściwości w Inspektorze obiektów.
O położeniu komponentu decydują właściwości Left (położenie w poziomie), Top (położenie w pionie), Width (szerokość komponentu) i Height (wysokość komponentu); wszystkie wartości podawane są w pikselach. Właściwości te obecne są w każdym komponencie wizualnym.
Zmiana właściwości komponentu
W naszym programie najpierw musimy dokonać pewnych zmian we właściwościach samego formularza. Aby to uczynić, musisz zaznaczyć formularz — wystarczy jedno kliknięcie w obszarze projektanta formularzy.
Możesz także z listy rozwijalnej Inspektora obiektów wybrać pozycję Form1.
Każdy komponent posiada właściwość Name; określa ona nazwę obiektu — czy to formularza, czy komponentu. Nazwa ta musi być unikalna — za jej pomocą możemy się w kodzie odwoływać zarówno do metod obiektu, jak i do właściwości.
Na samym początku należałoby dokonać zmiany nazwy głównego formularza projektu. W tym celu odszukaj w Inspektorze obiektów pozycję Name i zaznacz ją jednym kliknięciem (rysunek 3.1).
Rysunek 3.1. Zaznaczona właściwość Name
Co prawda nic się nie stanie, jeżeli zachowamy taką nazwę, jaka jest dotychczas, czyli Form1 — jest to nazwa domyślna — lecz regułą stało się, aby nazywać formularze, powiedzmy — bardziej opisowo. Jeżeli Twoja aplikacja jest bardzo skomplikowana i posiada wiele formularzy (może tak być), to nazywając je kolejno Form1, Form2 itd. łatwo się pogubić. Dlatego też zazwyczaj nazwy formularzy określa się według funkcji, jakie pełnią — np. MainForm, AboutForm. Pamiętaj, aby zawsze dodać w nazwie końcówkę Form (ang. formularz)!
Zmień więc właściwość Name, wpisując słowo MainForm
i akceptując je poprzez naciśnięcie klawisza Enter. Od tej pory formularz będzie nosił nazwę MainForm (formularz główny).
Szerokość i wysokość
Mając w dalszym ciągu zaznaczony formularz, zmień jego właściwości Width i Height na, odpowiednio, 400 (Width) i 125 (Height). Rozmiar formularza zostanie zmieniony wraz ze zmianą właściwości w Inspektorze obiektów.
Zdarzenia komponentów
W otwartym projekcie umieść komponent TLabel
, nadaj jego właściwości Caption wartość Przykład użycia komponentu TLabel
, natomiast właściwości Name nadaj wartość lblMain
.
Właściwość Name komponentu TButton
, uprzednio umieszczonego na formularzu, zmień na btnMain
, natomiast Caption na Naciśnij mnie!
.
Jak już napisałem w rozdziale pierwszym, zdarzenia służą do tego, aby odpowiednio reagować na sytuacje stwarzane w wyniku działania programu. Podczas pracy z projektem kliknij dwukrotnie przycisk; Delphi „przeniesie” Cię do Edytora kodu i automatycznie wygeneruje procedurę zdarzeniową.
procedure TMainForm.btnMainClick(Sender: TObject);
begin
end;
Procedura ta jest nieco inna niż te, którymi zajmowaliśmy się w poprzednim rozdziale. Możesz w niej wpisać kod, który będzie wykonywany po naciśnięciu przycisku przez użytkownika. Doprowadź tę procedurę (metode) do takiej postaci:
procedure TMainForm.btnMainClick(Sender: TObject);
begin
lblMain.Caption := 'Napis zmieniono';
end;
Uruchomienie programu i naciśnięcie przycisku spowoduje zmianę napisu w etykiecie. Widzisz więc, że poszczególne właściwości komponentów można zmieniać również w kodzie, także podczas działania aplikacji. Umożliwia to operator — znak kropki (.):
Nazwa_Obiektu.Nazwa_Właściwości := 'Nowa wartość';
Dostęp do poszczególnych elementów odbywa się mniej więcej tak, jak to widać powyżej.
Lista wygenerowanych zdarzeń w Inspektorze obiektów
Zaznacz ponownie komponent TButton
i kliknij zakładkę Events w Inspektorze obiektów. Okno Inspektora obiektów powinno wyglądać tak, jak na rysunku 3.2.
Rysunek 3.2. Lista zdarzeń Inspektora obiektów
Zwróć uwagę, że jedna pozycja (OnClick) jest już zajęta — oznacza to, że zdarzenie OnClick komponentu btnMain
jest już wygenerowane. Zostało to wykonane automatycznie po tym, jak dwukrotnie kliknąłeś przycisk.
Generowanie pozostałych zdarzeń
Korzystać możesz ze wszystkich zdarzeń, jakie oferuje Ci Delphi w obrębie danego komponentu. Przykładowo zdarzenie OnMouseMove umożliwia Ci oprogramowanie zdarzenia przesuwania kursora nad obiektem. Innymi słowy, możesz odpowiednio na taką sytuację zareagować.
Jak odbywa się generowanie zdarzeń z poziomu Inspektora obiektów ? Zaznacz zdarzenie, klikając je — niech to będzie OnMouseMove. Po prawej stronie pojawi się lista rozwijalna, która na razie jest pusta. Przesuń kursor nad białe pole — powinien zmienić swój kształt. W tym momencie kliknij dwukrotnie — spowoduje to wygenerowanie zdarzenia OnMouseMove.
procedure TMainForm.btnMainMouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Integer);
begin
end;
W niniejszej książce dla określenia nazw komponentów będę posługiwał się prawidłowym określeniem z literą T na początku — np. TButton
, TLabel
. Jest to prawidłowa nazwa dla komponentów VCL, chociaż wiele osób nadaje komponentom nazwy pozbawione tej litery na początku — Button, Label.
Jeżeli wygenerowane zdarzenie pozostanie puste, tj. nie będzie zawierało żadnego kodu, zostanie usunięte podczas kompilacji lub zapisu projektu.
Kod generowany automatycznie
Zwróć uwagę na to, że podczas gdy umieszczasz komponenty na formularzu i dokonujesz różnych zmian, Delphi jednocześnie pracuje w tle, modyfikując odpowiednio kod źródłowy programu. Po umieszczeniu przez Ciebie na formularzu dwóch kontrolek i wygenerowaniu zdarzenia cały kod programu przedstawia się tak, jak na listingu 3.3.
Listing 3.3. Kod formularza po dokonaniu zmian
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TMainForm = class(TForm)
btnMain: TButton;
Label1: TLabel;
procedure btnMainClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
procedure TMainForm.btnMainClick(Sender: TObject);
begin
lblMain.Caption := 'Napis zmieniono';
end;
end.
Dla nas istotnym elementem powyższego listingu jest ten fragment:
type
TMainForm = class(TForm)
btnMain: TButton;
Label1: TLabel;
procedure btnMainClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
Jest to kod klasy formularza. Zauważ, że Delphi automatycznie po umieszczeniu przez nas komponentu na formularzu dodaje odpowiednią instrukcję do klasy. Wygląda to tak, jak deklaracja zmiennej — lewa strona to nazwa obiektu, a po przecinku określony jest jego typ.
Podczas np. zmiany nazwy komponentu Delphi automatycznie zastąpi starą nazwę w kodzie programu.
Klasy
Klasa jest pewną strukturą metod oraz właściwości i pól, połączaną w jedną całość. Omówienie tematu, jakim są klasy, wydaje się trudnym zadaniem — szczególnie na tym etapie znajomości Delphi, na którym jesteś. Postaram się wyjaśnić to jak najdokładniej — jeżeli wszystko zrozumiesz, nie powinieneś mieć już problemów ze zrozumieniem zasad, jakimi rządzi się VCL.
Czym właściwie są klasy?
Cały VCL opiera się na klasach; upraszcza to tworzenie nowych komponentów i obiektów — wszystko jest ze sobą ściśle powiązane.
Załóżmy, że tworzysz duży projekt, zawierający wiele formularzy. Dla tak dużego i skomplikowanego projektu bardzo ważne jest stworzenie tzw. engine. Słowo to określić można mianem silnika (mechanizmu) albo serca aplikacji. Są to procedury lub funkcje (lub po prostu klasa), wykonujące najważniejsze czynności programowe, związane z prawidłowym działaniem samej aplikacji. Podczas omawiania klas przedstawię program, który będzie korzystał z klas, a całe jego „serce” znajdzie się w module Engine.pas.
Zadaniem programu będzie tworzenie stron WWW z szablonów. W odpowiednie miejsce programu wstawiane będą polecenia, które program automatycznie umieści w pliku HTML (szablonie).
Tworzenie klas
Klasy można zadeklarować albo w sekcji Interface
modułu, albo w sekcji Implementation
. Klasy zawsze muszą być deklarowane jako nowy typ danych — podstawowa konstrukcja wygląda tak:
type
TEngine = class
end;
Słowem kluczowym jest słowo class
, informujące kompilator, że ma do czynienia z klasą. Definicja klasy — jak każdego obiektu — zakończona musi być słowem end
;.
Klasy mogą zawierać jedynie deklarację funkcji i procedur (które nazywamy metodami) oraz deklarację zmiennych (wówczas nie należy używać słowa var
).
Definicja metod
Kompilator musi „wiedzieć”, że dana metoda należy do określonej klasy. Rozważmy przykład następującej klasy:
type
TEngine = class { brak średnika! }
procedure Parse;
end;
Procedura Parse
należy do danej klasy, ale jest to jedynie jej deklaracja — konieczne jest także umieszczenie kodu owej procedury w sekcji Implementation
:
procedure TEngine.Parse;
begin
end;
Zwróć uwagę na specyficzny zapis nagłówka procedury. Aby owa procedura była utożsamiana z klasą TEngine
, najpierw należy wpisać nazwę klasy, a dopiero po operatorze kropki nazwę procedury.
W Delphi wymagane jest, aby zadeklarowana w klasie metoda posiadała definicję, czyli — krótko mówiąc — aby w kodzie źródłowym znalazł się kod owej metody. W przeciwnym wypadku zostanie wyświetlony błąd: [Error] Engine.pas(20): Unsatisfied forward or external declaration: 'TEngine.xxx'.
Zalecane jest podczas tworzenia klas stosowanie specjalnego nazewnictwa; nazwa klasy powinna zaczynać się od litery T.
Tworzenie klasy
Oprócz zwykłej deklaracji nowej klasy należy stworzyć zmienną, która wskazywać będzie na nowy typ. Nie jest możliwe proste odwołanie się do metod danej klasy — wcześniej należy nowy obiekt utworzyć, co pozwoli na określenie przydziału pamięci. Dopiero wtedy można uzyskać pełny dostęp do funkcji, jakie oferuje nam dana klasa. Odbywa się to następująco:
procedure TMainForm.btnGenerateClick(Sender: TObject);
var
Template : TEngine;
begin
Template := TEngine.Create;
end;
Na samym początku konieczne jest stworzenie zmiennej wskazującej dany obiekt. Przydział pamięci odbywa się za pośrednictwem metody Create
, obecnej w każdej klasie:
Template := TEngine.Create;
Metoda Create
alokuje pamięć dla konkretnego obiektu, ale co ze zwalnianiem tej pamięci? Każda klasa posiada także ukrytą metodę Free
(lub Destroy
), która zwalnia pamięć. Pamiętaj o tym, aby zawsze po zakończeniu korzystania z klasy zwalniać pamięć dla niej przydzieloną.
Template.Free; // zwolnienie pamięci!
Do zwalniania pamięci służą dwie metody — Free
oraz Destroy
. Zdecydowanie bardziej zalecaną metodą jest Free
, dlatego że ponowne jej wywołanie w przypadku, gdy klasa została już zwolniona, nie powoduje błędu.
Korzystanie z naszej klasy TEngine
powinno odbywać się takimi etapami:
procedure TMainForm.btnGenerateClick(Sender: TObject);
var
Template : TEngine; // deklaracja zmiennej wskazującej klasę
begin
Template := TEngine.Create; // utworzenie klasy
{ wywołanie metod klasy }
Template.Parse;
Template.Free; // zwolnienie pamięci
end;
Poziomy dostępu do klasy
Posłużmy się poprzednim przykładem, w którym mówiłem o tworzeniu engine i dużego projektu opartego na głównej klasie. Załóżmy, że pracujesz w zespole, a Tobie zostało przydzielone zadanie stworzenia engine. Niektóre elementy wcale nie muszą być udostępniane na zewnątrz klasy. Bo i w jakim celu? Klasa może zawierać elementy, które nie powinny być ujawniane innym klasom lub innym modułom. Elementy te są tworzone tylko na potrzeby tylko tej klasy; niewłaściwe ich wykorzystanie przez użytkowników może spowodować niepożądane skutki.
Delphi udostępnia trzy poziomy dostępu do klasy — private
(prywatne), protected
(chronione), public
(publiczne). W zależności od sekcji, w której te metody będą umieszczone, będą one inaczej interpretowane przez kompilator.
Przykładowa deklaracja klasy z użyciem sekcji może wyglądać tak:
TEngine = class
private
FFileName : String;
FFileLines : TStringList;
protected
procedure Execute(Path : String);
public
Pattern : TTemplate;
Replace : TTemplate;
procedure Parse;
constructor Create(FileName : String);
destructor Destroy; override;
end;
Jak widzisz, wystarczy, że wpiszesz odpowiednie słowo kluczowe w klasie i poniżej jego będziesz wpisywał metody.
Sekcja private
Metody umieszczone w sekcji private
są określane jako prywatne. Oznacza to, że nie będą widoczne na zewnątrz modułu, w którym znajduje się dana klasa. A zatem po próbie odwołania się do metody umieszczonej w sekcji private
kompilator zasygnalizuje błąd — nazwa owej metody nie będzie mogła być przez niego rozpoznana.
Metoda z sekcji private
nie jest widoczna tylko w przypadku, gdy próbujesz się do niej odwołać z innego modułu.
Sekcja protected
Metoda umieszczona w sekcji protected
jest widoczna zarówno dla modułu, w którym znajduje się klasa, jak i dla całej klasy. Jest to jakby drugi poziom ochrony, gdyż metody z sekcji protected
są widoczne dla innych klas, które dziedziczą po naszej klasie! Aby to zrozumieć, należy wiedzieć, czym jest dziedziczenie — zajmiemy się tym w dalszej części rozdziału.
Sekcja public
Metody umieszczone w sekcji public
są widoczne dla wszystkich innych klas i modułów.
Istnieje jeszcze jedna sekcja — published
, ale dokładnie omówię ją w części czwartej niniejszej książki.
Dziedziczenie
Tym, co zapewnia szybki i dynamiczny rozwój VCL, jest dziedziczenie. Polega to na budowaniu nowych klas na bazie klas już istniejących. Dzięki temu wiele ważnych metod zawartych może być jedynie w klasie bazowej — inne klasy po niej dziedziczące nie muszą ponownie zawierać tych samych funkcji.
Po utworzeniu nowego projektu klasa znajdująca się w module wygląda następująco:
type
TForm1 = class(TForm)
Oznacza to, że w tym momencie tworzony jest nowy typ danych — TForm1
, który dziedziczy po klasie TForm
. Nazwę dziedziczonej klasy należy wpisać w nawiasie przy słowie kluczowym class
. Dzięki temu nasz formularz posiada takie właściwości, jak Height, Width czy Caption, które są obecne w klasie TForm
— nasza klasa jedynie je dziedziczy.
type
TEngine = class
private
FFileName : String;
FFileLines : TStringList;
protected
procedure Execute(Path : String);
public
Pattern : TTemplate;
Replace : TTemplate;
procedure Parse;
constructor Create(FileName : String);
destructor Destroy; override;
end;
TEngine2 = class(TEngine)
{ dziedziczymy z klasy TEngine }
end;
Spójrz na powyższy fragment kodu. Po uruchomieniu programu klasa TEngine2
zawierać będzie wszelkie metody z klasy TEngine
; nie ma jedynie dostępu do metod i zmiennych z sekcji private
.
Dzięki temu wykorzystując pierwotną wersję „silnika”, czyli klasy TEngine
, możemy ją unowocześnić, dodając nowe elementy do klasy TEngine2
i jednocześnie nie tracąc starych. Na tym opiera się idea dziedziczenia.
Klasa domyślna
Zastanawiasz się, z jakiej klasy dziedziczymy, jeżeli podczas tworzenia deklaracji nie wpiszemy w nawiasie żadnej nazwy — W tym wypadku Delphi automatycznie za klasę bazową uzna TObject
. Klasa TObject
jest podstawową klasą dla całego VCL — zawiera metody sterujące alokacją pamięci dla klasy, zwalnianiem pamięci itp.
Typy metod
Domyślnie wszystkie metody deklarowane przez nas w ramach danej klasy to metody statyczne, nie opatrzone żadną klauzulą. Możliwe jest jednak tworzenie metod wirtualnych oraz dynamicznych. Wiąże się to z opatrzeniem danej metody klauzulą virtual
lub dynamic
.
TEngine2 = class(TEngine)
procedure A; // statyczna
procedure B; virtual; // wirtualna
procedure C; dynamic; // dynamiczna
end;
Wspominam o tym, gdyż z typami metod wiąże się jeszcze jedno pojęcie, a mianowicie przedefiniowanie metod — o tym będzie mowa w kolejnym punkcie.
Metody wirtualne kontra metody dynamiczne
W działaniu metody dynamiczne i wirtualne są praktycznie takie same. Jedyne, co ich różni, to sposób wykonywania. Otóż w metody wirtualne większa jest szybkość wykonania procedury, natomiast metody dynamiczne umożliwiają lepszą optymalizację kodu.
Konstruktory i destruktory
Już wcześniej w tym rozdziale zapoznałeś się z metodami Create
i Destroy
. Te dwie metody to w rzeczywistości konstruktor (Create
) oraz destruktor (Destroy
). We własnej klasie możesz dodać dwie metody, które będą wykonywane na samym starcie (podczas tworzenia klasy) oraz po zakończeniu korzystania z klasy. Są to specjalne typy metod — oznaczamy je słowami kluczowymi constructor
i destructor
.
constructor Create(FileName : String);
destructor Destroy; override;
Ponieważ w klasie TObject
istnieje już destruktor Destroy
, opatrzony klauzulą virtual
, w naszej klasie należy przedefiniować ten typ, dodając na końcu definicji słowo override
. W przeciwnym wypadku Delphi wyświetli ostrzeżenie: [Warning] Engine.pas(21): Method 'Destroy' hides virtual method of base type 'TObject'.
W konstruktorach przeważnie umieszcza się kod, który ma być wykonany przed rozpoczęciem rzeczywistego korzystania z klasy. Może to być np. tworzenie innych klas lub alokacja pamięci. Pamięć po skończeniu pracy z klasą należy zwolnić; jest to przeważnie dokonywane w destruktorze klasy. Przykład użycia możesz znaleźć w kolejnym podrozdziale, „Przykład użycia klas”.
Konstruktory i destruktory (a może być ich wiele) zawsze muszą znajdować się w sekcji public
klasy.
Przykład użycia klas
Jak dotąd, wiedza na temat klas była dość „sucha” i teoretyczna. Wiadome jest, że człowiek najlepiej uczy się poprzez praktykę — stąd ten punkt, w którym opisałem tworzenie przykładowej aplikacji opartej na klasach. Aplikacja będzie dość prosta w założeniu, lecz przy okazji jej tworzenia poznasz zastosowanie paru ciekawych funkcji w Delphi oraz utrwalisz swą wiedzę na temat klas.
Ogólne założenia
Zadanie polega na wygenerowaniu dokumentu HTML, który zawierał będzie informacje wpisane w programie. Wygląda to tak, że użytkownik wpisuje tytuł strony HTML, nagłówek oraz treść. Po naciśnięciu przycisku na dane z programu zostaną — podstawie odpowiedniego szablonu — „wtopione” w ten szablon.
Tworzenie modułu Engine
Z menu File wybierz polecenie New/Other. W oknie wskaż ikonę Unit — utworzony zostanie nowy moduł. Do prawidłowego działania konieczne będzie włączenie do listy uses
plików Windows.pas, SysUtils.pas i Classes.pas.
uses Windows, Classes, SysUtils;
Moduł Windows
jest podstawowym modułem dla wszystkich innych modułów i aplikacji — bez niego praktycznie nie może istnieć żaden program.
W module Classes
umiejscowione są klasy, z których często korzysta się podczas programowania przy użyciu VCL.
Moduł SysUtils
także jest często używanym modułem — zawiera wiele przydatnych procedur oraz funkcji konwertowania czy operacji na łańcuchach.
Szablon
Podstawą działania naszego przykładowego programu jest szablon. Zawiera on „szkielet” pliku HTML (patrz listing 3.4).
Listing 3.4. Szablon programu
<code class="html4strict"><html>
<head>
<meta http—equiv="Content-Type" content="text/html; charset=iso-8859-2">
<meta http—equiv="Content-Language" content="pl">
<title><!--PAGE_TITLE--></title>
</head>
<body>
<h1>Witaj <!--USER_NAME--></h1>
<hr>
<!--HELLO_INFO-->
</body>
</html>
Zwróć uwagę na komentarze HTML, umieszczone między znakami <!--
i -->
; komentarze te będą zastąpione odpowiednimi wartościami, przekazanymi klasie przez interfejs programu.
Wygląd klasy
Klasa, z której będziemy korzystać w naszej aplikacji, nie jest zbyt skomplikowana.
type
TTemplate = array of String;
TEngine = class
private
FFileName : String;
FFileLines : TStringList;
protected
procedure Execute(Path : String); virtual;
public
Pattern : TTemplate;
Replace : TTemplate;
procedure Parse;
constructor Create(FileName : String);
destructor Destroy; override;
end;
Nowy typ TTemplate
to tablica dynamiczna typu String
; myślę, że na ten temat wystarczająco wiele napisałem już w poprzednim rozdziale.
Sekcja private
posiada dwie zmienne — FFileName zawiera ścieżkę szablonu, z którego będziemy korzystali. FFileLines to wskazanie klasy typu TStringList
— służy ona do wykonywania operacji na tekście (ładowanie, zapis do pliku, tworzenie nowego wiersza), gdzie wszystko odbywa się w pamięci komputera.
W sekcji protected
znajduje się tylko jedna procedura Execute
, która służy do otwierania strony internetowej wskazanej w parametrze Path.
W sekcji public
bardzo ważną rolę odgrywają dwie zmienne, wskazujące na typ TTemplate
. Za pomocą procedury Parse dokonywana jest zamiana odpowiednich wartości w szablonie.
Kod źródłowy modułu
Konstruktor i destruktor
Konstruktor i destruktor naszej klasy służą do załadowania szablonu do pamięci, a wcześniej do utworzenia klasy TStringList
:
constructor TEngine.Create(FileName : String);
begin
FFileName := FileName; // przypisanie wartości parametru do zmiennej w sekcji private
FFileLines := TStringList.Create; // utworzenie typu TStringList
FFileLines.LoadFromFile(FileName); // załadowanie zawartości zmiennej z pliku
end;
destructor TEngine.Destroy;
begin
FFileLines.Free; // zwolnienie typu
{ zwolnienie tablic }
Pattern := nil;
Replace := nil;
DeleteFile('temporary.html'); // wykasowanie pliku tymczasowego
inherited; // wywołanie destruktora klasy bazowej
end;
Załadowanie szablonu do pamięci następuje za pomocą procedury LoadFromFile
z klasy TStringList
. Na tym polega właśnie potęga VCL — w jednym wierszu kodu realizowanych jest wiele na pozór skomplikowanych czynności.
W destruktorze należy zwolnić zmienną FFileLines oraz tablice typu TTemplate
. Oprócz tego wykonanie procedury DeleteFile
powoduje wykasowanie pliku tymczasowego — temporary.html, który użyty zostanie do wyświetlenia rezultatów w przeglądarce.
Dokonywanie zmian w szablonie
Kluczową rolę odgrywa procedura Parse
. Zmienne, przekazane do klasy w postaci tablicy typu TTemplate
, muszą zawierać pola do służące do zastąpienia wartości w szablonie. To w zasadzie główne zadanie realizowane jest przez funkcję Parse
:
procedure TEngine.Parse;
var
i : Integer;
begin
for I := Low(Pattern) to High(Pattern) do
{ zastąpienie określonych wartości w FFileLines }
FFileLines.Text := StringReplace(FFileLines.Text, Pattern[i], Replace[i], [rfReplaceAll]);
FFileLines.SaveToFile('temporary.html');
Execute('temporary.html');
end;
Właściwość Text z klasy TStringList
zwraca treść załadowanego pliku, w tym wypadku szablonu. Procedura ta realizuje polecenie StringReplace
, w wyniku którego zamieniane są odpowiednie wartości w zmiennej. Tak zmodyfikowany plik jest zapisywany ponownie na dysku pod nazwą temporary.html. W listingu 3.5. przedstawiony jest kod źródłowy modułu Engine
Listing 3.5. Kod źródłowy modułu Engine.pas
unit Engine;
interface
uses Windows, Classes, SysUtils;
type
TTemplate = array of String;
TEngine = class
private
FFileName : String;
FFileLines : TStringList;
protected
procedure Execute(Path : String); virtual;
public
Pattern : TTemplate;
Replace : TTemplate;
procedure Parse;
constructor Create(FileName : String);
destructor Destroy; override;
end;
implementation
{ TEngine }
uses ShellAPI; // włączenie modułu ShellAPI
constructor TEngine.Create(FileName : String);
begin
FFileName := FileName; // przypisanie wartości parametru do zmiennej w sekcji private
FFileLines := TStringList.Create; // utworzenie typu TStringList
FFileLines.LoadFromFile(FileName); // załadowanie zawartości zmiennej z pliku
end;
destructor TEngine.Destroy;
begin
FFileLines.Free; // zwolnienie typu
{ zwolnienie tablic }
Pattern := nil;
Replace := nil;
DeleteFile('temporary.html'); // wykasowanie pliku tymczasowego
inherited; // wywołanie destruktora klasy bazowej
end;
procedure TEngine.Execute(Path: String);
begin
// otwarcie pliku w przeglądarce Internetowej
ShellExecute(0, 'open', PChar(Path), nil, nil, SW_SHOW);
end;
procedure TEngine.Parse;
var
i : Integer;
begin
for I := Low(Pattern) to High(Pattern) do
{ zastąpienie określonych wartości w FFileLines }
FFileLines.Text := StringReplace(FFileLines.Text, Pattern[i], Replace[i], [rfReplaceAll]);
FFileLines.SaveToFile('temporary.html');
Execute('temporary.html');
end;
end.
Ciekawą funkcją przedstawioną w tym module jest ShellExecute
, która zawarta jest w module ShellApi.pas. Funkcja ta powoduje uruchomienie pliku określonego w trzecim parametrze. Trzecim parametrem mogą być dodatkowe parametry, z jakimi ma zostać uruchomiony program, a parametr czwarty określa ścieżkę do pliku. Ostatni parametr to tzw. flaga, określająca sposób, w jaki uruchomiony zostanie program — może to być SW_SHOW
(pokaż), SW_HIDE
(ukryj na starcie), SW_SHOWMAXIMIZED
(pokaż zmaksymalizowany) lub SW_SHOWMINIMIZED
(pokaż zminimalizowany).
Interfejs programu
Interfejsem nazywamy ogólny wygląd programu — kontrolki, które komunikują się z „wnętrzem” aplikacji, przekazując polecenia wydane przez użytkownika. Nasz przykładowy program nie będzie zawierał wielu „bajerów”. Wystarczy parę kontrolek, dzięki którym użytkownik będzie mógł określić tytuł strony, treść oraz nagłówek.
Moja propozycja interfejsu aplikacji została przedstawiona na rysunku 3.3.
Rysunek 3.3. Interfejs programu
W skład komponentów umieszczonych w formularzu wchodzi:
*komponent TGroupBox
, w którym znajdują się inne komponenty;
*trzy komponenty typu TLabel
(etykiety);
*dwa pola TEdit
— dwie kontrolki, przeznaczone do wpisania tytułu i nagłówka;
*TMemo
— kontrolka tekstowa, wielowierszowa;
*TButton
— przycisk realizujący całe zadanie.
Komponent TGroupBox
nie odgrywa żadnej znaczącej roli — służy jedynie do tworzenia ozdobnej ramki. Jest także tzw. rodzicem dla komponentów. Oznacza to, że umieszczone w nim komponenty są jakby grupowane w jedną całość. Spróbuj przesunąć komponent TGroupBox
— zauważysz, że wraz z przesunięciem obiektu przemieszczone zostaną także inne komponenty w nim umieszczone.
Kod źródłowy formularza głównego
Na samym początku do listy uses
formularza głównego należy włączyć nasz engine — moduł Engine.pas. Następnie cały proces związany z wykonaniem zadania powierzany jest klasie — my ją tylko inicjujemy. Kod źródłowy formularza głównego znajduje się w listingu 3.6.
Listing 3.6. Kod źródłowy formularza głównego
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TMainForm = class(TForm)
btnGenerate: TButton;
GroupBox1: TGroupBox;
lblTitle: TLabel;
edtTitle: TEdit;
lblHeader: TLabel;
edtHeader: TEdit;
Label1: TLabel;
memWelcome: TMemo;
procedure btnGenerateClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
uses Engine;
procedure TMainForm.btnGenerateClick(Sender: TObject);
var
Template : TEngine;
begin
{ wywołaj konstruktor klasy z parametrem — nazwa pliku szablonu }
Template := TEngine.Create('index.tpl');
{ określ rozmiary tablicy }
SetLength(Template.Pattern, 3);
SetLength(Template.Replace, 3);
{ przypisz do tablicy element do zastąpienia }
Template.Pattern[0] := '<!--PAGE_TITLE-->';
{ przypisz do tablicy element, który zastąpi komentarz }
Template.Replace[0] := edtTitle.Text;
Template.Pattern[1] := '<!--USER_NAME-->';
Template.Replace[1] := edtHeader.Text;
Template.Pattern[2] := '<!--HELLO_INFO-->';
Template.Replace[2] := memWelcome.Lines.Text;
Template.Parse;
Template.Free;
end;
end.
Uruchamianie programu
Spróbuj skompilować i uruchomić program. Jeżeli zawiera błędy, popraw je - zgodnie z listingiem, który znajdziesz na dołączonej do książki płycie CD-ROM.
Rysunek 3.4 przedstawia program w trakcie działania, a rysunek 3.5 rezultat wykonania zadania — stronę HTML, wygenerowaną przez program.
Rysunek 3.4. Program w trakcie działania
Rysunek 3.5. Strona wygenerowana przez aplikację
Parametr Sender procedury zdarzeniowej
Być może zaważyłeś, że podczas generowania nowego zdarzenia procedura zdarzeniowa zawsze posiada parametr Sender. Przykładowo po wygenerowaniu zdarzenia OnKeyPress formularza procedura zdarzeniowa wygląda następująco:
procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
end;
Zdarzenie OnKeyPress odpowiada za „przechwytywanie” informacji dotyczących klawisza naciśniętego podczas działania programu. Posiada ono dwa parametry — Sender i Key. Parametr Key zawiera informacje o klawiszu, który został naciśnięty podczas działania aplikacji.
Parametr Sender jest jakby „wskaźnikiem” — dzięki niemu możemy dowiedzieć się, z jakiego komponentu pochodzi zdarzenie, co jest ważne w przypadku, gdy jedna procedura obsługuje kilka zdarzeń jednego typu. Aby lepiej to zilustrować, napiszmy odpowiedni program.
Przechwytywanie informacji o naciśniętym klawiszu
Przy okazji tego ćwiczenia zaprezentuję, w jaki sposób można skorzystać z parametru Sender w przypadku, gdy jedna procedura zdarzeniowa używana jest przez kilka komponentów.
#W formularzu umieść trzy przykładowe komponenty — TMemo
(kontrolka edycyjna wieloliniowa), TEdit
(kontrolka jednoliniowa) i TCheckBox
(zaznaczenie opcji).
#Zmień właściwość Enabled komponentu TMemo
na False
. Spowoduje to, że komponent TMemo
podczas działania programu będzie nieaktywny, tj. nie będzie można wpisywać w nim żadnego tekstu.
Zaznacz następnie formularz, tak aby w Inspektorze obiektów pojawiły się właściwości i zdarzenia formularza. Wybierz zakładkę Events z Inspektora obiektów i odszukaj trzy zdarzenia — OnKeyDown, OnKeyPress i OnKeyUp. Wszystkie trzy są związane z przechwytywaniem procesu naciśnięcia klawisza.
Zdarzenie OnKeyDown występuje w momencie naciśnięcia klawisza przez użytkownika. Zdarzenie to będzie występowało, dopóki użytkownik nie puści tego klawisza. Umożliwia ono także przechwytywanie naciskania takich klawiszy, jak F1—F12, Home, End itd.
Zdarzenie OnKeyPress występuje w trakcie naciskania klawisza na klawiaturze — dostarcza użytkownikowi parametr Key typu Char
, czyli przekazuje naciśnięty znak. W przypadku, gdy użytkownik naciśnie taki klawisz jak Alt lub Ctrl, zdarzenie to nie występuje.
Zdarzenie OnKeyUp natomiast generowane jest w momencie puszczenia naciśniętego uprzednio klawisza klawiatury. Ponadto zdarzenia OnKeyDown i OnKeyUp dostarczają informację o tym, czy w danym momencie wciśnięty jest także klawisz Ctrl lub Alt czy może naciśnięty jest lewy przycisk myszki.
Wygeneruj teraz wszystkie trzy zdarzenia OnKeyDown, OnKeyPress oraz OnKeyUp:
procedure TMainForm.FormKeyPress(Sender: TObject; var Key: Char);
begin
memKeyInfo.Lines.Add('Naciśnięcie klawisza ' + Key);
end;
procedure TMainForm.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
memKeyInfo.Lines.Add('Wciśnięto klawisz #' + IntToStr(Key));
end;
procedure TMainForm.FormKeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
memKeyInfo.Lines.Add('Puszczono klawisz #' + IntToStr(Key));
end;
Podczas uruchomienia programu w komponencie TMemo
dodawane są nowe wiersze, zawierające informacje o naciśnięciu klawisza oraz jego kodzie ASCII. Dodanie nowej linii do komponentu realizowane jest za pomocą instrukcji:
memKeyInfo.Lines.Add('tekst do dodania');
W rzeczywistości właściwość Lines komponentu typu TMemo
wskazuje na typ TStringList
(korzystaliśmy z niego podczas omawiania klas) — widzisz więc, jak wszystkie klasy VCL są ze sobą połączone.
Obsługiwanie zdarzeń przez inne komponenty
W przypadku komponentów TEdit
oraz TCheckBox
nie będziemy pisali nowych procedur obsługi zdarzeń — skorzystamy z tych, które już mamy.
Zaznacz komponent TEdit
i przejdź do zakładki Events z Inspektora Obiektów; odszukaj zdarzenie OnKeyDown, zaznacz je, a następnie naciśnij klawisz strzałki, co spowoduje rozwinięcie listy zdarzeń tego typu, dostępnych w aplikacji (rysunek 3.6).
Rysunek 3.6. Lista zdarzeń możliwych do zastosowania
Wybierz z tej listy zdarzenie OnKeyDown — od tej pory wystąpienie tego zdarzenia w komponencie TEdit
będzie realizowane przez procedurę FormKeyDown
. Tak samo postąp ze zdarzeniami OnKeyPress i OnKeyUp, a także ze zdarzeniami komponentu TCheckBox
. W takim przypadku wszystkie zdarzenia z tych trzech komponentów obsługiwane będą przez jedną procedurę.
Obsługa parametru Sender
Możesz uruchomić program i sprawdzić jego działanie. Nieważne, czy aktywny jest komponent TEdit
, czy TCheckBox
— wszystkie naciśnięcia klawiszy są przechwytywane (rysunek 3.7).
Rysunek 3.7. Monitorowanie naciskania klawiszy
Dzięki parametrowi Sender, który obecny jest w każdej procedurze zdarzeniowej, możemy dowiedzieć się, z którego komponentu „zostało przesłane” zdarzenie.
Zmodyfikuj w kodzie procedurę FormKeyPress:
procedure TMainForm.FormKeyPress(Sender: TObject; var Key: Char);
begin
memKeyInfo.Lines.Add('Naciśnięcie klawisza ' + Key + ' z klasy ' + Sender.ClassName);
end;
Do treści komponentu TMemo
będzie także dołączona informacja, z której klasy pochodzi to zdarzenie. Realizuje to właściwość ClassName z klasy TObject
.
Ponownie uruchom aplikację i sprawdź teraz jej działanie; możesz aktywować komponenty TCheckBox
oraz TEdit
kliknięciem myszki i dopiero wówczas naciskać klawisze. Daje to efekt przedstawiony na rysunku 3.8.
Rysunek 3.8. Informacja o naciskanych klawiszach
W przypadku, gdy ilość wierszy znajdujących się w komponencie TMemo
jest na tyle duża, że nie mieszczą się one w oknie, nie mamy możliwości przewinięcia zawartości komponentu. Należy zmienić odpowiednie ustawienie we właściwości ScrollBars komponentu TMemo
. Z listy rozwijalnej możesz wybrać ssNone (brak pasków przewijania), ssHorizontal (poziomy pasek przewijania), ssVertical (pionowy pasek przewijania) lub ssBoth (obydwa paski przewijania).
Operatory is i as
Dwa operatory, is
i as
, są stosowane w połączeniu z klasami. Pewnie rzadko będziesz z nich korzystał, jednak warto poświęcić im nieco uwagi.
Pierwszy z nich — operator is
— służy do sprawdzania, czy np. aktywna kontrolka nie jest typu TEdit
. To jest tylko przykład, gdyż operator ten zazwyczaj realizuje porównanie typów klas — zobacz:
if Sender is TEdit then memKeyInfo.Lines.Add('Zdarzenie pochodzi z klasy TEdit');
W przypadku, gdy zdarzenie pochodzi z komponentu typu TEdit
, instrukcja if
zostaje spełniona. Operator is
działa podobnie jak porównanie za pomocą =
. W niektórych jednak przypadkach nie można użyć operatora =:
if Sender = TEdit then { kod }
Spowoduje to wyświetlenie komunikatu o błędzie: [Error] MainFrm.pas(34): Incompatible types, gdyż parametr Sender pochodzący z klasy TObject
oraz klasa TEdit
to dwie oddzielne klasy.
Operator as
natomiast służy do tzw. konwersji. Nie jest to jednak typowa konwersja, jaką omawiałem w poprzednim rozdziale.
Załóżmy, że masz kilka kontrolek typu TEdit
— zdarzenie OnKeyPress dla każdej z nich jest obsługiwane przez jedną procedurę zdarzeniową. Chciałbyś zmienić jakąś właściwość jednego komponentu typu TEdit
, a to jest możliwe dzięki operatorowi as
.
procedure TMainForm.FormKeyPress(Sender: TObject; var Key: Char);
begin
if Sender is TEdit then (Sender as TEdit).Text := '';
memKeyInfo.Lines.Add('Naciśnięcie klawisza ' + Key + ' z klasy ' + Sender.ClassName);
end;
Po uruchomieniu programu i naciśnięciu klawisza w momencie, gdy komponent TEdit
jest aktywny, wywołane zostanie zdarzenie OnKeyPress — wówczas właściwość Text (która określa tekst wpisany w kontrolce) zostanie wyczyszczona.
Formularz posiada właściwość ActiveControl, która „ustawia” wybraną kontrolkę aktywną zaraz po uruchomieniu programu.
Parametr Self
Słowo kluczowe Self
jest często nazywane wskaźnikiem klasy. W Delphi jest ono ukrywane, lecz stanowi wskazanie danej klasy — oto przykładowy kod:
procedure TForm1.FormCreate(Sender: TObject);
begin
Caption := 'Pole Caption';
end;
W takim wypadku Delphi „domyśla się”, że chodzi tutaj o odwołanie do właściwości Caption klasy TForm1
i kod jest wykonywany prawidłowo. Równie dobrze można by napisać:
Self.Caption := 'Pole Caption';
Z punktu widzenia kompilatora wygląda to tak, jak przedstawiono powyżej (wykorzystano wskaźnik Self
danej klasy); kod taki również zostanie skompilowany prawidłowo.
Kolejny przykład ilustruje dynamiczne tworzenie przycisku. Tworzenie jakiegokolwiek komponentu w sposób dynamiczny wygląda tak samo, jak tworzenie instancji zwykłej klasy. Jest jednak mała różnica — w konstruktorze musisz podać tzw. rodzica, czyli, krótko mówiąc, określić, w jakim komponencie ma zostać umieszczony komponent właśnie tworzony.
procedure TMainForm.btnCreateClick(Sender: TObject);
var
Button : TButton;
begin
Button := TButton.Create(Self);
Button.Parent := Self;
Button.Caption := 'Nowy...';
Button.Left := 150;
end;
W konstruktorze wpisałem słowo Self
— stanowi to informację dla kompilatora, że rodzicem komponentu ma być właśnie formularz, czyli TMainForm
.
Dociekliwy Czytelnik może zapytać, dlaczego po zakończeniu korzystania z klasy, jaką jest TButton
, nie zwolniłem pamięci. Delphi uczyni to za mnie automatycznie, ponieważ rodzicem dla komponentu jest formularz — po jego zamknięciu zostaną uprzednio zwolnione wszelkie obiekty w nim się znajdujące.
Łańcuchy tekstowe
Łańcuchami (ang. strings) nazywamy ciąg znaków o jakiejś długości. W Delphi, w przeciwieństwie do innych języków, istnieje wiele zmiennych określających łańcuch. Dotąd stosowałem jedynie zmienne typu String
oraz (czasami) PChar
. Np. w C++ nie istnieje pojęcie łańcuch — w tym języku łańcuchem jest w istocie tablica o określonej liczbie elementów.
ShortString
Typ ShortString
to podstawowy typ łańcuchowy Delphi 1 o długości ograniczonej do 255 znaków. Z tej przyczyny główną zaletą wykorzystania tego typu łańcucha jest szybkość. Zmienną używającą łańcuch ShortString
można zadeklarować na dwa sposoby:
var
S1 : ShortString; // długość - 255 znaków
S2 : String[255]; // długość - 255 znaków
Obie zmienne będą w tej sytuacji zmiennymi typu ShortString
. W przypadku zmiennej S2 możesz równie dobrze zadeklarować zmienną o mniejszej długości, wpisując odpowiednią wartość w nawiasie.
Długość łańcucha ShortString
umieszczona jest w pierwszym bajcie — łatwo więc można odczytać rzeczywistą długość tekstu:
var
S : ShortString;
Len : Integer;
begin
S := 'Hello World!';
Len := Ord(S[0]);
Zmienna Len zawierać będzie wartość 12.
Funkcja Ord
służy do zamiany (konwersji) znaku typu Char
do wartości liczbowej Integer
. Odwrotną funkcję (zamiana wartości Integer
do Char
) realizuje funkcja Chr
.
AnsiString
Typ AnsiString
pojawił się po raz pierwszy w Delphi 2 — nie ma w nim ograniczenia długości, przez co typ ten staje się bardzo uniwersalny. Domyślne ustawienia Delphi nakazują traktować typ String
tak samo jak typ AnsiString
.
Delphi automatycznie zarządza pamięcią dla zmiennych typu AnsiString
— Ty nie musisz się niczym przejmować. Wadą tego łańcucha jest odrobinę wolniejsze działanie niż w przypadku ShortString
, ale — ze względu na brak limitów długości łańcucha — jego użycie jest zalecane.
Odczyt długości łańcucha nie może tutaj odbyć się z użyciem znaków [], jak ma to miejsce w łańcuchu ShortString
; w tym wypadku można skorzystać z funkcji Length
.
var
S : AnsiString;
Len : Integer;
begin
S := 'Hello World!';
Len := Length(S);
WideString
Ten typ jest bardzo podobny do AnsiString
— także nie posiada limitowanej długości. Jest przeważnie używany przez funkcje API korzystające ze znaków Unicode.
Łańcuchy z zerowym ogranicznikiem
Pod tą nazwą kryją się w rzeczywistości zmienne typu PChar
lub tablice Char
. Nazwa pochodzi stąd, że łańcuch reprezentowany przez typ PChar
jest zakończony znakiem o kodzie 0. W języku C wszystkie łańcuchy to w rzeczywistości tablice Char
— np.:
var
S : array[0..255] of char;
begin
S := 'Hello World!';
Po deklaracji tablicy 255-elementowej typu Char
możemy przypisywać do niej dane jak do zwykłego łańcucha. Wartość zmiennej S jest zakończona znakiem #0 informującym o końcu łańcucha.
Typ PChar
jest w rzeczywistości typem wskaźnikowych (pointers), który wskazuje na tablicę znaków.
Prawdopodobnie nie będziesz często używał typu PChar
, jednak ze względu na to, że system Windows był pisany w języku C, w większości procedur Win API wymagane jest podanie jako parametrów zmiennych typu PChar
.
Operacje na łańcuchach
Delphi posiada bardzo wygodne funkcje umożliwiające operowanie na łańcuchach, czyli ich edycję, wycinanie części, znajdowanie fragmentów itp. Być może Delphi nie posiada aż tylu użytecznych funkcji co np. PHP, ale na nasze potrzeby na razie wystarczą.
Wszystkie funkcje prezentowane w tym punkcie są zawarte w module SysUtils.pas lub StrUtils.pas.
Łączenie łańcuchów
Łączenie dwóch typów łańcuchowych może odbywać się za pomocą operatora +, ale także dzięki funkcji Concat
.
procedure TForm1.Button1Click(Sender: TObject);
var
S1, S2 : AnsiString;
begin
S1 := 'Adam';
S2 := ' Boduch';
ShowMessage(
Concat(S1, S2)
);
end;
W rezultacie wykonania tej procedury w okienku wyświetlony zostanie napis Adam Boduch
. Tę samą funkcję pełni operator +, który jest ponadto szybszy. Dlatego też najprawdopodobniej nie będziesz miał okazji wiele razy stosować funkcji Concat
. Ja na przykład jeszcze nigdy z niej nie korzystałem.
Wycinanie łańcucha
Przez wycinanie łańcucha rozumiem kasowanie jego części. Uzyskiwanie jego fragmentów omówiłem w punkcie kolejnym.
Usunięcie części danych z łańcucha realizuje funkcja Delete. Należy w niej podać, od którego znaku ma się rozpocząć „wycinanie” i ile znaków ma zostać wyciętych.
procedure TForm1.Button1Click(Sender: TObject);
var
S1 : AnsiString;
begin
S1 := 'Borland Delphi 7 Studio';
Delete(S1, 1, 8);
ShowMessage(S1);
end;
W wyniku wykonania tej operacji w oknie wyświetlony zostanie jedynie napis Delphi 7 Studio
, a słowo Borland
zostanie wycięte.
Uzyskiwanie fragmentów łańcucha
W tym zakresie Delphi oferuje nam dość sporo przydatnych funkcji. Są to funkcje kopiujące określoną ilość bajtów z lewej lub z prawej strony ciągu, a także funkcja Copy
, która kopiuje z określonego miejsca podaną ilość znaków.
W Delphi 7 zostały wprowadzone nowe funkcje z modułu StrUtils
: LeftBStr
, RightBStr
oraz MidBStr
. Służą one do uzyskiwania części łańcucha z lewej lub prawej strony oraz z wybranego miejsca. Mają one poprawić obsługę na poziomie pojedynczych bajtów.
Nowa wersja Delphi została zaopatrzona także w przeciążone (overloaded) funkcje LeftStr
, RightStr
i MidStr
, które umożliwiają teraz działanie także na zmiennych WideString
.
Przykłady użycia:
uses StrUtils;
procedure TForm1.Button1Click(Sender: TObject);
var
S1 : AnsiString;
begin
S1 := 'Borland Delphi 7 Studio';
ShowMessage(
LeftBStr(S1, 8)
);
{ zwróci napis "Borland" }
end;
(**********************************************)
uses StrUtils;
procedure TForm1.Button1Click(Sender: TObject);
var
S1 : AnsiString;
begin
S1 := 'Borland Delphi 7 Studio';
ShowMessage(
RightBStr(S1, 6)
);
{ zwróci napis "Studio" }
end;
(************************************************)
uses StrUtils;
procedure TForm1.Button1Click(Sender: TObject);
var
S1 : AnsiString;
begin
S1 := 'Borland Delphi 7 Studio';
ShowMessage(
MidBStr(S1, 8, 7)
);
{ zwróci napis "Delphi" }
end;
W identyczny sposób jak funkcja MidStr
działa także funkcja Copy
. Funkcja ta jest funkcją wbudowaną, a zatem nie jest konieczne dołączanie jakiegokolwiek modułu do prawidłowego działania owej funkcji.
Wstawianie danych do łańcucha
Wstawianie nowych danych do już istniejącego łańcucha realizuje wbudowana funkcja Insert
. Pierwszym parametrem tej funkcji musi być tekst, który ma zostać wstawiony do łańcucha, a kolejny parametr to nazwa zmiennej, na której będziemy operować; parametr ostatni to pozycja, na której zostanie wstawiony tekst. Przykład:
procedure TForm1.Button1Click(Sender: TObject);
var
S1 : AnsiString;
begin
S1 := 'Borland Delphi 7 Studio';
Insert(' Enterprise', S1, Length(S1) + 1);
ShowMessage(S1);
end;
Po uruchomieniu programu w okienku pojawi się tekst Borland Delphi 7 Studio Enterprise
. W ostatnim parametrze procedury Insert
do długości łańcucha (którą to długość uzyskujemy za pomocą funkcji Length
) dodawana jest cyfra 1, aby zachować przerwę między wyrazami.
Wyszukiwanie danych w łańcuchu
Nowością w Delphi 7 jest funkcja PosEx
, dzięki której można jeszcze lepiej zrealizować operację wyszukiwania danych w łańcuchu. Funkcja ta znajduje się w module StrUtils
, a jej „starsza siostra” — funkcja Pos
— jest funkcją wbudowaną, także służącą do znajdowania danych w zmiennej typu String
.
Funkcja Pos
zwraca pozycję w zmiennej typu String
, gdzie znaleziony został szukany tekst; jeżeli tekst nie zostanie znaleziony, funkcja zwraca wartość 0.
procedure TForm1.Button1Click(Sender: TObject);
var
S1 : AnsiString;
begin
S1 := 'Borland Delphi 7 Studio';
if Pos('Studio', S1) > 0 then
ShowMessage('Znaleziono napis Studio!');
end;
Nowa funkcja PosEx
posiada dodatkowo parametr opcjonalny, który może oznaczać miejsce, od którego rozpocznie się wyszukiwanie.
Pozostałe funkcje
Pragnę przedstawić Ci parę funkcji, które być może przydadzą Ci się podczas programowania w Delphi. Zaznaczam, że są to tylko wybrane funkcje — więcej na ich temat możesz dowiedzieć się z pomocy Delphi.
AnsiMatchStr
Realizuje wyszukiwanie wartości określonej w pierwszym parametrze tablicy, która musi być przekazana w drugim parametrze tej funkcji. Nagłówek funkcji przedstawia się następująco:
function AnsiMatchStr(const AText: string; const AValues: array of string): Integer;
AnsiReverseString
Funkcja realizuje algorytm odwracania liter. Przykładowo jeżeli wywołasz tę polecenie z takim parametrem: AnsiReverseString('ABC');
, otrzymasz łańcuch CBA
.
DupeString
Funkcja dubluje przekazany jako pierwszy parametr tekst określoną w drugim parametrze ilość razy. Np.:
S := DupeString('Delphi', 2);
W wyniku takiej operacji otrzymamy wartość DelphiDelphi.
SearchBuf
Za pomocą tego polecenia możesz wyszukać tekst znajdujący się w buforze. Pojęcie bufor w tym wypadku oznacza wartość (tekst lub zmienna), która będzie przedmiotem wyszukiwania. Nagłówek tej funkcji:
function SearchBuf(Buf: PChar; BufLen: Integer; SelStart, SelLength: Integer; SearchString: String;
Options: TStringSearchOptions = [soDown]): PChar;
Pierwszym parametrem jest tekst, w którym odbędzie się szukanie; parametr kolejny to długość tekstu (możemy ją określić poprzez SizeOf
). Parametr trzeci to pozycja, od której rozpocznie się szukanie. Parametr SelLength
określa ilość znaków, które zostaną przeanalizowane od miejsca określanego jako SelStart. Kolejny parametr — SearchString — określa tekst do znalezienia, a ostatni opcje szukania (tabela 3.1).
Tabela 3.1. Możliwe wartości typu TStringSearchOptions
Wartość | Krótki opis |
---|---|
soDown | Szukanie odbędzie się w dół |
soMatchCase | Podczas szukania rozróżniane będą wielkie i małe litery |
soWholeWord | Pod uwagę będą brane nie tylko całe określenia, ale fragmenty. |
Przykładowo szukając słowa ABC, program weźmie pod uwagę także ABCDEEF
LowerCase
Funkcja powoduje zamianę wszystkich znaków określonych w pierwszym parametrze na małe litery.
S := LowerCase(S); // wyraz DELPHI zostanie zamieniony na delphi
Zalecane jest korzystanie z funkcji AnsiLowerCase
, która także zmienia znaki na małe litery, ale uwzględnia np. polskie znaki diakrytyczne.
UpperCase
W odróżnieniu od funkcji LowerCase
zamienia wszystkie znaki na duże litery:
S := UpperCase(S); // wyraz Delphi zostanie zamieniony na DELPHI
Zalecane jest korzystanie z funkcji AnsiUpperCase
, która także zmienia znaki na duże litery, ale uwzględnia np. polskie znaki diakrytyczne.
Trim
Funkcja Trim
obcina spacje z początku i końca łańcucha.
procedure TForm1.Button1Click(Sender: TObject);
var
S1 : AnsiString;
begin
S1 := 'Borland Delphi 7 Studio ';
ShowMessage(Trim(S1));
end;
Jak widać, do funkcji Trim
przekazany został napis zawierający na końcu wiele spacji — po konwersji spacje te zostaną obcięte.
Istnieją także funkcje TrimLeft
oraz TrimRight
, które obcinają spacje odpowiednio z lewej oraz prawej strony tekstu.
WrapText
Funkcja WrapText
przydaje się w przypadku, gdy mamy do czynienia z długim łańcuchem.
Powoduje ona zawinięcie wierszy poprzez wstawienie znaku nowego wiersza lub innego, przez nas określonego.
function WrapText(const Line, BreakStr: string; nBreakChars: TSysCharSet; MaxCol: Integer):string; overload;
Pierwszy parametr określa numer wiersza, a drugi — tekst, który wstawiony będzie pomiędzy „łamane” wiersze. Parametr nBreakChars jest zbiorem (o zbiorach mówiliśmy w poprzednim rozdziale) znaków, po których nastąpi łamanie wiersza. Ostatni parametr określa maksymalną długość wiersza.
Typ wariantowe
Typy wariantowe nie pojawiły się po raz pierwszy w Delphi, znane były już programistom języka Clipper. Obecnie w językach PHP czy Perl podczas przydzielania danych do zmiennych typ zmiennej jest ustalany przez kompilator. To samo jest możliwe w Delphi — po zadeklarowaniu jednej zmiennej typu Variant
można do niej przydzielać różne dane, takie jak łańcuchy, liczby itp.
procedure TForm1.Button1Click(Sender: TObject);
var
V : Variant;
begin
{ przydzielenie łańcucha }
V := 'Adam Boduch';
{ przydzielenie liczb }
V := 123;
{ przydzielenie liczb zmiennoprzecinkowych }
V := 1.23;
{ przydzielenie wartości typu Boolean }
V := TRUE;
end;
Taki kod zostanie bezproblemowo skompilowany przez Delphi. Do jednej zmiennej można przypisać wszystkie rodzaje danych, bez obsługiwania konwersji. Również taki kod zostanie skompilowany, a program prawidłowo wykonany:
procedure TForm1.Button1Click(Sender: TObject);
var
V : Variant;
begin
V := 123;
ShowMessage(V);
end;
Nie wykorzystano tu żadnych funkcji konwersji, a mimo to liczba została wyświetlona jak typ String
.
Nie mówiłem wcześniej o typie Boolean
. Zmienna korzystająca z tego typu może przybrać jedynie dwie wartości — True
(prawda) lub False
(fałsz). Często będziesz korzystał z tego typu dla określenia właściwości, która może być albo wykonana (True
), albo nie (False
).
Właściwości
Parę najbliższych stron poświęcę omówieniu podstawowych właściwości VCL, jakie napotkać możesz podczas pracy z Delphi. Nie będą to naturalnie wszystkie właściwości komponentów dostępne w Delphi — przedstawię tylko te podstawowe właściwości, dotyczące większości obiektów biblioteki VCL.
Align
Właściwość Align służy do określenia położenia komponentu w formularzu; właściwość ta dotyczy jedynie komponentów wizualnych. Wartość właściwości wybieramy z listy rozwijalnej Inspektora obiektów; może ona zawierać wartości takie, jak w tabeli 3.2.
Tabela 3.2. Możliwe wartości właściwości Align
Wartość | Opis |
---|---|
alBottom | Komponent położony będzie u dołu formularza, niezależnie od jego wielkości |
alClient | Obszar komponentu wypełni cały obszar formularza |
alCustom | Położenie jest określane względem komponentu (formularza) macierzystego |
alLeft | Obiekt położony będzie zawsze przy lewej krawędzi formularza lub komponentu macierzystego |
alNone | Położenie nieokreślone (swobodne) |
alRight | Obiekt położony będzie zawsze przy prawej krawędzi formularza lub komponentu macierzystego |
alTop | Komponent będzie położony u góry formularza |
Właściwość Align może określać położenie komponentu względem formularza lub względem innego komponentu macierzystego. Takim komponentem jest TPanel
, który jest rodzicem dla komponentu. Komponent TPanel
, tak jak i wszystkie komponenty na nim umieszczone, stanowią jedną całość.
Anchors
Właściwość Anchors można rozwinąć, klikając ikonkę znajdującą się obok nazwy tej właściwości (rysunek 3.9).
Rysunek 3.9. Rozwinięta właściwość Anchors
Właściwość ta określa położenie komponentu względem komponentu-rodzica. Np. w przypadku, gdy właściwość akLeft
gałęzi Anchors ma wartość True
, położenie komponentu po lewej stronie jest jakby -„blokowane”. Podczas uruchomienia programu i rozciągania formularza komponent na nim umieszczony będzie zawsze położony w tym samym miejscu.
Sprawdź to! Zmień wszystkie właściwości gałęzi Anchors na False
. Teraz uruchom program i spróbuj rozciągnąć lub zwężać formularz. Zauważysz, że komponent (np. TButton
) będzie dopasowywał swe położenie do rozmiarów formularza.
Constraints
Po rozwinięciu tej gałęzi pojawią się właściwości MaxHeight, MinHeight, MaxWidth i MinWidth. Określają one kolejno: maksymalną szerokość, minimalną szerokość, maksymalną wysokość oraz minimalną wysokość komponentu. Domyślnie wszystkie te właściwości posiadają wartość 0, co oznacza brak limitów. Jeżeli chcesz zablokować rozmiary komponentu, pamiętaj wówczas o gałęzi Constraints.
Cursor
Każdy komponent wizualny może posiadać osobny kursor. Oznacza to, że po naprowadzeniu kursora myszki nad dany obiekt kursor zostanie zmieniony według właściwości Cursor danego obiektu. Po rozwinięciu listy rozwijalnej obok nazwy każdego kursora pojawi się jego podgląd (rysunek 3.10).
Rysunek 3.10. Lista kursorów właściwości Cursor
DragCursor, DragKind, DragMode
Wszystkie te trzy właściwości związane są z techniką Drag and Drop (ang. przeciągnij i upuść). Delphi umożliwia konstruowanie aplikacji, która obsługuje przeciąganie komponentów i umieszczanie ich w innych miejscach formularza.
DragCursor określa kursor, który określał będzie stan przeciągania.
DragKind określa, czy dany obiekt będzie mógł zostać przeciągany po formularzu czy też będzie to miejsce tzw. dokowania, czyli miejsce, gdzie może być umieszczony inny obiekt.
DragMode określa, czy możliwe będzie przeciąganie danego komponentu. Ustawienie właściwości na dmManual
wyłącza tę opcję; z kolei ustawienie dmAutomatic włącza taką możliwość.
Font
Właściwość Font dotyczy tylko komponentów wizualnych i określa czcionkę przez nie używaną. Gałąź Font można rozwinąć, a następnie zdefiniować szczegółowe elementy, takie jak kolor, nazwa czcionki, wysokość czy styl (pogrubienie, kursywa, podkreślenie). Klasą TFont
i związaną z nią właściwością Font szczegółowo zajmiemy się w rozdziale na temat grafiki.
HelpContex, HelpKeyword, HelpType
Właściwości te związane są z plikiem pomocy. Większość starannie zaprojektowanych aplikacji w systemie Windows posiada plik pomocy — Delphi natomiast zawiera mechanizmy pozwalające na zintegrowanie pliku pomocy z aplikacją.
HelpContex określa numer ID strony pomocy, której dotyczyć będzie dana kontrolka.
HelpKeyword może zawierać słowo kluczowe określające daną kontrolkę. Łączy się to z ostatnią właściwością HelpType. Szukanie może się bowiem odbywać według ID (htContext) lub według słów kluczowych (htKeyword
).
Hint, ShowHint
Właściwości typu Hint są związane z tzw. „dymkami” podpowiedzi (ang. hint). Za ich pomocą możesz ustawić tekst podpowiedzi, który będzie wyświetlany po tym, jak użytkownik umieści kursor nad obiektem. Aby podpowiedź była wyświetlana, właściwość ShowHint musi być ustawiona na True
.
Z „dymkami” podpowiedzi wiąże się kilka dodatkowych właściwości klasy TApplication
. Klasy TApplication
nie trzeba tworzyć — jest to wykonywane automatycznie; wystarczy odwołać się do konkretnej pozycji:
Application.HintColor := clBlue;
Właściwość HintColor pozwala na określenie koloru tła podpowiedzi.
Kolejna właściwość — HintHidePause — określa czas w milisekundach (1 sek. = 1 000 milisekund), po którym podpowiedź zostanie ukryta.
HintPause określa czas, po którym podpowiedź zostanie wyświetlona. Domyślna wartość to 500 milisekund.
HintShortCuts to właściwość typu Boolean
. Po zmianie tej właściwości na True
wraz z podpowiedzią będzie wyświetlony skrót klawiaturowy wywołujący daną funkcję — np. „Wycina tekst do schowka (Ctrl+X)”.
Domyślna wartość kolejnej właściwości — HintShortPause — to 50 milisekund. Właściwość ta określa, po jakim czasie wyświetlona zostanie podpowiedź kolejnej kontrolki, jeżeli przemieścimy kursor znad jednego komponentu na drugi (np. wędrując po pozycjach menu lub przyciskach pasków narzędziowych).
Podpowiedź będzie wyświetlana tylko wówczas, gdy właściwość ShowHint danego obiektu będzie ustawiona na True
.
Visible
Właściwość Visible dotyczy jedynie komponentów wizualnych. Jeżeli jej wartość to True
(wartość domyślna), wówczas komponent będzie wyświetlany; jeżeli False
— komponent podczas działania programu będzie ukryty.
Tag
Często możesz napotkać się na właściwość Tag, gdyż jest ona obecna w każdym komponencie. Nie pełni ona żadnej funkcji — jest przeznaczona jedynie dla programisty do dodatkowego użycia. Możesz w niej przechowywać różne wartości liczbowe (właściwość Tag jest typu Integer
).
Zdarzenia
Parę najbliższych stron poświęcę omówieniu podstawowych zdarzeń VCL, jakie napotkać możesz podczas pracy z Delphi. Nie będą to naturalnie wszystkie zdarzenia komponentów dostępne w Delphi, gdyż to jest akurat specyficzną sprawą dla każdego komponentu.
OnClick
Zdarzenie OnClick występuje podczas kliknięcia klawiszem myszy w obszarze danej kontrolki — jest to chyba najczęściej używane zdarzenie VCL, dlatego nie będę go szerzej opisywał. Mam nadzieję, że podczas czytania tej książki zorientujesz się, do czego służy ta właściwość.
OnContextPopup
Delphi umożliwia tworzenie menu, w tym menu podręcznego (tzw. popup menu), rozwijalnego za pomocą kliknięcia prawym przyciskiem myszki. To zdarzenie jest generowane właśnie wówczas, gdy popup menu zostaje rozwinięte.
Wraz z tym zdarzeniem programiście dostarczana jest informacja dotycząca położenia kursora myszki (parametr MousePos) oraz tzw. uchwytu (o tym przy innej okazji).
Parametr MousePos jest typu TPoint
, a to nic innego jak zwykły rekord, zawierający dwie pozycje X i Y. A zatem jeżeli chcemy odczytać położenie kursora myszki w poziomie, wystarczy odczytać je poprzez MousePos.X
;
OnDblClick
Zdarzenie jest generowane podczas dwukrotnego kliknięcia danego obiektu. Obsługiwane jest tak samo jak zdarzenie OnClick
— wraz ze zdarzeniem nie są dostarczane żadne dodatkowe parametry.
OnActivate, OnDeactivate
Te dwa zdarzenia związane są jedynie z oknami (formularzami). Występują w momencie, gdy okno stanie się aktywne (OnActivate) lub zostanie dezaktywowane (OnDeactivate).
OnClose, OnCloseQuery
Te dwa zdarzenia związane są również z formularzem, a konkretnie z jego zamykaniem. Dzięki zdarzeniu OnClose możesz zareagować podczas próby zamknięcia okna. Wraz ze zdarzeniem dostarczany jest parametr Action, który określa „akcję” do wykonania. Możemy nadać temu parametrowi wartości przedstawione w tabeli 3.3.
Tabela 3.3. Właściwości klasy TCloseAction
Wartość | Opis |
---|---|
caNone | Nic się nie dzieje — można zamknąć okno |
caHide | Okno nie jest zamykane, a jedynie ukrywane |
caMinimize | Okno jest minimalizowane zamiast zamykania |
caFree | Okno zostaje zwolnione, co w efekcie powoduje zamknięcie |
Zdarzenia OnCloseQuery możesz użyć, aby zapytać użytkownika, czy rzeczywiście chce zamknąć okno. Zdarzenia posiada parametr CanClose; jeżeli nastąpi jego zmiana na False
, okno nie zostanie zamknięte.
OnPaint
Zdarzenie OnPaint występuje zawsze wtedy, gdy okno jest wyświetlane i umieszczane na pierwszym planie. W zdarzeniu tym będziesz umieszczał kod, którego zadaniem będzie „malowanie” w obszarze formularza.
OnResize
Zdarzenie OnResize występuje tylko wtedy, gdy użytkownik zmienia rozmiary formularza. Możesz dzięki temu zdarzeniu odpowiednio zareagować na zmiany lub nie dopuścić do nich.
OnShow, OnHide
Jak łatwo się domyśleć, te dwa zdarzenia informują o tym, czy aplikacja jest ukrywana czy pokazywana. Pokazanie lub ukrycie formularza dokonywane jest za pomocą metody Show
lub Hide
klasy TForm
.
OnMouseDown, OnMouseMove, OnMouseUp, OnMouseWheel, OnMouseWheelDown, OnMouseWheelUp
Wszystkie wymienione zdarzenia związane są z obsługą myszy — są to kolejno: kliknięcie w obszarze kontrolki, przesunięcie kursora nad kontrolką, puszczenie klawisza myszy, użycie rolki myszki, przesunięcie rolki w górę lub w dół.
Wraz z tymi zdarzeniami do aplikacji może być dostarczana informacja o położeniu kursora myszy oraz o przycisku myszy, który został naciśnięty (lewy, środkowy, prawy). Informacje te zawiera parametr Button klasy TMouseButton
(tabela 3.4).
Tabela 3.4. Możliwe wartości klasy TMouseButton
Wartość | Opis |
---|---|
mbLeft | Naciśnięto lewy przycisk myszki |
mbMiddle | Naciśnięto środkowy przycisk myszki |
mbRight | Naciśnięto prawy przycisk myszki. |
Wraz ze zdarzeniami obsługi myszy może być dostarczany również parametr Shift, który jest obecny także w zdarzeniach klawiaturowych (OnKeyUp, OnKeyDown). Wartości, jakie może posiadać parametr Shfit, przedstawione są w tabeli 3.5.
Tabela 3.5. Możliwe wartości klasy TShiftState
Wartość | Opis |
---|---|
ssShift | Klawisz Shift jest przytrzymany w momencie wystąpienia zdarzenia |
ssAlt | Klawisz Alt jest przytrzymany w momencie wystąpienia zdarzenia |
ssCtrl | Klawisz Ctrl jest przytrzymany w momencie wystąpienia zdarzenia |
ssLeft | Przytrzymany jest również lewy przycisk myszki |
ssRight | Przytrzymany jest także prawy przycisk myszki |
ssMiddle | Przytrzymany jest środkowy przycisk myszy |
ssDouble | Nastąpiło dwukrotne kliknięcie |
Zdarzenia związane z dokowaniem
Wspominałem już wcześniej o możliwości dokowania obiektów metodą przeciągnij i upuść. Związane jest z tym parę zdarzeń, które często możesz napotkać, przeglądając listę z zakładki Events z Inspektora Obiektów.
OnDockDrop
Zdarzenie OnDockDrop generowane jest w momencie, gdy użytkownik próbuje osadzić jakąś inną kontrolkę w obrębie naszego obiektu.
OnDockOver
Zdarzenie to występuje w momencie, gdy jakaś inna kontrolka jest przeciągana nad naszym obiektem.
OnStartDock
Zdarzenie występuje w momencie, gdy rozpoczynasz przeciąganie jakiegoś obiektu. Warunkiem wystąpienia tego zdarzenia jest ustawienie właściwości DragKind na wartość dkDock
.
OnStartDrag
Zdarzenie występuje tylko wówczas, gdy właściwość DragKind komponentu jest ustawiona na dkDrag
. Wykorzystaj to zdarzenie w momencie, kiedy chcesz zareagować na przeciąganie obiektu.
OnEndDrag, OnEndDock
Pierwsze ze zdarzeń wykorzystaj w przypadku, gdy chcesz zareagować na zakończenie procesu przeciągania; drugie natomiast występuje w przypadku zakończenia procesu „przeciągnij i upuść”.
OnDragDrop
Zdarzenie to generowane jest w momencie, gdy w danym komponencie następuje „spuszczenie” danych przeciąganych metodą drag nad drop.
OnDragOver
Zdarzenie to generowane jest w monecie, gdy nad danym komponentem użytkownik przeciąga kursor z przeciąganymi danymi.
Przykładowy program
Zainteresowanych metodą wymiany danych pomiędzy dwoma obiektami odsyłam do przykładowego programu znajdującego się na płycie CD-ROM, dołączonej do książki. Program umieszczony jest w katalogu ../listingi/3/Drag’n’Drop, a jego działanie prezentuje rysunek 3.11.
Rysunek 3.11. Działanie programu wykorzystującego metodę Drag and Drop
Program umożliwia wymianę danych metodą przeciągania pomiędzy komponentami TListBox
; możliwe jest także dowolne przemieszczanie komponentów — np. TButton
, TLabel
oraz umieszczanie ich w panelu (TPanel
).
Aby przemieszczanie danych pomiędzy komponentami TListBox
mogło dojść do skutku, właściwość DragMode musi być ustawiona na dmAutomatic
. Równie dobrze można wywołać procedurę DragBegin
komponentu TListBox
w celu uruchomienia procesu przeciągania.
Wyjątki
Żaden program nie jest pozbawiony błędów — jest to zupełnie naturalne, gdyż nawet największe firmy, zatrudniające wielu programistów, nie są w stanie zlikwidować w swoich produktach wszystkich niedociągnięć (dotyczy to zwłaszcza dużych projektów). Programując w Delphi, mamy możliwość — przynajmniej do pewnego stopnia — zapanowania nad tymi błędami. Błąd może bowiem wynikać z wykonania pewnej operacji, której my, projektanci, się nie spodziewaliśmy; może też wystąpić wówczas, gdy użytkownik wykona czynności nieprawidłowe dla programu — np. poda złą wartość itp. W takim wypadku program generuje tzw. wyjątki, czyli komunikaty o błędach. My możemy jedynie odpowiednio zareagować na zaistniały wyjątek, poprzez np. wyświetlenie stosownego komunikatu czy chociażby wykonanie pewnej czynności.
Słowa kluczowe try..except
Objęcie danego kodu „kontrolą” odbywa się poprzez umieszczenie go w bloku try..except
. Wygląda to tak:
try
{ instrukcje do wykonania }
except
{ instrukcje do wykonania w razie wystąpienia błędu }
end;
Jeżeli kod znajdujący się po słowie try spowoduje wystąpienie błędu, program automatycznie wykona instrukcje umieszczone po słowie except
.
Jeżeli uruchamiasz program bezpośrednio z Delphi (naciskając klawisz F9), wyjątki mogą nie zadziałać. Związane jest to z tym, że Delphi automatycznie kontroluje wykonywanie aplikacji i w razie błędu wyświetla stosowny komunikat (rysunek 3.12) oraz zatrzymuje pracę programu. Żeby temu zapobiec, musisz wyłączyć odpowiednią opcję. W tym celu przejdź do menu Tools/Debugger Options, kliknij zakładkę Language Exceptions i usuń zaznaczenie pozycji Stop on Delphi Exception.
Rysunek 3.12. Okno wyświetlane przez Delphi w przypadku wystąpienia błędu
Przykład: musisz pobrać od użytkownika pewne dane — np. liczbę. Dzięki wyjątkom możesz sprawdzić, czy podane w polu TEdit
wartości są wartościami liczbowymi.
procedure TMainForm.btnConvertClick(Sender: TObject);
begin
try
StrToInt(edtValue.Text); // próba konwersji tekstu na liczbę
Application.MessageBox('Konwersja powiodła się!', 'OK', MB_ICONINFORMATION);
except
Application.MessageBox('Błąd! Musisz wpisać liczbę!', 'Błąd', MB_ICONERROR)
end;
end;
Na samym początku w bloku try
następuje próba konwersji tekstu na liczbę (StrToInt
). Jeżeli wszystko odbędzie się „zgodnie z planem”, to okienko informacyjne zawierać będzie odpowiedni tekst. Jeżeli natomiast podana wartość nie będzie liczbą, wykonany zostanie wyjątek z bloku except
.
Funkcja MessageBox
z klasy TApplication
ma takie same działanie jak funkcja MessageBox
z modułu Windows.pas.
Słowa kluczowe try..finally
Kolejną instrukcją wyjątków są słowa kluczowe try
oraz finally
. W odróżnieniu od bloku except
kod znajdujący się po słowie finally
będzie wykonywany zawsze, niezależnie od tego, czy wystąpi wyjątek.
Konstrukcji tej używa się np. w wypadku, gdy konieczne jest zwolnienie pamięci, a nie jesteśmy pewni, czy podczas operacji nie wystąpi żaden błąd.
{ rezerwujemy pamięć }
try
{ operacje mogące stać się źródłem wyjątku }
finally
{ zwolnienie pamięci }
end;
Instrukcje try oraz finally
są często używane przez programistów podczas tworzenia nowych klas i zwalniania danych — oto przykład:
MojaKlasa := TMojaKlasa.Create;
try
{ jakieś operacje }
finally
MojaKlasa.Free;
end;
Dzięki temu niezależnie od tego, czy wystąpi wyjątek, czy też nie, pamięć zostanie zwolniona! Z taką konstrukcję możesz spotkać się bardzo często, przeglądając kody źródłowe innych programistów.
Możliwe jest również połączenie bloków try
oraz except
z blokiem try..finally
:
MojaKlasa := TMojaKlasa.Create;
try
try
{ operacje mogące stać się źródłem wyjątków }
except
{ komunikat informujący o wystąpieniu błędu }
end;
finally
MojaKlasa.Free; // zwolnienie pamięci
end;
Słowo kluczowe raise
Słowo kluczowe raise
służy do tworzenia klasy wyjątku. Brzmi to trochę skomplikowanie, ale w rzeczywistości takie nie jest. Spójrz na poniższy kod:
if Length(Edit1.Text) = 0 then
raise Exception.Create('Wpisz jakiś tekst w polu Edit!');
W przypadku, gdy użytkownik nie wpisze nic, w polu Edit
wygenerowany zostanie wyjątek. Wyjątki generowane są za pomocą klasy Exception
, ale o tym napiszę nieco później. Na razie powinieneś wiedzieć, że słowo raise
umożliwia generowanie wyjątków poza blokiem try..except
.
Pozostawienie słowa raise samego, jak w poniższym przypadku, spowoduje wyświetlenie domyślnego komunikatu o błędzie:
try
{ jakieś funkcje }
except
raise;
end;
Jeżeli w tym wypadku w bloku try
znajdą się instrukcje, które doprowadzą do wystąpienia błędu, to słowo kluczowe raise
spowoduje wyświetlenie domyślnego komunikatu o błędzie dla tego wyjątku.
Nie możesz jednak używać słowa raise poza blokiem try..except
— w takim wypadku zostanie wyświetlony komunikat o błędzie: [Error] Unit1.pas(29): Re-raising an exception only allowed in exception handler.
Klasa Exception
W module SysUtils
zadeklarowana jest klasa Exception
(wyjątkowo bez litery „T” na początku), która jest klasą bazową dla wszystkich wyjątków. W Delphi istnieje kilkadziesiąt klas wyjątków, a każda klasa odpowiada za inny wyjątek. Przykładowo błąd EConvertError
występuje podczas błędów konwersji, a EDivByZero
— podczas próby dzielenia liczb przez 0. Wszystko to związane jest z tzw. selektywną obsługą wyjątków, o czym będziemy mówili za chwilę.
W każdym razie możliwe jest zadeklarowanie w programie własnego typu wyjątku.
type
ELowError = class(Exception);
EMediumError = class(Exception);
EHighError = class(Exception);
Przyjęło się już, że nazwy wyjątków rozpoczynane są od litery E — Tobie także zalecam stosowanie takiego nazewnictwa. Od mementu zadeklarowania nowego typu możesz generować takie wyjątki:
raise EHighError.Create('Coś strasznego! Zakończ aplikację!');
Obiekt EHighError
jest zwykłą klasą, dziedziczoną z Exception
, więc należy także wywołać jej konstruktor. Tekst wpisany w apostrofy wyświetlony zostanie w okienku komunikatu o błędzie (rysunek 3.13).
Rysunek 3.13. Komunikat o błędzie wygenerowany za pomocą klasy EHighError
Selektywna obsługa wyjątków
Selektywna obsługa wyjątków polega na wykryciu rodzaju błędu i wyświetleniu stosownej informacji (lub wykonaniu jakiejś innej czynności).
try
{ instrukcje mogące spowodować błąd }
except
on ELowError do { jakiś komunikat }
on EHighError do { jakiś komunikat }
end;
Właśnie poznałeś zastosowanie kolejnego operatora języka Object Pascal — on. Jak widzisz, dzięki niemu możemy określić, jakiego typu jest wyjątek i odpowiednio nań zareagować. W module SysUtils
zadeklarowanych jest kilkadziesiąt klas wyjątków, jak np. EAccessViolation
(błędy związane z nieprawidłowym dostępem do pamięci), EInvalidCast
(związany z nieprawidłowym rzutowaniem) czy EInvalidPointer
(związany z nieprawidłowymi operacjami na wskaźnikach). Więcej możesz dowiedzieć się z systemu pomocy Delphi.
Zdarzenie OnException
Na próżno szukać zdarzenia OnException
na liście zakładki Events z Inspektora obiektów. Zdarzenie OnException
jest związane z całą aplikacją, a nie jedynie formularzem — stąd znajduje się w klasie TApplication
.
Dzięki temu zdarzeniu możemy przechwycić wszystkie komunikaty o błędach występujące w naszej aplikacji; jest to jednak odmienna forma zdarzenia, której nie generujemy z poziomu Inspektora obiektów. Musimy w programie napisać nową procedurę, która będzie obsługiwać zdarzenie OnException.
Deklaracja takiej procedury musi wyglądać tak:
procedure MyAppException(Sender: TObject; E : Exception);
Drugi parametr E zawiera wyjątek, który wystąpił w programie. Zapytasz, czemu właśnie taka deklaracja ? Podczas gdy generowałeś zdarzenia z poziomu Inspektora obiektów — np. OnMouseMove — zawierały one specyficzne parametry dotyczące określonej sytuacji (w przypadku OnMouseMove były to współrzędne myszki oraz parametr Shift). Delphi nie dopuści do uruchomienia programu w przypadku, gdy procedura zdarzeniowa OnException nie będzie zawierała parametru E.
Aby rzeczywiście móc przechwytywać wyjątki zaistniałe w programie, należy wykonać jeszcze jedną czynność:
procedure TMainForm.FormCreate(Sender: TObject);
begin
Application.OnException := MyAppException;
end;
Nakazujemy programowi, aby wszelkie wyjątki zaistniałe w programie były obsługiwane przez procedurę MyAppException
.
Obsługa wyjątków
Mamy już procedurę, która będzie obsługiwała zdarzenie OnException, ale to jeszcze nie koniec. Musimy jeszcze naszą procedurę MyAppException
jakoś oprogramować i nakazać jej wykonywanie jakichś czynności związanych z wyjątkami.
procedure TMainForm.MyAppException(Sender: TObject; E: Exception);
begin
{ wyświetlenie komunikatów wyjątków }
Application.ShowException(E);
if E is EHighError then // jeżeli wyjątek to EHighError...
begin
if Application.MessageBox('Dalsze działanie programu grozi zawieszeniem systemu. Czy chcesz kontynuować?',
'Błąd', MB_YESNO + MB_ICONWARNING) = Id_No then Application.Terminate;
end;
end;
Pierwszy wiersz procedury to wykonanie polecenia ShowException
z klasy Application
. Polecenie to powoduje wyświetlenie komunikatu związanego z danym wyjątkiem (rysunek 3.14.).
Kolejne instrukcje stanowią już tylko przykład, jak można zareagować w sytuacji wystąpienia jakiegoś konkretnego błędu (listing 3.7.)
Listing 3.7. Kod modułu MainForm
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, ComCtrls;
type
TMainForm = class(TForm)
rgExceptions: TRadioGroup;
btnGenerate: TButton;
StatusBar: TStatusBar;
procedure FormCreate(Sender: TObject);
procedure btnGenerateClick(Sender: TObject);
private
procedure MyAppException(Sender: TObject; E : Exception);
public
{ Public declarations }
end;
ELowError = class(Exception);
EMediumError = class(Exception);
EHighError = class(Exception);
var
MainForm: TMainForm;
implementation
{$R *.dfm}
{ TMainForm }
procedure TMainForm.MyAppException(Sender: TObject; E: Exception);
begin
{ wyświetlenie komunikatów wyjątków }
Application.ShowException(E);
if E is EHighError then // jeżeli wyjątek to EHighError...
begin
if Application.MessageBox('Dalsze działanie programu grozi zawieszeniem systemu. Czy chcesz kontynuować?',
'Błąd', MB_YESNO + MB_ICONWARNING) = Id_No then Application.Terminate;
end;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
{ przypisanie zdarzeniu OnException procedury MyAppException }
Application.OnException := MyAppException;
end;
procedure TMainForm.btnGenerateClick(Sender: TObject);
begin
{ odczytanie pozycji z komponentu TRadioGroup }
case rgExceptions.ItemIndex of
0: raise ELowError.Create('Niegroźny błąd!');
1: raise EMediumError.Create('Niebezpieczny błąd!');
2: raise EHighError.Create('Bardzo niebezpieczny błąd!');
end;
end;
end.
Zamiast standardowego wyświetlenia opisu błędu w komunikacie informacyjnym (co w listingu 3.7 jest efektem polecenia ShowException
) możliwe jest wyświetlenie komunikatu, np. w komponencie aplikacji. Wystarczy, że zmodyfikujesz listing 3.7 i w zdarzeniu MyAppException
napiszesz:
StatusBar.SimpleText := E.Message;
Rysunek 3.14. Program podczas działania
Klasa TApplication
Program wykorzystujący formularze posiada ukrytą zmienną Application, która wskazuje klasę TApplication
. Klasa ta odpowiada za działanie aplikacji, jej uruchamianie i zamykanie, obsługę wyjątków itp. Niestety właściwości oraz zdarzenia tej klasy nie są widoczne w Inspektorze obiektów, więc operacji na klasie TApplication
należy dokonywać bezpośrednio w kodzie programu.
Oto zawartość głównego pliku DPR zaraz po utworzeniu nowego projektu:
program Project1;
uses
Forms,
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Wszystkie metody wywoływane w bloku begin..end
znajdują się w klasie TApplication
— to może świadczyć o tym, jak ważna z punktu widzenia VCL jest ta klasa.
Pierwszy wiersz, czyli instrukcja Initialize
, powoduje zainicjowanie procesu działania aplikacji. Kolejna instrukcja — CreateForm
— powoduje utworzenie formularza, a ostatnia — Run
— uruchomienie aplikacji.
W dalszych punktach przedstawię najważniejsze właściwości, metody i zdarzenia klasy TApplication
. Nie chcę jednak przekraczać pewnych ram i mówić o rzeczach, o których dowiesz się w dalszej części książki — omówię zatem teraz tylko podstawowe właściwości, zdarzenia i metody.
Właściwości klasy TApplication
Właściwości klasy TApplication
są ściśle związane z działaniem aplikacji i obsługą niektórych jej aspektów. Oto najważniejsze z nich…
Active
Właściwość Active jest właściwością tylko do odczytu. Oznacza to, że nie można jej modyfikować, a jedynie odczytać jej wartość. Właściwość ta zwraca wartość True
, jeżeli aplikacja jest aplikacją pierwszoplanową.
ExeName
ExeName jest także właściwością tylko do odczytu. Określa ona ścieżkę do aplikacji wykonywalnej EXE.
Label1.Caption := Application.ExeName;
Powyższy kod spowoduje wyświetlenie w etykiecie ścieżki do programu.
Pełny kod źródłowy programu wyświetlającego ścieżkę do aplikacji znajduje się na płycie CD-ROM w katalogu ../listingi/3/ExeName.
ShowMainForm
Właściwość ShowMainForm domyślnie posiada wartość True
, co oznacza, że formularz główny zostanie wyświetlony. Nadając tej właściwości wartość False
, blokujemy wyświetlenie formularza głównego:
begin
Application.Initialize;
Application.ShowMainForm := False; // nie wyświetlaj!
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end
.
Title
Właściwość Title określa tekst, który jest wyświetlony na pasku stanu obok ikony w czasie, gdy aplikacja jest zminimalizowana.
Application.Title := 'Nazwa programu';
Metody klasy TApplication
Oto parę opisów wybranych metod z klasy TApplication
.
CreateForm
Metoda CreateForm
jest używana do tworzenia nowego formularza. Pełny przykład użycia tej procedury możesz znaleźć w kolejnym rozdziale.
Minimize
Wywołanie metody Minimize
spowoduje zminimalizowanie aplikacji do paska zadań. Wywołanie procedury jest proste:
Application.Minimize; // minimalizuj
Terminate
Wywołanie metody Terminate
spowoduje natychmiastowe zamknięcie aplikacji. Inną funkcją zamykającą jest Close
, ale zamyka ona jedynie formularz, a nie całą aplikację, dlatego zalecane jest używanie zamiast niej funkcji Terminate.
MessageBox
Metoda MessageBox
powoduje wyświetlenie okienka informacyjnego; jest zatem jakby rozbudowaną funkcją ShowMessage
, gdyż umożliwia ustalenie większej ilości parametrów.
procedure TForm1.FormCreate(Sender: TObject);
begin
if Application.MessageBox('Uruchomiony program?',
'Tak/Nie', MB_YESNO + MB_ICONINFORMATION) = id_No then Application.Terminate;
end;
Na podstawie powyższego kodu źródłowego na starcie programu zostanie wyświetlone okienko z pytaniem. Jeżeli użytkownik naciśnie przycisk Nie, program zostanie zamknięty.
ProcessMeessages
Pisząc programy w Delphi, pewnie nieraz skorzystasz jeszcze z funkcji ProcessMessages
. Owa metoda jest stosowana w trakcie wykonywania długich i czasochłonnych obliczeń (np. wykonanie dużej pętli), dzięki czemu nie powoduje zablokowania programu na czas wykonywania owych obliczeń.
Załóżmy, że w programie wykorzystujesz dużą pętlę for
, która wykona, powiedzmy, milion iteracji. Do czasu, aż pętla nie zakończy swego działania, nasz program będzie zablokowany. Oznacza to, że użytkownik nie będzie miał żadnych możliwości zamknięcia programu czy zmiany położenia jego okna do czasu zakończenia działania pętli. W takim wypadku należy zastosować funkcję ProcessMessages
:
for I := 0 to 1000000 do
begin
Application.ProcessMessages;
{ wykonywanie instrukcji }
end;
Powyższy kod sprawia, ze wykonywanie pętli nie spowoduje „zawieszenia” programu.
Moje wyjaśnienie dotyczące zasady działania metody ProcessMessages
nie było całkiem „profesjonalne”, gdyż wymaga zrozumienia mechanizmu zwanego komunikatami (będziemy o tym mówić w rozdziale 5.). Funkcja ProcessMessage
powoduje bowiem „przepuszczenie” wszystkich komunikatów z kolejki, a dopiero później zwrócenie sterowania do aplikacji — dzięki temu program nie sprawia wrażenia „zawieszonego”.
Jak pisałem, ten opis może nie mówi Ci zbyt wiele — najpierw przeczytaj rozdział 5., a dopiero potem powróć do tego opisu.
Restore
Wywołanie metody Restore
spowoduje powrót aplikacji do normalnego stanu (jeżeli jest np. zminimalizowana).
Application.Restore; // przywróć normalne okno
Zdarzenia klasy TApplication
Już raz podczas czytania niniejszej książki miałeś okazję zapoznać się z działaniem zdarzenia o nazwie OnException, z klasy TApplication
. Powinieneś więc wiedzieć, jak wygląda obsługa zdarzeń klasy TApplication
z poziomu Delphi. Tabela 3.6 przedstawia opis najważniejszych zdarzeń.
Tabela 3.6. Zdarzenia klasy TApplication
Zdarzenie | Krótki opis |
---|---|
OnActivate | Zdarzenie występuje w momencie, gdy aplikacja staje się aktywna |
OnDeactivate | Kiedy aplikacja przestaje być aktywna, generowane jest zdarzenie |
OnException | O tym zdarzeniu była już mowa we wcześniejszych fragmentach rozdziału. Powoduje ono przechwycenie wszystkich wyjątków zaistniałych w programie |
OnIdle | Występuje w momencie, gdy aplikacja przestaje być aktywna — nie wykonuje żadnych czynności |
OnMinimize | Zdarzenie jest generowane w momencie, gdy aplikacja jest minimalizowana |
OnRestore | Kiedy aplikacja jest przywracana do normalnego stanu metodą Restore, generowane jest to zdarzenie |
OnShortCut | W momencie naciśnięcia przez użytkownika skrótu klawiaturowego generowane jest zdarzenie OnShortCut (występuje przed zdarzeniem <va>OnKeyDown</var>) |
OnShowHint | W momencie pojawienia się „dymka podpowiedzi” generowane jest zdarzenie OnShowHint |
Podsumowanie
Niniejszy rozdział był poświęcony w całości bibliotece VCL. Starałem się umieścić w nim informacje ściśle związane z projektowaniem wizualnym — mam nadzieję, że wszystko jest na tym etapie zrozumiałe.
To, co mogło sprawić trudności, to zrozumienie idei klas, ale jeżeli tego wciąż nie rozumiesz, nie przejmuj się — to przyjdzie z czasem! Uwierz mi! Kiedyś powrócisz do tego rozdziału i stwierdzisz, że wszystko jest takie łatwe!.
Załączniki:
*Listingi_3.zip
Więcej informacji Delphi 2005. Kompendium programisty Adam Boduch Format: B5, stron: 1048 oprawa twarda Zawiera CD-ROM |
Wrzuci ktoś listing z tego rozdziału?
Jest tak, jak mówi Rysiu. Ja dodałem jeszcze po ShellExecute Sleep (2000) - dwie sekundy na otworzenie strony.
Dziwne, ale w dziale tworzenie modułu Engine destruktor kasuje plik temporary.html zanim przeglądarka zdąży go wyświetlić
wszystko świetnie, tylko zatrzymałem się nad punktem 6.5, a dokładniej w ShellExecute.
zamiast w przeglądarce internetowej, otwiera mi się w notatniku.
???
W tym rozdziale czesto uzywalem zamiennie okreslenia "metoda" z "procedura" (lub "funkcja") co mozne lekko mylic czytelnika. Jezeli ktos mialby czas i ochote na poprawki to oczywiscie zapraszam :)
Zabraklo tutaj tez szerszych objasnien pojecia "wlasciwosc" oraz "pole" (zamiast tego pisze o zmiennych w klasie).