Rozdział 3 - VCL

Spis treści:

Rzut okiem na kod formularza
Klasy

Podstawowe informacje dotyczące klas
Dziedziczenie
Odwołanie do klasy
Dostęp do klasy
Przedefiniowanie metod
Konstruktory i destruktory

Biblioteka wizualna
Parametr Sender procedury
Operatory as i is
Korzystanie z plików pomocy
Sposoby na zmniejszenie pliku wykonywalnego
Podsumowanie


 

W poprzednim rozdziale uczyłeś się podstaw Object Pascal'a. Wtedy pisząc programy nie korzystaliśmy z formularzy. Ale przecież Delph to język obiektowy i grzechem by było nie używać formularzy, ani komponentów. 

VCL - skrót od Visual Component Library. Czyli inaczej mówiąc wizualna biblioteka komponentów. A co to jest komponent? To już wiesz...

 

Rzut okiem na kod formularza

Pamiętasz jeszcze jak należało się przełączać do edytora kodu? Tak - F12. No więc jesteś w edytorze kodu. Aha, czy powiedziałem, aby stworzyć nowy projekt? Dobrze, powinieneś zobaczyć taki kod:

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

end.

Pamiętaj - każdemu formularzowi towarzyszy jeden moduł! W poprzednim rozdziale mówiłem o budowie modułu, ale ten trochę się różni. Zwróć uwagę na kod po słowie type. To słowo, które jest przez Delphi wytłuszczone - słowo kluczowe class. To jest klasa.

 
Klasa - jest to obiekt, który zawiera metody ( procedury i funkcje ) ze sobą współpracujące w celu wykonania jakiejś czynności. 

 

O klasach będziemy mówić w dalszej części tego rozdziału. 

Bardzo ważną rzeczą w programowaniu jest odpowiednie formatowanie kodu. Uwierz mi, że po tym jak programista formatuje kod można odróżnić, czy jest to amator, czy profesjonalista. Zawsze początkujący ( tak było także ze mną ) nie zwracają uwagi na to w jaki sposób piszą komendy ( małe, duże litery, wcięcia itp. ) bo są za bardzo pochłonięci nauką samego programowania. Jednak prawidłowy, czytelny zapis kodu jest bardzo ważną rzeczą. Programiści firmy Borland opracowali regułę według jakiej należy zapisywać komendy w Delphi. Ja jak i większość programistów się do niej stosuje. Jeżeli nie chcesz być wyjątkiem staraj się pisać kod tak jak robię to ja w tej książce. O regułach kodowania możesz poczytać między innymi na stronie www.programowanie.of.pl 

Przełącz się z powrotem do formularza i umieść na nim komponent np. Button. ( jest to przycisk - ikona z przyciskiem ). Mam nadzieje, że pamiętasz jak to się robiło. Teraz znów przełącz się do edytora kodu. Zauważ, że Delphi dodało nową linijkę:

type
  TForm1 = class(TForm)
    Button1: TButton; { <-- nowa linia }
  private
    { Private declarations }
  public
    { Public declarations }
  end;

Jest to nowy element obiektu ( klasy ). W ten sposób zapisywany jest nowy komponent. Po lewej stronie jego nazwa, a po prawej po średniku typ komponentu. Kolejną regułą jest, ze nazwa każdego komponentu poprzedzana być powinna literą T. Tak więc jeżeli mamy komponent Label ( etykieta ) to Ty wiesz, że komponent jest typu TLabel. Komponent Memo - TMemo itd., itd.  

Napiszemy teraz jakiś prosty program z użyciem formularzy. Zaznacz komponent Button, który umieściłeś na formularzu. W Inspektorze Obiektów we właściwości Name wpisz "btnOK". We właściwości Caption wpisz "Kliknij mnie!". Teraz zaznacz formę ( po prostu raz kliknij na niej ). We właściwości Name formy wpisz "MainForm". We właściwości Caption wpisz: "Mój drugi program napisany w Delphi". Dobrze, wygląd masz już gotowy ( możesz zauważyć, ze wprowadzone przez Ciebie dane zostały również zmienione w edytorze kodu ). 

Napiszemy teraz jakiś kod. Podwójnie kliknij na przycisk, który umieściłeś na formularzu. O! Zostałeś znów przerzucony do edytora kody. Tym razem Delphi dodało do kodu w sekcji Implementation takie coś:

procedure TMainForm.bntOKClick(Sender: TObject);
begin

end;

No nie "coś", tylko procedurę. Pierwszy człon jej nazwy to nazwa klasy ( czyli formularza ). Później kropka, czyli odwołanie do elementu obiektu ( klasy ). To się nazywa zdarzeniem.

Zdarzenie - jest to procedura, która będzie wykonana po zajściu jakiegoś zdarzenia, czyli np. po kliknięciu na jakiś komponent, albo po dwukrotnym
kliknięciu lub np. najechaniu myszką w obręb komponentu.

 

Listę zdarzeń możesz zobaczyć zaznaczając wybrany komponent, a następnie klikając na zakładkę Events.

Zauważ, że pierwsze ze zdarzeń jest już zajęte przez procedurę, która została przed chwilą wygenerowana. Tak się składa, ze jeżeli klikniesz na jakiś komponent to Delphi "myśli", że chcesz wygenerować jego procedurę OnClick, czyli wpisać kod, który zostanie wygenerowany po kliknięciu na komponent. Jak uzupełniać te zdarzenia? Bardzo prosto. Przykładowo: chciałbyś obsłużyć zdarzenie wejścia kursora w obszar komponentu. Obsługuje to zdarzenie OnMouseMove. Kliknij raz na tym napisie. Po prawej stronie jest białe pole. Poprowadź kursor nad białe pole i jak zmieni się kursor to kliknij myszką dwa razy. Wtedy w edytorze kodu zostanie wygenerowana procedura. Może na początek doprowadź procedury do takiej postaci: 

procedure TMainForm.bntOKClick(Sender: TObject);
begin
 { obsługa kliknięcia na przycisk }
  ShowMessage('Witaj! Ten program jest pisany w Delphi');
end;

procedure TMainForm.bntOKMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  { obsłuż zdarzenie "najechania" kursorem na komponent }
  Caption := 'Kursor już wszedł w obszar komponentu Button';
end;

W pierwszej procedurze znane już nam polecenie ShowMessage. A co w drugiej? Normalnie z poziomu kodu programu możesz się odwołać do poszczególnych właściwości komponentów. Jeżeli chcesz zmienić napis na przycisku to piszesz:

btnOK.Caption := 'Nowy napis';

Jeżeli piszesz po prostu samo Caption to Delphi wie, że chcesz się odwołać do samego formularza i zmienić jego właściwość Caption. 

 

 

 

 

Klasy

Podczas omawiania tego działu napiszesz pierwszy poważny program. He, he, jak to brzmi - poważny. Jak każdy kto kupi sobie komputer zaczyna od gier, a dopiero później przechodzi wyżej i wyżej. Teraz doszedłeś do programowania i właśnie teraz napiszesz pierwszą grę - prosty manager. Zaprezentuje Ci w tym rozdziale właśnie tę grę, którą kiedyś napisałem. Myślę, że jest ona dobrym przykładem do zaprezentowania zasady działania klas. 

Podstawowe informacje dotyczące klas

Klasę deklaruje się w ten sposób:

type
  TMojaKlasa = class
  end;

Jak już pewnie zauważyłeś klasę deklaruje się za pomocą słowa class. Jeżeli chcesz do klasy dodać jakąś procedurę po postu ją wpisujesz po słowie class, a przed słowem end.  Jeżeli chcesz ją uzupełnić to wpisujesz ją w sekcji Implementation. Nie wystarczy jednak sama nazwa. Spójrz: 

{ deklarujesz klasę w sekcji Interface }
type
  TMojaKlasa = class
    procedure ProcKlasy;
  end;

Teraz jeżeli chcesz uzupełnić procedurę ProcKlasy to piszesz w sekcji Implementation: 

procedure TMojaKlasa.ProcKlasy;
begin

end;

Istnieje szybszy sposób. Użytkownicy nowszych wersji Delphi - 5.0 lub 4.0 mogą po prostu najechać kursorem na nazwę procedury i wcisnąć kombinację klawiszy lewy Ctrl + lewy Shift + C - procedura zostaje wygenerowana w sekcji Implementation. 

Mam nadzieje, że na razie wszystko jest jasne.

Dziedziczenie 

Klasy dodatkowo mogą dziedziczyć z innych klas. Dziedziczenie to bazowanie nowej klasy na starej. Przykład:

type
  TMojaKlasa = class
    procedure ProcKlasy;
  end;

  TDrugaKlasa = class(TMojaKlasa) { klasa dziedziczy z klasy TMojaKlasa }
    procedure Procc;
  end;

Jak widzisz druga klasa dziedziczy z pierwszej. Dziedziczenie odbywa się poprzez wpisanie pierwszej klasy po słowie class w nawiasie. 

Odwołanie do klasy

To nie jest takie proste. Na początek należy stworzyć zmienną, która będzie wskazywała na daną klasę  Następnie tę klasę należy stworzyć przydzielając pamięć dla jej wykonania. Na końcu tę pamięć należy zwolnić. 

var
  MojaKlasa : TMojaKlasa; // wskazanie na klasę
begin
  MojaKlasa := TMojaKlasa.Create;  { stworzenie klasy - przydział pamięci }
  MojaKlasa.ProcKlasy; // użycie procedury zawartej w klasie
  MojaKlasa.Free; // po użyciu klasy należy ją zwolnić

Na samym początku należy klasę stworzyć. Robi się to za pomocą tzw. konstruktora Create. Taki konstruktor jest automatycznie tworzony przy deklaracji klasy. Dobrze, później możesz używać procedur i funkcji zawartych w klasie. I na końcu bardzo ważna rzecz - obiekt należy zwolnić, zwalniając pamięć. Robi się to za pomocą polecenia Free. 

Dostęp do klasy

Delphi oferuje 4 poziomy dostępu do procedur i funkcji. Ty na razie poznasz trzy:

To jest trudne! Przyznaje. Nie martw się - czytaj dalej. 

Stwórz nowy moduł i nazwij go Samo. W module tym zadeklaruj nową klasę: ( żeby używać klas musisz do listy uses modułów dodać słowo Classes )

type
  TSamochod = class
  private
    FPredkosc : Integer;
  public
    function JakaPredkosc : Integer;
  end;

Teraz wygeneruj funkcję JakaPredkosc - oto kod:

function TSamochod.JakaPredkosc: Integer;
begin
  FPredkosc := 220; // przypisz wartość tej zmiennej
  Result := FPredkosc; // zwróć jako rezultat wartość
end;

Dobrze, teraz możesz już moduł zapisać. Teraz powróć do swojego głównego modułu programu. Pierwsze co będziesz musiał zrobić to dodać nazwę modułu Samo do listy uses. Wpisz więc na końcu słowo Samo. Ok, moduł już został do programu włączony. Teraz umieść na formie przycisk i wygeneruj jego procedurę OnClick:

procedure TMainForm.btnCreateClick(Sender: TObject);
var
  Samochod : TSamochod;  // wskazanie na nową klasę
begin
  Samochod := TSamochod.Create; // stwórz klasę
  ShowMessage(IntToStr( // wyświetl rezultat jej wykonania
    Samochod.JakaPredkosc));
  Samochod.Free; // zwolnij
end;

Wszystko powinno być dobrze - program powinien się skompilować i po naciśnięciu przycisku na ekranie pojawi się liczba 220. Jeżeli nie porównaj swój kod z tym dołączonym do książki. Przed poleceniem ShowMessage umieść jeszcze jedną linię:

  Samochod.FPredkosc := 110;

W tym poleceniu próbujesz zmienić wartość zmiennej FPredkosc klasy TSamochod. Nic z tego. Dlaczego? Bo FPredkosc zadeklarowana jest w sekcji private klasy i jest ukryta! Pytanie po co ukrywać metody? Po pierwsze dlatego, aby nie udostępniać procedur i funkcji "na zewnątrz". Przykładowo w klasie masz procedurę, która nie robi nic specjalnego - wykonywana jest tylko na potrzeby danej klasy i nie musisz jej udostępniać innym klasom. Bo po co? Druga sprawa: pamiętasz jak Delphi wyświetla elementy danego obiektu? W poprzednim rozdziale o tym mówiłem. Że jeżeli napiszesz nazwę jakiegoś obiektu i postawisz kropkę, a następnie odczekasz parę sekund to pojawi się okienko z listą metod, które można wykorzystać z daną klasą. Jeżeli deklarujesz procedurę w sekcji private to na tej liście procedura ukryta się pojawi. Jeżeli zadeklarujesz w sekcji public to się pojawi. A po co ma się pojawiać jeżeli nie jest do niczego potrzebna użytkownikowi danej klasy? Po co robić mętlik i nie potrzebne zamieszczanie? 

Pozostało omówienie jeszcze sekcji protected. Zmodyfikuj klasę TSamochód w module Samo tak, aby cały moduł wyglądał tak:

{
   Copyright © - Adam Boduch
}

unit Samo;

interface

uses
  Classes, Windows;

type
  TSamochod = class
  private
    FPredkosc : Integer;
  public
    function JakaPredkosc : Integer;
  protected
    procedure About;
  end;

implementation


procedure TSamochod.About;
begin
  MessageBox(0, 'To jest klasa TSamochód.', 'O klasie...', MB_OK);
end;

function TSamochod.JakaPredkosc: Integer;
begin
  FPredkosc := 220; // przypisz wartość tej zmiennej
  Result := FPredkosc; // zwróć jako rezultat wartość
end;

end.

Jak widzisz dodałem w sekcji protected nową procedurę. Wyświetla ona okienko z informacją o klasie - nic nadzwyczajnego. Normalnie procedura About jest ukryta tak jak jak by była w sekcji private. 

Dobrze, teraz w module głównym ( formularzu ) w sekcji Interface zadeklaruj klasę dziedziczącą z klasy TSamochod. Całość wygląda tak:

 

{
  Copyright © 2001 - Adam Boduch
}

unit MainFrm;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TMainForm = class(TForm)
    btnCreate: TButton;
    btnAbout: TButton;
    procedure btnCreateClick(Sender: TObject);
    procedure btnAboutClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.DFM}

uses Samo;

type
  TF1 = class(TSamochod) { dziedziczenie z klasy TSamochod }
  end;

procedure TMainForm.btnCreateClick(Sender: TObject);
var
  Samochod : TSamochod;  // wskazanie na nową klasę
begin
  Samochod := TSamochod.Create; // stwórz klasę
  ShowMessage(IntToStr( // wyświetl rezultat jej wykonania
    Samochod.JakaPredkosc));
  Samochod.Free; // zwolnij
end;

procedure TMainForm.btnAboutClick(Sender: TObject);
var
  F1 : TF1;
begin
  F1 := TF1.Create;
  F1.About; { <-- teraz procedura jest "widoczna" }
  F1.Free;
end;

end.

Zadeklarowałem klasę TF1, która dziedziczy z klasy TSamochod. Tak jak mówiłem wcześniej dla klasy TF1 dostępne są metody, które zostały zadeklarowane w sekcji protected klasy TSamochod! I tym samym procedura About także jest dostępna. 

Przedefiniowywanie metod

To też jest dosyć trudne. Zmodyfikuj klasę TF1 do takiej postacii:

type
  TF1 = class(TSamochod) { dziedziczenie z klasy TSamochod }
  private
    procedure About;
  end;

Jak widzisz dodaliśmy tutaj metodę About:

procedure TF1.About;
begin
  ShowMessage('Tak naprawdę to ja jestem procedura z innej klasy');
end;

W programie wywoływaliśmy procedurę About. Pytanie skąd program ma wiedzieć, czy to ma być procedura About klasy TF1, czy TSamochod? Nie wie. I wywołuje metodę z klasy TSamochod.  To jest właśnie przedefiniowanie metod. Na początek w klasie TSamochod musisz po nazwie procedury About dodać słowo virtual - spowoduje to, że metoda będzie wirtualna. 

    procedure About; virtual; // metoda wirtualna

Teraz w klasie TF1 należy przedefiniować metodę. Robi się to dodając na końcu słowo override. Dodaj więc to słowo na końcu deklaracji procedury About. Teraz jest ok. Możesz po prostu procedurę About klasy TSamochod rozszerzyć. Wszystko za sprawą słowa kluczowego inherited. Jeżeli je dodasz to w tym miejscu gdzie wstawione jest to słowo wykonywana będzie procedura z klasy bazowej:

procedure TF1.About;
begin
  inherited; // wykonanie procedury About z klasy TSamochod
  ShowMessage('Tak naprawdę to ja jestem procedura z innej klasy');
end;

Konstruktory i destruktory

Konstruktor to taka procedura, która jest wykonywana zaraz podczas stworzenia klasy i rezerwacji pamięci. Destruktor natomiast wykonywany jest na samym końcu. Konstruktory i destruktory MUSZĄ być deklarowane w sekcji public - oto jak ja to zrobiłem w swoim przykładzie:

    constructor Create(Predkosc: Integer);
  { Trzeba dodać override bo destruktor Destroy obecny jest także
    w klasie bazowej. }
    destructor Destroy; override;

Konstruktory i destruktory muszą być tak jak w powyższym przykładzie deklarowane za pomocą słów kluczowych - constructor oraz destructor.

Teraz kod:

constructor TSamochod.Create(Predkosc: Integer);
begin
  inherited;
{
  do zmiennej FPredkosc zostaje przypisana wartość podana przez użytkownika
  podczas stworzenia klasy.
}
  FPredkosc := Predkosc;
end;

destructor TSamochod.Destroy;
begin
 { nic nie zwalniamy - wystarczy wywołać destruktor domyślny }
  inherited;
end;

Nic nadzwyczajnego. Konstruktor jak i destruktor nie jest obowiązkową metodą, która musi znaleźć się w klasie - możesz je więc ominąć. Nic ważnego tutaj nie dodałem - wszystko co najważniejsze zostało opisane w komentarzu. Zauważ jednak, że destruktor opatrzyłem słowem kluczowym override. Dlatego, że w klasie bazowej istnieje taki sam destruktor ( o tej samej nazwie ). Pewnie niektórzy się zdziwią gdy mówię o klasie bazowej klasy TSamochod bo w kodzie po prostu klasy bazowej nie ma! To nie błąd. Jeżeli nie wyznaczysz dla danej klasy klasy bazowej to Delphi sam sobie obiera jako klasę bazową TObject. I właśnie w TObject jest destruktor taki sam jaki zadeklarowaliśmy w TSamochod. 

Zabierzmy się za pisanie naszej gry. Stwórz nowy moduł i nazwij go Samo.pas. Teraz w module utworzyłem taką oto klasę: 

 TSamochod = class  // nasza nowa klasa
  private
    MaxPredkosc : Integer; // max prędkość jaka może osiągnąć pojazd
    Rodzaj : TCars; // typ pojazdu ( zmienna wskazuje na nasz nowy typ )
    Predkosc : Integer; // aktualna prędkość poruszania się pojazdu
    Timer : TTimer;  // wskazanie na komponent "TTimer"
  protected
    procedure Jedz(Speed : Integer); // procedura, która powoduje poruszanie pojazdu
    procedure Stop; // zatrzymanie poruszania się pojazdu
    procedure OnTimer(Sender: TObject);
  public
    Paliwo : Integer; // ilość paliwa
    Interval : Integer; // częstotliwość występowania zdarzenia czasowego
    constructor Create(Model : TCars = caPorshe; Pal : Integer = 100);
    destructor Destroy; override;
    function JakaPredkosc : Integer; // podaje aktualna predkosc
    function Go(Pred: Integer; Polecenie : Integer; var Odp: String) : Boolean;
    function ZmienOpony : Integer; // funkcja, która zmienia opony
    function Zatankuj(IlePaliwa: Byte) : Integer; // funkcja zwiększająca ilość paliwa
  end;

Tylko się nie przestrasz... 

Zwróć uwagę na ostatnią linię w sekcji private. Jest tam zadeklarowana metoda Timer o typie TTimer. TTimer to komponent. W Delphi komponenty możesz tworzyć również dynamicznie, czyli podczas działania programu - poprzez napisanie odpowiedniego kodu. Komponent ten znajduje się na palecie System. Jest to  komponent niewidzialny. Podczas uruchamiania programu jest po prostu niewidoczny. Służy on do wykonywanie tej samej procedury co określony okres czasu. Czas w Delphi reprezentowany jest w milisekundach ( 1 sek = 1000 milisekund ). 

W sekcji protected masz procedurę OnTimer. Będzie to procedura obsługująca występowanie tego zdarzenia w komponencie Timer. Zawiera ona jeden parametr Sender, który jest typu TObject. Każda procedura, która jest zdarzeniem jakiegoś komponentu MUSI zawierać ten parametr. Po prostu mówi on kompilatorowi, że procedura będzie podpięta pod komponent. 

Najpierw przyjrzyj się całemu modułowi Samo.pas, a później go omówię:

(********************************************************)
(*                                                      *)
(*           Sample Unit Game for Delphi 5              *)
(*        Copyright (c) 2001 by Adam Boduch             *)
(*          HTTP://WWW.PROGRAMOWANIE.OF.PL              *)
(*              E - mail: boduch@poland.com             *)
(*      Build: 10.01.2001 r.                            *)
(*                                                      *)
(********************************************************)

unit Samo;

interface

uses
  SysUtils, Classes, ExtCtrls;

{ Oto pozycje w jakich moze sie znajdowac pojazd: }
const
  stStart = 0;
  stStop = 1;
  stPit = 2;

{
  Oto nowy typ - przedstawia on samochody do wykorzystania
  w aplikacji
}
type
  TCars = (caPorshe, caFerrari, caLamborgini);

  TSamochod = class  // nasza nowa klasa
  private
    MaxPredkosc : Integer; // max prędkość jaka może osiągnąć pojazd
    Rodzaj : TCars; // typ pojazdu ( zmienna wskazuje na nasz nowy typ )
    Predkosc : Integer; // aktualna prędkość poruszania się pojazdu
    Timer : TTimer;  // wskazanie na komponent "TTimer"
  protected
    procedure Jedz(Speed : Integer); // procedura, która powoduje poruszanie pojazdu
    procedure Stop; // zatrzymanie poruszania się pojazdu
    procedure OnTimer(Sender: TObject);
  public
    Paliwo : Integer; // ilość paliwa
    Interval : Integer; // częstotliwość występowania zdarzenia czasowego
    constructor Create(Model : TCars = caPorshe; Pal : Integer = 100);
    destructor Destroy; override;
    function JakaPredkosc : Integer; // podaje aktualna predkosc
    function Go(Pred: Integer; Polecenie : Integer; var Odp: String) : Boolean;
    function ZmienOpony : Integer; // funkcja, która zmienia opony
    function Zatankuj(IlePaliwa: Byte) : Integer; // funkcja zwiększająca ilość paliwa
  end;

implementation

{ TSamochod }

constructor TSamochod.Create(Model: TCars; Pal : Integer);
begin
  inherited Create;
    Rodzaj := Model;
    Paliwo := Pal;
{
  W zależności od wybranego modelu ustalana jest max. prędkość
  poruszania się tegoż pojazdu.
}
    case Model of
      caPorshe: MaxPredkosc := 280;
      caFerrari : MaxPredkosc := 310;
      caLamborgini : MaxPredkosc := 290;
    end;
  Interval := 0; // wyzeruj zmienna
{
  Otwórz obiekt typu "TTimer" i ustaw jego właściwości. Ustawiany jest
  czas występowania zdarzenia na podst. zmiennej "Interval", komponent
  jest aktywowany, a następnie zostaje przypisane zdarzenie typu "OnTimer"
}
  Timer := TTimer.Create(nil);
  Timer.Interval := Interval; // co ile milisekund będzie występowało zdarzenie?
  Timer.Enabled := True; // uaktywnij komponent
  Timer.OnTimer := OnTimer; // przypisz procedurę zdarzenia
end;

destructor TSamochod.Destroy;
begin
  Timer.Free; // przy zamykaniu zwolnij obiekt Timer
  inherited;
end;

function TSamochod.Go(Pred: Integer; Polecenie : Integer; var Odp: String): Boolean;
begin
{
  Procedura do wysyłania poleceń. To tutaj się ustawia, czy pojazd
  ma jechać, czy tez nie. Tutaj także sprawdzane jest, czy nie próbujemy
  przypisać prędkości do pojazdu, który nie jest w stanie jej rozwinąć.
}
  Result := True;
  Predkosc := Pred; // ustawianie prędkości
  case Polecenie of // w zależności od wybranej opcji:
    stStart : // powoduje start pojazdu
    begin
{ Sprawdzane jest tutaj, czy nie próbowaliśmy danemu pojazdowi przypisać
  prędkości jakiej nie zdołałby rozwinąć }
      if (Rodzaj = caPorshe) and (Predkosc > 280) then
      begin
        Odp := 'Nie mogę jechać tak szybko!'; // przypisanie odpowiedniego tekstu
        Result := False; // wynik operacji jako błędny
        Exit; // nie rób dalej nic
      end else Jedz(Predkosc); // w przeciwnym wypadku wykonaj procedurę
      if (Rodzaj = caFerrari) and (Predkosc > 310) then
      begin        { j/w }
        Odp := 'Nie mogę jechać tak szybko!';
        Result := False;
        Exit;
      end else Jedz(Predkosc);
      if (Rodzaj = caLamborgini) and (Predkosc > 290) then
      begin
        Odp := 'Nie moge jechać tak szybko!';
        Result := False;
        Exit;
      end else Jedz(Predkosc);
    end;
    stStop : // Jeżeli pojazd ma być zatrzymany...
    begin
      Stop; // wywołaj odpowiednia procedurę
      Result := True;  // wynik operacji jako pozytywny
      Odp := 'Tak jest! Zatrzymuje się!'; // wygeneruj tekst
      Exit; // dalej nie rób już nic
    end;
    stPit : // Jeżeli pojazd ma zjechać do PitStopu...
    begin
      Stop; // wywołaj odpowiednia procedurę
      Result := True; 
      Odp := 'Wjechałem właśnie do PitStopu-u!'; // wygeneruj tekst
      Exit;
    end;
  end;

{
  Jeżeli wszystko się powiedzie to przypisz zmiennej odpowiedni tekst.
}
  if Result then
    Odp := Format('Zrozumiałem. Jadę z prędkością %d km\h', [Predkosc]);
end;

function TSamochod.JakaPredkosc: Integer;
begin
{
  Funkcja zwraca rezultat w postaci prędkości z jaka jedzie pojazd.
  Dodatkowo w zależności od prędkości do zmiennej "Interva" przypisywane
  są odpowiednie wartości mające symbolizować przedział, w którym będzie
  generowane odpowiednie zdarzenie "OnTimer".
}
  Result := Predkosc;
  case Result of
    0..50: Interval := 10000;
    51..100: Interval := 6000;
    101..200: Interval := 2000;
    201..300: Interval := 1000;
    301..310: Interval := 600;
  end;
  Timer.Interval := Interval; // przypisz do komponentu
end;

procedure TSamochod.Jedz(Speed: Integer);
begin
// ustaw predkosc w zależności od podanego w zmiennej "Speed" parametru
  Predkosc := Speed;
end;

procedure TSamochod.OnTimer(Sender: TObject);
begin
// W wypadku wystąpienia zdarzenia zmniejszaj ilość paliwa
  Paliwo := Pred(Paliwo);
end;

procedure TSamochod.Stop;
begin
  Predkosc := 0;  // zatrzymaj pojazd
end;

function TSamochod.Zatankuj(IlePaliwa: Byte) : Integer;
begin
// zwiększ ilość paliwa w zależności od podanej wartości
  Paliwo := Paliwo + IlePaliwa;
  Result := Paliwo; // zwróć rezultat w postaci ilości paliwa znajdującego się w baku
end;

function TSamochod.ZmienOpony: Integer;
begin
// funkcja zmienia opony, czyli na nowo podaje jej stan na 100%
  Result := 100;
end;

end.

 

Tylko się nie załamuj... Nie martw się nic jeżeli nie wszystko rozumiesz. Jeżeli nie wyjaśnię Ci tego teraz to sam to zrozumiesz za parę tygodni. Spokojnie zacznijmy omawianie tego modułu. Program będzie wyglądał tak: Ty na samym początku wybierzesz sobie jakim chcesz jechać samochodem. Będziesz mu wydawał polecenia jak np. jedź, stój, wjedź do PitStop-u. Będziesz mógł go także zatankować lub zmienić opony. Właściwie większą cześć zadania wykonuje ten moduł. Jest to jakby "serce" programu. Na samym początku zadeklarowałem różne stałe:

{ Oto pozycje w jakich może się znajdować pojazd: }
const
  stStart = 0;
  stStop = 1;
  stPit = 2;

Będą to polecenia jakie będziesz mógł wydać swojemu kierowcy. Zrobiłem to tylko dla ułatwienia. Są to jakby aliasy. Łatwiej jest pisać ( i zapamiętać ) całe słowo - np. stStart niż cyfrę 0. Następnie zadeklarowałem nowy typ - takie będziesz miał samochody do wyboru. Na razie trzy. Później sama klasa. Zawiera ona wiele procedur i funkcji. Są to rozkazy, które możesz wydawać swojemu kierowcy. W sekcji private ( w public tez są dwie ) są zmienne, które przechowywać będą różne wartości jak aktualną prędkość pojazdu, ilość paliwa itp. 

Zacznijmy od konstruktora. Tworząc klasę będziesz musiał podać dwa parametry - model samochodu oraz ilość paliwa jaką będziesz chciał zabrać ze sobą. Na początku wartości z parametrów konstruktora są przypisane do zmiennych klasy:

  Rodzaj := Model;
  Paliwo := Pal;

Następnie w zależności od tego jaki model został wybrany do zmiennej MaxPredkosc zostaje przypisana odpowiednia wartość. Wiadomo, że Porshe nie pojedzie tak szybko jak Ferrari, prawda? Później trudniejsza część - tworzenie komponentu. Już wcześniej mówiłem o komponencie Timer. Oto jak go stworzyć z poziomu kodu programu:

  Timer := TTimer.Create(nil);
  Timer.Interval := Interval; // co ile milisekund będzie występowało zdarzenie?
  Timer.Enabled := True; // uaktywnij komponent
  Timer.OnTimer := OnTimer; // przypisz procedurę zdarzenia

Tworzenie komponentu Timer odbywa się prawie tak samo jak w przypadku klas. Komponenty także mają konstruktor. Różnica jest taka, że przy tworzeniu trzeba podać tzw. "rodzica". Przyjmij, że komponent to dziecko, a formularz do rodzic. Moduł nasz jednak nie posiada formularza więc wpisujesz tutaj słowo nil, czyli wskazanie puste.  Komponent Timer ma właściwość Interval, która określa "co ile" ma występować zdarzenie. Jak już mówiłem wartość ta musi być podana w milisekundach.  Na razie przypisujemy jej wartość zmiennej o tej samej nazwie. 
Co daje? Komponent zostaje uaktywniony ( Enabled := True ). Na końcu trzeba przypisać zdarzenie które będzie wykonywane co ileś tam sekund. Zdarzenie to opisze później. 
W destruktorze następuje zwolnienie komponentu Timer. Pamiętasz jak mówiłem o tym jak ważne jest zwalnianie obiektów? Po prostu trzeba to zrobić. Teraz już wiesz do czego służy konstruktor i destruktor?  

Jedziemy dalej - procedura OnTimer. Oto jej treść:

procedure TSamochod.OnTimer(Sender: TObject);
begin
// W wypadku wystąpienia zdarzenia zmniejszaj ilość paliwa
  Paliwo := Pred(Paliwo);
end;

Zmniejsza ona o jeden ilość paliwa. Wiadomo, ze prędkość z jaką jedziemy jest proporcjonalna do tego jak będzie nam ubywać paliwo. Tak więc częstotliwość wykonywania powyższej procedury będzie zależeć od prędkości z jaką jedzie pojazd:

function TSamochod.JakaPredkosc: Integer;
begin
{
  Funkcja zwraca rezultat w postaci prędkości z jaka jedzie pojazd.
  Dodatkowo w zależności od prędkości do zmiennej "Interval" przypisywane
  są odpowiednie wartości mające symbolizować przedział, w którym będzie
  generowane odpowiednie zdarzenie "OnTimer".
}
  Result := Predkosc;
  case Result of
    0..50: Interval := 10000;
    51..100: Interval := 6000;
    101..200: Interval := 2000;
    201..300: Interval := 1000;
    301..310: Interval := 600;
  end;
  Timer.Interval := Interval; // przypisz do komponentu
end;

Pierwsza komenda - do funkcji zostaje zwrócona wartość zmiennej Predkosc. Teraz w zależności od prędkości zostaje ustawiania wartość zmiennej Interval. Zobacz, jeżeli pojazd jedzie z prędkością ponad 300 km/h to wartość zmiennej Interval jest najmniejsza - 600 milisekund. A jeżeli wartość Interval jest mała to tym częściej występuje zdarzenie OnTimer, które zmniejsza ilość dostępnego paliwa.

W tym programie kluczową rolę odgrywa funkcja Go. Zwraca ona rezultat w postaci zmiennej Boolean. Ten typ zmiennej może zwrócić wartość True albo False. W naszym wypadku jeżeli polecenie się powiedzie to program je wykona, i zwróci wartość True ( prawda - funkcja wykonana ). Funkcja Go zawiera trzy parametry, dwa pierwsze to polecenie, które będziesz musiał wydać oraz prędkość z jaką Twój pojazd ma jechać. Ostatni parametr to zmienna typu String, która będzie przechowywać odpowiedź. Nie powiedziałem wcześniej, że z poziomu procedury ( w naszym wypadku funkcji Go ) możesz zmieniać wartość parametru danej funkcji ( w naszym wypadku parametru Odp funkcji Go ).  Dlatego, że deklarując ten parametr poprzedziłem go słowem kluczowym var. No, ale zajmijmy się samą funkcją. Następuje w niej sprawdzenie jaki polecenie zostało przypisane, czy Twój samochód może jechać z taką prędkością jaką zadeklarowałeś. Jeżeli nie to do parametru Odp zostaje przypisana odpowiedź ( w tym wypadku negatywna ). I dalej program nic już nie robi ( polecenie Exit ). Polecenie Exit powoduje zaniechanie dalszych działań ( coś podobnego jak polecenie Break, tyle, ze nie musi występować w pętli ). Jeżeli polecenie nie zostanie wykonane bo np. użytkownik podał nieprawidłowe wartości to funkcja zwróci rezultat False ( fałsz - źle wykonana ). W przeciwnym wypadku wykonana zostanie funkcja Jedz. 

Na samym końcu tej funkcji jest zastosowana funkcja jakiej do tej pory nie omawiałem. Jest to funkcja Format. Można powiedzieć, że ma ona działanie takie same jak funkcja printf w C. Umożliwia ona konwersje. W wybrane miejsce wstawia wartości zmiennych. Przykładowo:

var
  I : Integer;
begin
  I := 1;
  ShowMessage(
    Format('Zajełem %d pozycję', [i]));

W powyższym przypadku w miejsce znaku %d zostaje wstawiona wartość zmiennej I. Oprócz znaku %d możesz oczywiście wstawiać inne. Np. %s oznacza, ze w tym miejscu będzie wstawiona wartość zmiennej String. Jeżeli %c to zmienna Char jeżeli %f to zmienna typu zmiennoprzecinkowego. Tutaj na chwilę się zatrzymamy. Można bowiem określić ile cyfr po przecinku będzie wyświetlony:

var
  F : Double; // typ zmiennoprzecinkowy 
begin
  F := 12444.123333;
  ShowMessage(
    Format('%0.1f', [f]));

Jeżeli tak napiszesz to na ekranie wyświetli się liczba: 12444,1. Wszystko przez ten specyficzny zapis. Liczba przed literą f określa ile liczb po przecinku będzie wyświetlonych. 

 

Dobra. Moduł już mamy. Teraz trzeba napisać program, który z tego modułu korzysta. Teraz w sumie najprostsza część zadania, ale równie ważna, czyli układanie komponentów i nadanie programowi wyglądu ( interfejsu ). Oto jak ja to zrobiłem:

Strzałkami oraz napisami oznaczyłem jak nazywają się poszczególne komponenty. Możesz zrobić podobnie jeżeli chcesz.

Dwa komponenty na samym dole to TGauge. Są to wskaźniki postępu. Ten po prawej ma we właściwości Kind wybrane gkPie dzięki czemu tak wygląda.

Komponent ComboBox to lista rozwijalna. Tak jak w większości popularnych programów biurowych zawiera ona listę np. czcionek do wyboru.

ListBox to komponent podobny do ComboBox tyle, że nie jest to lista rozwijalna, ale można powiedzieć "rozwinięta".

RadioGroup - można o nim powiedzieć "komponent graficzny". Nie robi on nic specjalnego. Jest to taka ramka. Ustawiając właściwość Caption komponentu ustawiasz tekst, który pojawi się na górze komponentu. Posiada on właściwość Items. Jeżeli ją zaznaczysz to po prawej pojawi się mały kwadracik z trzykropkiem. Po naciśnięciu go pojawi się okno, w którym będziesz mógł wpisać pozycje, które pojawią się na tym komponencie. Wpisz więc jedna linia pod drugą: Start Stop PitStop. Naciskasz ok i tym sposobem na komponencie pojawiają się dodatkowe pozycje. Oczywiście może pozostać sama ramka bez żadnych pozycji.

No i ostatni komponent do omówienia - Edit. Jest to komponent edycyjny, w którym może znajdować się max. jedna linia tekstu. We właściwości Text możesz wpisać tekst, który będzie wyświetlany po uruchomieniu programu w tym właśnie komponencie. No i oprócz tego jest trochę etykiet tekstowych. 

Teraz zajmijmy się kodem źródłowym. Pierwsze co musisz zrobić to dodać nazwę modułu Samo do listy uses. Teraz w sekcji private dodaj linie: 

    Car : TSamochod; // zmienna wskazująca na nowa klasę

Utworzyliśmy w ten sposób zmienną wskazującą na naszą klasę, którą przed chwilą pisaliśmy. Zaznacz teraz komponent ComboBox i w Inspektorze Obiektów odnajdź właściwość Items. Określa ona jakie pozycje będą na liście tego komponentu. Kliknij na przycisk z trzykropkiem. Powinno otworzyć się okienko, w którym wpisz jeden pod drugim nazwę samochodów, które będą dostępne w grze. Wpisz więc: Porshe, Lamborgini, Ferrari. Kliknij teraz na zakładkę Events.  Wygeneruj procedurę OnChange ( o generowaniu pisałem wcześniej ). Wpisz w niej taki kod: 

procedure TMainForm.CarBoxChange(Sender: TObject);
begin
{
  W zależności od wybranej pozycji w komponencie CarBox
  tworzona jest klasa z odpowiednim parametrem.
}
  case CarBox.ItemIndex of
    0: Car := TSamochod.Create(caPorshe);
    1: Car := TSamochod.Create(caFerrari);
    2: Car := TSamochod.Create(caLamborgini);
  end;
end;

CarBox to nazwa komponentu ComboBox. Komponent ten ma właściwość ItemIndex, która oznacza, która linia została wybrana. Z tym, że pierwsza linia określana jest cyfrą 0. Tak więc w zależności od wybranej pozycji utworzona zostaje klasa. 

Teraz ustaw zdarzenie OnClick przycisku "Akceptuj". Zdarzenie to wywoływać będzie procedurę Go z modułu Samo.pas. Na początku pobierane będą wartości wpisane przez użytkownika - prędkość oraz wybrane polecenie:

procedure TMainForm.btnSendMessageClick(Sender: TObject);
var
  Odp : String;  // zmienna przechowywać będzie odpowiedź
begin
{
  Procedura ta przekazuje funkcjom zawartym w module "Samo"
  odpowiednie parametry, które ustalane są przez użytkownika.
}
  Car.Go(StrToInt(edtSpeed.Text), Polecenie.ItemIndex, Odp);
  Mess.Items.Add(Odp);


// uruchom Timer'y
  tmrOpony.Enabled := True;
  tmrFuel.Enabled := True;

{
  Na podst. aktualnej prędkości ustal częstotliwość występowania
  zdarzenia.
  Innymi słowy w zależności od prędkości ustalany jest postęp w
  zużyciu opon.
}
  case Car.JakaPredkosc of
    0..50 : tmrOpony.Interval := 10000;
    51..100: tmrOpony.Interval := 5000;
    101..150: tmrOpony.Interval := 2500;
    151..200: tmrOpony.Interval := 1300;
    201..250: tmrOpony.Interval := 700;
    251..310: tmrOpony.Interval := 500;
  end;

  tmrFuel.Interval := Car.Interval; // ustaw częstotliwość występowania zdarzania na podst. zmiennej z modułu "Samo"
end;

Zmienna Odp przechowywać będzie odpowiedź modułu Samo.pas, a konkretniej funkcji Go. Widzisz więc, że w tej procedurze, że najpierw wykonywana zostaje funkcja Go z modułu Samo. Pierwszym parametrem jest prędkość. Pobierana jest ona z właściwości Text komponentu edtSpeed ( tak nazwałem jeden komponent Edit ). Drugie parametr to polecenie. Tak jak w przypadku komponentu ComboBox, w RadioGroup też jest właściwość ItemIndex, która określa jaka pozycja jest zaznaczona ( 0 - pozycja pierwsza; 1 - pozycja druga; itd. ). Komponent ListBox nazwałem Mess. Takie oto polecenie:

Mess.Items.Add(Odp);

Powoduje dodanie do komponentu nowej linii. Innymi słowy dodana zostanie wartość zmiennej Odp. Jeżeli chcesz np. dodać nową linie to piszesz: Mess.Items.Add('Nowa linia'); Idziemy dalej: tmrOpony oraz tmrFuel to nazwy dwóch komponentów typ Timer. Będą one miały za zadanie zmniejszanie wartości komponentu Gauge. Jak już mówiłem Gauge to taki pasek postępu i będzie on w programie pokazywał ile jeszcze zostało paliwa, w jakim stanie jest ogumienie. Następnie zostaje pobrana wartość z funkcji JakaPredkosc z modułu Car. W zależności od prędkości ustawiana jest częstotliwość występowania zdarzenia OnTimer komponentu tmrOpony. Częstotliwość występowania zdarzenia OnTimer komponentu tmrFuel pobierana jest z modułu Samo ( w module tym napisaliśmy odpowiednią procedurę ). 

Teraz zdarzenia OnTimer dwóch komponentów typu Timer: 

procedure TMainForm.tmrOponyTimer(Sender: TObject);
begin
// Jeżeli prędkość wynosi 0 to przestań naliczać zużycie opon
  if Car.JakaPredkosc = 0 then Exit;
  
  gOpony.Progress := Pred(gOpony.Progress); // zmniejsz wskaźnik postępu
  case gOpony.Progress of // zmieniaj kolor komponentu w zależności od postępu
    0..19: gOpony.Color := clWhite;
    20..39: gOpony.Color := clAqua;
    40..69: gOpony.Color := clBlue;
    70..89: gOpony.Color := clNavy;
    90..100: gOpony.Color := clBlack;
  end;
{
  Jeżeli postęp zaznaczony na komponencie "Gauge" osiągnie niepokojący wynik,
  czyli będzie brakowało paliwa :) wyświetl odpowiedni komunikat w Mess
}
  if gOpony.Progress = 50 then
    Mess.Lines.Add('Mam kiepskie ogumienie. Należałoby zjechać do PitSop-u');
  if gOpony.Progress = 10 then
    Mess.Lines.Add('Jeżeli nie zmienie opon to się rozbije!');
  if gOpony.Progress = 0 then
    Mess.Lines.Add('AAAA!!! Robiłem sie!');
end;

Ta procedura zmniejsza jakość opon. Na początek następuje sprawdzenie, czy pojazd jest w ruchu - jeżeli nie to jakość opon się nie zmienia. W przeciwnym wypadku zmniejszaj tę ilość. Teraz komponent Gauge może przybierać różne kolory ( właściwość Color ). Im mniejsza będzie jakość opon tym kolor będzie bladł. No i na samym końcu tej procedury w zależności od tego jaki jest stan w komponencie ListBox wyświetlany będzie tekst informujący. Jeżeli jakość będzie na 50% ( właściwość Progress komponentu ) to zostanie dodany odpowiedni tekst w komponencie ListBox. 

I kolejny Timer:

procedure TMainForm.tmrFuelTimer(Sender: TObject);
begin
  if Car.JakaPredkosc = 0 then Exit; // Jeżeli prędkość wynosi 0 to nie rób nic

  gFuel.Progress := Car.Paliwo; // zaznacz na komponencie ilość paliwa
    if gFuel.Progress = 50 then
      Mess.Items.Add('Mam tylko połowe zbiornika. Należałoby zwiększyć tę ilość');
    if gFuel.Progress = 10 then
      Mess.Items.Add('Zaraz się zatrzymam! Muszę zjechać to PitStopu!');
    if gFuel.Progress = 0 then
      Mess.Items.Add('Koniec jazdy. Brak mi paliwa!');
end;

Tutaj także postęp komponentu Gauge wyświetlany jest na podstawie ilości paliwa, a ta pobierana jest z modułu Samo. Teraz tak samo jak poprzednio zostają wyświetlone ostrzeżenia jeżeli paliwa jest za mało. 

Myślę, że to co najważniejsze już opisałem. Oto cały kod programu:

(********************************************************)
(*                                                      *)
(*           Sample Game for Delphi 5                   *)
(*        Copyright (c) 2001 by Adam Boduch             *)
(*          HTTP://WWW.PROGRAMOWANIE.OF.PL              *)
(*              E - mail: boduch@poland.com             *)
(*      Build: 10.01.2001 r.                            *)
(*                                                      *)
(********************************************************)

unit MainFrm;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls, Gauges, Samo {<-- oto nasz modul! };

type
  TMainForm = class(TForm)
    CarBox: TComboBox;
    Polecenie: TRadioGroup;
    grpSetup: TGroupBox;
    Label1: TLabel;
    edtSpeed: TEdit;
    Label2: TLabel;
    Panel1: TPanel;
    gFuel: TGauge;
    gOpony: TGauge;
    Bevel1: TBevel;
    Label3: TLabel;
    Label4: TLabel;
    edtFuel: TEdit;
    btnSendMessage: TButton;
    Label5: TLabel;
    Label6: TLabel;
    Label7: TLabel;
    tmrOpony: TTimer;
    btnCh: TButton;
    tmrFuel: TTimer;
    btnFuel: TButton;
    Mess: TListBox;
    procedure CarBoxChange(Sender: TObject);
    procedure btnSendMessageClick(Sender: TObject);
    procedure tmrOponyTimer(Sender: TObject);
    procedure btnChClick(Sender: TObject);
    procedure tmrFuelTimer(Sender: TObject);
    procedure btnFuelClick(Sender: TObject);
  private
    Car : TSamochod; // zmienna wskazująca na nowa klasę
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.CarBoxChange(Sender: TObject);
begin
{
  W zależności od wybranej pozycji w komponencie CarBox
  tworzona jest klasa z odpowiednim parametrem.
}
  case CarBox.ItemIndex of
    0: Car := TSamochod.Create(caPorshe);
    1: Car := TSamochod.Create(caFerrari);
    2: Car := TSamochod.Create(caLamborgini);
  end;
end;

procedure TMainForm.btnSendMessageClick(Sender: TObject);
var
  Odp : String;  // zmienna przechowywać będzie odpowiedź
begin
{
  Procedura ta przekazuje funkcjom zawartym w module "Samo"
  odpowiednie parametry, które ustalane są przez użytkownika.
}
  Car.Go(StrToInt(edtSpeed.Text), Polecenie.ItemIndex, Odp);
  Mess.Items.Add(Odp);


// uruchom Timer'y
  tmrOpony.Enabled := True;
  tmrFuel.Enabled := True;

{
  Na podst. aktualnej prędkości ustal częstotliwość występowania
  zdarzenia.
  Innymi słowy w zależności od prędkości ustalany jest postęp w
  zużyciu opon.
}
  case Car.JakaPredkosc of
    0..50 : tmrOpony.Interval := 10000;
    51..100: tmrOpony.Interval := 5000;
    101..150: tmrOpony.Interval := 2500;
    151..200: tmrOpony.Interval := 1300;
    201..250: tmrOpony.Interval := 700;
    251..310: tmrOpony.Interval := 500;
  end;

  tmrFuel.Interval := Car.Interval; // ustaw częstotliwość występowania zdarzania na podst. zmiennej z modułu "Samo"
end;

procedure TMainForm.tmrOponyTimer(Sender: TObject);
begin
// Jezeli predkosc wynosi 0 to przestań naliczać zużycie opon
  if Car.JakaPredkosc = 0 then Exit;
  
  gOpony.Progress := Pred(gOpony.Progress); // zmniejsz wskaźnik postępu
  case gOpony.Progress of // zmieniaj kolor komponentu w zależności od postępu
    0..19: gOpony.Color := clWhite;
    20..39: gOpony.Color := clAqua;
    40..69: gOpony.Color := clBlue;
    70..89: gOpony.Color := clNavy;
    90..100: gOpony.Color := clBlack;
  end;
{
  Jeżeli postęp zaznaczony na komponencie "Gauge" osiągnie niepokojący wynik,
  czyli będzie brakowało paliwa :) wyświetl odpowiedni komunikat w Mess
}
  if gOpony.Progress = 50 then
    Mess.Items.Add('Mam kiepskie ogumienie. Należałoby zjechać do PitSop-u');
  if gOpony.Progress = 10 then
    Mess.Items.Add('Jeżeli nie zmienie opon to się rozbije!');
  if gOpony.Progress = 0 then
    Mess.Items.Add('AAAA!!! Robiłem sie!');
end;

procedure TMainForm.btnChClick(Sender: TObject);
begin
{
  Wywołaj funkcje "ZmienOpony" z modułu "Samo" jeżeli naciśnięty
  zostanie przycisk. Jeżeli nie jesteś w pitstopie to generowany
  zostaje komunikat w Mess
}
  if Polecenie.ItemIndex = 2 then
    gOpony.Progress := Car.ZmienOpony else
  Mess.Items.Add('Muszę wjechać do PitStopu');
end;

procedure TMainForm.tmrFuelTimer(Sender: TObject);
begin
  if Car.JakaPredkosc = 0 then Exit; // Jeżeli prędkość wynosi 0 to nie rób nic

  gFuel.Progress := Car.Paliwo; // zaznacz na komponencie ilość paliwa
    if gFuel.Progress = 50 then
      Mess.Items.Add('Mam tylko połowe zbiornika. Należałoby zwiększyć tę ilość');
    if gFuel.Progress = 10 then
      Mess.Items.Add('Zaraz się zatrzymam! Muszę zjechać to PitStopu!');
    if gFuel.Progress = 0 then
      Mess.Items.Add('Koniec jazdy. Brak mi paliwa!');
end;

procedure TMainForm.btnFuelClick(Sender: TObject);
begin
{
  Ta procedura natomiast uzupełnia zapas paliwa. Poniższe
  warunki sprawdzają, czy nie próbujesz dolać więcej paliwa niż
  możliwe to jest do pomieszczenia w zbiorniku.
  Jeżeli ilość wpisana w "edtFuel" będzie przekraczać 'niedobór'
  w Gauge to odpowiednio zmniejsz ta ilość
}
  if Polecenie.ItemIndex = 2 then begin
    if StrToInt(edtFuel.Text) > (100 - gFuel.Progress) then
      edtFuel.Text := IntToStr(100 - gFuel.Progress);
    gFuel.Progress := Car.Zatankuj(StrToInt(edtFuel.Text)); // zatankuj tyle ile jest wpisane w kontrolce "edtFuel"
  end else
  Mess.Items.Add('Muszę najpierw wjechać do PitStopu'); // Jeżeli nie jesteś w PitStopie wyświetl komunikat
end;

end.

Ufff. To nie był najłatwiejszy temat - przyznaje. Mam nadzieje, że dalsze będą mniej bolesne. 

Biblioteka wizualna

Delphi nie jest jedynym językiem, w którym zastosowano wizualne projektowanie. Takie cuda umożliwia także Visual C++ oraz Visual Basic. Moim jednak zdaniem najlepiej to wypadło w Delphi, a najgorzej w Visual C++. Jest jedna tego wada - duże rozmiary programów. "Zwykły" program bez żadnych komponentów i dodatkowego kodu po skompilowaniu zajmuje u mnie ( Delphi 5 ) 289 kB! Im starsza wersja Delphi tym mniejszy jest ten rozmiar, ale i tak jest to dużo ( np. w porównaniu z Visual C++ ). Można oczywiście pisać bez wykorzystania komponentów i wtedy można osiągnąć bardzo małe rozmiary programu ( mnie udało się napisać program, który miał 6 kB! ),  ale jest to nieporównywalnie trudniejsze. A Delphi wymyślono w końcu, aby pisać programy z wykorzystaniem VCL'a. 

Twórcy Delphi dołączyli kody źródłowe wszystkich modułów, które możesz wykorzystać w Delphi oraz źródła kilku komponentów. Możesz im się przyjrzeć bliżej. Są one zawarte w katalogu ..\Source\Vcl. Inne moduły także w katalogu ..\Source\Rtl\Win. Najważniejszym modułem w Delphi jest moduł Windows.pas. Windows był pisany w C i większość procedur wykorzystywanych przez Windows zawartych jest w bibliotekach DLL. Moduł Windows można powiedzieć "importuje" te procedury i funkcje z DLL'i i "tłumaczy" z języka C na Pascal. Pisząc z wykorzystaniem modułu Windows używasz funkcji, które są importowanie z bibliotek DLL Windowsa. 

Parametr Sender procedury

Pamiętasz jak przy okazji omawiania klas mówiłem o parametrze Sender procedury? Teraz zajmiemy się tym dokładniej. Każda procedura ( zdarzenie ) musi posiadać przynajmniej ten parametr. Jest to jakby wskaźnik na komponent, który korzysta z danego zdarzenia. Parametr ten jest rzadko wykorzystywany, ale warto o tym wiedzieć. Napiszmy jakiś prosty programik. Stwórz nowy projekt w Delphi. Umieść na formularzu dwa komponenty typu Button. Nazwij je odpowiednio btnFirst, btnSecond. Dobrze, teraz przełącz się do formularza, czeka Cię modyfikacja klasy. Do klasy dodasz nową sekcję, o której wcześniej nie mówiłem. Jest to sekcja published. Będzie o niej mowa przy okazji tworzenia komponentów. W tej sekcji umieszcza się metody, które będą widoczne w Inspektorze Obiektów. Doprowadź klasę do takiej postaci:

type
  TMainForm = class(TForm)
  private
    { Private declarations }
  public
    { Public declarations }
  published
    procedure Click(Sender: TObject);
  end;

Teraz uzupełnij procedurę Click w sekcji Implementation:

procedure TMainForm.Click(Sender: TObject);
begin
  if Sender = btnFirst then
    ShowMessage('Nacisnołeś przycisk "btnFirst"');
  if Sender = btnSecond then
    ShowMessage('Nacisnołeś przycisk "btnSecond"');
end;

Tę procedurę podepniemy pod dwa przyciski. Znaczy to, że użytkownik naciskając obojętnie, czy pierwszy, czy drugi przycisk wywoła tę samą procedurę. Tutaj następuje sprawdzenie z jakiego komponentu pochodzi zdarzenie. Do tego właśnie służy parametr Sender. W zależności od tego jaki przycisk został naciśniety ( od jakiego komponentu pochodzi zdarzenie ) zostaje wyświetlony komunikat. 

Dobrze, teraz przełącz się do Inspektora Obiektów. Zaznacz komponent pierwszy, a następnie kliknij na zakładkę Events. Powinieneś mieć zaznaczoną pierwszą linię. Możesz teraz wybrać zdarzenie OnClick dla tego komponentu. Kliknij na przycisk ze strzałką, a wyświetli się lista procedur, które możesz wykorzystać z tym komponentem. Zobacz fotografię: 

Możesz teraz wybrać to zdarzenie. Rozumiesz? W wyniku naciśnięcia przycisku wykonana zostaje procedura Click. Gdybyś deklaracji procedury nie umieścił w sekcji published to na tej liście nie byłoby nic bo w takim wypadku Delphi nie wiedziałby, że ta procedura jest przeznaczona do wykorzystania przez komponenty. Tak samo zrób z komponentem btnSecond - teraz oba korzystają z tej samej procedury. Możesz uruchomić program, aby zobaczyć jak działa. Tak jak tego chcieliśmy, prawda? W zależności od naciśniętego przycisku wyświetla komunikat.  

 

Operatory as i is

Pierwszy z nich jest tzw. operatorem rzutowania. Najłatwiej jest to wyjaśnić na przykładzie: umieść na formie dwa komponenty - Button i Label. Teraz wygeneruj procedurę OnClick komponentu Button. Pod zdarzenie OnClick komponentu Label także "podepnij" procedurę OnClick komponentu Button. Teraz oba komponenty mają tę samą procedurę obsługi zdarzenia OnClick:

procedure TForm1.Button1Click(Sender: TObject);
begin
  (Sender as TButton).Caption := 'Komponent Button';
end;

O co chodzi w tej procedurze?  Wskaźnik ( parametr ) Sender jest rzutowany na komponent Button. Innymi słowy zmień właściwość Caption komponentu jeżeli zdarzenie pochodzi od komponentu typu TButton. Sprawdź to. Uruchom Delphi, a następnie kliknij na przycisku. Fajnie, tekst na przycisku się zmieni. Teraz kliknij na etykiecie - pojawi się błąd. Etykieta nie jest typu TButton i nie może w niej zostać zmieniona wartość Caption. 

Istnieje tylko problem z komunikatem błędu. Jak się go pozbyć? Do tego służy operator is. Służy on do porównywania typów. Oto zmodyfikowana wersja tej procedury:

procedure TForm1.Button1Click(Sender: TObject);
begin
  if Sender is TButton then
    (Sender as TButton).Caption := 'Komponent Button'
  else Exit;
end;

Zastosowaliśmy tutaj instrukcję if. Najpierw następuje sprawdzenie, czy procedura pochodzi z komponentu typu TButton. Jeżeli tak to następuje rzutowanie, jeżeli nie - program nie robi nic. 

Jeszcze jeden przykład. Tym razem program będzie wyszukiwał, czy na formie są jakieś komponenty typu TEdit. Jeżeli tak to zmień napis na nich. Umieść więc na formie kilka komponentów typu Edit i parę innych.

procedure TForm1.Button1Click(Sender: TObject);
var
  I : Integer;
begin
  for I := 0 to ComponentCount -1 do
    if Components[i] is TEdit then
      TEdit(Components[i] as TEdit).Text := 'Komponent Edit';
end;

Zastosowałem tutaj pętle. Właściwość ComponentCount to liczba komponentów znajdujących się na formie. Następnie następuje sprawdzenie, czy dany komponent jest typu Edit ( Components[i] - wyszukiwany komponent ). Jeżeli tak to następuje zmiana tekstu na nim.

Korzystanie z plików pomocy w Delphi

Delphi ma bardzo dobrze rozbudowany system pomocy. Właściwie możesz znaleźć w niej opis każdej funkcji. Wiem, wiem, że po angielsku, ale są także przykłady, a z samych przykładów możesz się czegoś nauczyć. Jeżeli piszesz jakiś kod w edytorze kodu i nie rozumiesz danego polecenia, albo szukasz pomocy, jakie parametry powinna posiadać ta funkcja to zaznaczasz dane słowo, wciskasz F1, a Delphi znajdzie Ci temat dotyczący szukanego polecenia. Pliki pomocy są podzielone, a naciskając F1 Delphi otworzy jeden domyślny plik pomocy. Wpisując w nim hasło którego szukasz może go po prostu w tym pliku ni być. A wyszukując tą metodą, która przed chwilą opisałem jesteś pewien, że przeszukał wszystkie pliki. 

Sposoby na zmniejszenie rozmiaru pliku wykonywalnego

Tak, istnieją na to sposoby. Po pierwsze Delphi dołącza do pliku wykonywalnego różne pliki. Pliki te są potrzebne jeżeli gość, któremu dostarczasz program NIE ma Delphi. Jeżeli przesyłasz program do osoby, która ma tę samą Delphi co Ty to tych plików do EXE włączać nie musisz i masz mniejszy plik wykonywalny. U mnie jest to 14 kB jeżeli nie dołączam tych plików. Jak to zrobić? Wybierz z menu Project -> Options, a następnie kliknij na zakładkę Packages. Zaznacz "Build with runtime packages". Teraz z menu Project -> Build. Spowoduje to zbudowanie EXE jeszcze raz. Teraz EXE jest mniejszy, nie?

Drugi sposób to użycie specjalnych programów. Są takie, które potrafią o bardzo wiele zmniejszyć rozmiar pliku wykonywalnego. Najpopularniejsze na dzień dzisiejszy to chyba UPX i ASPack. Jak one działają? Jak zwykłe programy kompresujące. Otóż kompresują Twój program, a następnie DOŁĄCZAJĄ do tego skompresowanego programu dekompresor. Podczas uruchamiania programu uruchamiany jest dekompresor który rozpakowuje spakowany program. Najpoważniejsze wady takich programów to wolniejsze działania skompresowanego pliku. Inna sprawa, że programy antywirusowe mogą wsiąść taki spakowany plik jako wirus! W końcu jest w nim dołączony inny plik. 

Podsumowanie

To był trudny rozdział, ale najgorsze masz już za sobą. W następnych rozdziałach będzie już łatwiej. Teraz wiesz co to są klasy i umiesz je wykorzystać.