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

3.1.jpg
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.

3.2.jpg
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.

3.3.jpg
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.

3.4.jpg
Rysunek 3.4. Program w trakcie działania

3.5.jpg
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).

3.6.jpg
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).

3.7.jpg
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.

3.8.jpg
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
soDownSzukanie odbędzie się w dół
soMatchCasePodczas szukania rozróżniane będą wielkie i małe litery
soWholeWordPod 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
alBottomKomponent położony będzie u dołu formularza, niezależnie od jego wielkości
alClientObszar komponentu wypełni cały obszar formularza
alCustomPołożenie jest określane względem komponentu (formularza) macierzystego
alLeftObiekt położony będzie zawsze przy lewej krawędzi formularza lub komponentu macierzystego
alNonePołożenie nieokreślone (swobodne)
alRightObiekt położony będzie zawsze przy prawej krawędzi formularza lub komponentu macierzystego
alTopKomponent 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).

3.9.jpg
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).

3.10.jpg
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
caNoneNic się nie dzieje — można zamknąć okno
caHideOkno nie jest zamykane, a jedynie ukrywane
caMinimizeOkno jest minimalizowane zamiast zamykania
caFreeOkno 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
mbLeftNaciśnięto lewy przycisk myszki
mbMiddleNaciśnięto środkowy przycisk myszki
mbRightNaciś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
ssShiftKlawisz Shift jest przytrzymany w momencie wystąpienia zdarzenia
ssAltKlawisz Alt jest przytrzymany w momencie wystąpienia zdarzenia
ssCtrlKlawisz Ctrl jest przytrzymany w momencie wystąpienia zdarzenia
ssLeftPrzytrzymany jest również lewy przycisk myszki
ssRightPrzytrzymany jest także prawy przycisk myszki
ssMiddlePrzytrzymany jest środkowy przycisk myszy
ssDoubleNastą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.

3.11.jpg
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.

3.12.jpg
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).

3.13.jpg
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;

3.14.jpg
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

ZdarzenieKrótki opis
OnActivateZdarzenie występuje w momencie, gdy aplikacja staje się aktywna
OnDeactivateKiedy aplikacja przestaje być aktywna, generowane jest zdarzenie
OnExceptionO tym zdarzeniu była już mowa we wcześniejszych fragmentach rozdziału. Powoduje ono przechwycenie wszystkich wyjątków zaistniałych w programie
OnIdleWystępuje w momencie, gdy aplikacja przestaje być aktywna — nie wykonuje żadnych czynności
OnMinimizeZdarzenie jest generowane w momencie, gdy aplikacja jest minimalizowana
OnRestoreKiedy aplikacja jest przywracana do normalnego stanu metodą Restore, generowane jest to zdarzenie
OnShortCutW momencie naciśnięcia przez użytkownika skrótu klawiaturowego generowane jest zdarzenie OnShortCut (występuje przed zdarzeniem <va>OnKeyDown</var>)
OnShowHintW 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

de25kp.jpg Więcej informacji

Delphi 2005. Kompendium programisty
Adam Boduch
Format: B5, stron: 1048
oprawa twarda
Zawiera CD-ROM
[[Delphi/Kompendium|Spis treści]]

[[Delphi/Kompendium/Prawa autorskie|©]] Helion 2003. Autor: Adam Boduch. Zabrania się rozpowszechniania tego tekstu bez zgody autora.

5 komentarzy

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