Tworzenie klas

  Pisząc programy w c++ nieustannie korzystamy z różnego rodzaju klas i właściwie nie można bez nich stworzyć żadnego programu (przynajmniej w c++). Komponenty które ciągle wykorzystujemy w swoich programach są klasami. W BCB przyjęło się poprzedzanie nazwy każdej nowo tworzonej klasy dużą literą T, np.: TForm, TLabel, TEdit, TMemo itp...
Klasa jest grupą zmiennych najczęściej o różnych typach skojarzonych z zestawem odnoszących się do nich funkcji. Klasa może składać się z dowolnej kombinacji zmiennych prostych oraz zmiennych innych klas. Wygodę wynikającą z tworzenia własnych klas można docenić dopiero gdy się nauczy je tworzyć, dlatego teraz postaram się pokazać na przykładzie jak stworzyć prostą klasę.
  Stworzymy klasę o nazwie TViewerSFX (nazwa dowolna), która będzie rysowała na obiekcie Image zawierającym grafikę w formacie BMP inny obraz, ale będzie się to odbywało linia po linii tak, że stworzy to ciekawy efekt. Program wykorzystujący tą klasę znajduje się w tutaj.

    1. Tworzenie klasy TViewerSFX.

  Uruchamiamy BCB, program automatycznie utworzy projekt aplikacji, ale na razie nie będzie to nam potrzebne więc zamykamy wszystko wybierając z menu
File | Close All. Następnie wybieramy menu File | New - powinno wyskoczyć okno dialogowe New Items - wybieramy opcję Unit . Zostanie utworzony plik Unit1.cpp i Unit1.h. Zapisujemy pliki pod nazwą SFXViewer (nazwa jest dowolna).
Pliki powinny wyglądać tak:

// Plik źródłowy SFXViewer.cpp
//--------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"


//--------------------------------
#pragma package(smart_init)
//--------------------------------


// Plik nagłówkowy SFXViewer.h
//--------------------------------
#ifndef Unit1H
#define Unit1H

//--------------------------------
#endif
//--------------------------------

    Zostały utworzone szkielety plików potrzebne do stworzenia klasy. Każda klasa zaczyna się od słowa kluczowego class po którym podaje się nazwę klasy a następnie wstawia się nawiasy otwierający i zamykający '{ }'. Wewnątrz nawiasów umieszcza się deklaracje funkcji i zmiennych. Domyślnie klasa zawiera konstruktora i destruktora klasy, nie ma potrzeby ich tworzyć ponieważ kompilator zadba o wszystko, ale jeśli potrzebujemy zainicjować jakieś parametry początkowe, które są potrzebne do zainicjowania klasy, wtedy należy utworzyć konstruktora klasy. Jeżeli utworzymy konstruktora to trzeba również utworzyć destruktora klasy. Konstruktor klasy jest to funkcja, która nosi taką samą nazwę jak klasa, natomiast destruktor również jest funkcją o takiej samej nazwie jak klasa, lecz nazwa jest poprzedzona znakiem '~'. Deklarację konstruktora i destruktora umieszcza się w pliku nagłówkowym:

// Plik nagłówkowy SFXViewer.h
//--------------------------------
#ifndef Unit1H
#define Unit1H

//--------------------------------
class TViewerSFX
{
public:
       __fastcall TViewerSFX(); // konstruktor
       __fastcall ~TViewerSFX(); // destruktor
private:

};
//--------------------------------
#endif

    Jak zapewne już zauważyliście wewnątrz ciała klasy znajdują się sekcje public i private. Konstruktor i destruktor klasy zawsze są deklarowane w sekcji public. Przeznaczenie tych funkcji jest takie:
public - wewnątrz tej sekcji deklaruje się funkcje, metody i zmienne, które będą dostępne zarówno wewnątrz i na zewnątrz klasy
.
private - wewnątrz tej sekcji deklaruje się funkcje, metody i zmienne, które będą dostępne tylko wewnątrz klasy.
   
Zaczynamy tworzyć klasę. Nasza klasa będzie wykorzystywała grafikę więc włączymy do niej istniejącą już klasę Graphics. W tym celu importujemy w pliku nagłówkowym w sekcji include plik Graphics.hpp:

// Plik nagłówkowy SFXViewer.h
//--------------------------------
#ifndef Unit1H
#define Unit1H

#include <Graphics.hpp>
//--------------------------------
class TViewerSFX
{
public:
       __fastcall TViewerSFX(); // konstruktor
       __fastcall ~TViewerSFX(); // destruktor
private:

};
//--------------------------------
#endif

Następnie deklarujemy w sekcji public pliku nagłówkowego funkcję która będzie pobierała parametry potrzebne do zainicjowania i dalszej pracy klasy. Funkcję nazwiemy ViewSFXImage a jako parametry przekażemy jej wskaźnik do obiektu typu TImage na którym będą wykonywane operacje rysowania, wskaźnik do nazwy plików, współrzędne poziomego i pionowego rozmieszczenia rysowanych bitmap na obiekcie typu TImage, oraz liczbę plików, które będą brały udział w animacji:

// Plik nagłówkowy SFXViewer.h
//--------------------------------
#ifndef Unit1H
#define Unit1H

#include <Graphics.hpp>
//--------------------------------
class TViewerSFX
{
public:
       __fastcall TViewerSFX(); // konstruktor
       __fastcall ~TViewerSFX(); // destruktor
    
  void __fastcall ViewSFXImage(TImage *Image, String PathName, int X, int Y, int count);
private:

};
//--------------------------------
#endif

Po zadeklarowaniu funkcji w pliku nagłówkowym trzeba je zdefiniować w pliku źródłowym:

// Plik źródłowy SFXViewer.cpp
//--------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"

//--------------------------------
#pragma package(smart_init)
//--------------------------------
__fastcall TViewerSFX::TViewerSFX() // definicja konstruktora klasy
{
}
//--------------------------------
__fastcall TViewerSFX::~TViewerSFX() // definicja destruktora klasy
{
}
//--------------------------------
void __fastcall TViewerSFX::ViewSFXImage(TImage *Image, String PathName, int X, int Y, int count)
{
}
//--------------------------------

Teraz w pliku nagłówkowym zadeklarujemy obiekt Timer typu TTimer oraz funkcję obsługującą zdarzenie OnTimer dla obiektu Timer. Obiekt i funkcja zostaną zadeklarowane w sekcji private pliku nagłówkowego:

// Plik nagłówkowy SFXViewer.h
//--------------------------------

private:
       
TTimer *Timer;
        void __fastcall
TimerTimer(TObject *Sender);

//--------------------------------

Następnie przechodzimy do pliku źródłowego i wewnątrz konstruktora klasy tworzymy obiekt Timer i ustawiamy początkowe parametry pracy, "podłączamy" również zdarzenie OnTimer do funkcji TimerTimer. Ponieważ funkcja TimerTimer nie została jeszcze zdefiniowana, należy również to teraz zrobić:

// Plik źródłowy SFXViewer.cpp
//--------------------------------
__fastcall TViewerSFX::TViewerSFX() // definicja konstruktora klasy
{
 Timer = new TTimer(NULL);
 Timer->Enabled = false;
 Timer->Interval = 500;
 Timer->OnTimer = TimerTimer;
}
//--------------------------------
__fastcall TViewerSFX::~TViewerSFX() // definicja destruktora klasy
{
}
//--------------------------------
void __fastcall TViewerSFX::ViewSFXImage(TImage *Image, String PathName, int X, int Y, int count)
{
}
//--------------------------------
void __fastcall TViewerSFX::TimerTimer(TObject *Sender)
{
}
//--------------------------------

Potrzebnych nam będzie również kilka zmiennych typu int, kilka obiektów typu TBitmap i TImage, zmienna typu bool oraz zmienna typu String. Wszystko to deklarujemy w sekcji private pliku nagłówkowego:

// Plik nagłówkowy SFXViewer.h
//--------------------------------

private:
       
TTimer *Timer;
        void __fastcall
TimerTimer(TObject *Sender);
       
int a, b;
        int t, top, left;
        int Count;
        Graphics::TBitmap *bmp;
        Graphics::TBitmap *tmp;
        Graphics::TBitmap *tlo;
        String path;
        bool BStart;
        TImage *Image1;
//--------------------------------

Zadeklarowane obiekty trzeba zdefiniować, a najlepszym do tego celu miejscem jest konstruktor klasy, natomiast w destruktorze klasy zniszczymy wszystkie utworzone obiekty:

// Plik źródłowy SFXViewer.cpp
//--------------------------------
__fastcall TViewerSFX::TViewerSFX() // definicja konstruktora klasy
{
 a = 0;
 b = 1;
 t = 0;
 BStart = true;
 Timer = new TTimer(NULL);
 Timer->Enabled = false;
 Timer->Interval = 500;
 Timer->OnTimer = TimerTimer;
 bmp = new Graphics::TBitmap();
 tmp = new Graphics::TBitmap();
 tlo = new Graphics::TBitmap();
 Image1 = new TImage(NULL);
}
//--------------------------------
__fastcall TViewerSFX::~TViewerSFX() // definicja destruktora klasy
{
 bmp->Free();
 tmp->Free();
 tlo->Free();
 Image1->Free();
 Timer->Free();
}
//--------------------------------

Przechodzimy do pliku źródłowego i wewnątrz funkcji ViewSFXImage inicjujemy wszystkie potrzebne zmienne i obiekty. Wewnątrz tej funkcji zostanie zainicjowany Timer poprzez ustawienie jego właściwości Enabled na true. To oznacza, że po wywołaniu w programie funkcji ViewSFXImage Timer zacznie "tykać" i rozpocznie się animacja grafiki, dlatego wewnątrz zdarzenia TimerTimer umieszczamy kod, który będzie sterował animacją:

// Plik źródłowy SFXViewer.cpp
//--------------------------------
void __fastcall TViewerSFX::ViewSFXImage(TImage *Image, String PathName, int X, int Y, int count)
{
 Image1 = Image;
 path = PathName;
 tlo->LoadFromFile(path + "0.bmp");
 top = Y;
 left = X;
 Count = count;
 Timer->Enabled = true;
}
//--------------------------------
void __fastcall TViewerSFX::TimerTimer(TObject *Sender)
{
 if(BStart)
 {
  t++;
  if(t > Count) t = 1;

  Timer->Enabled = false;

  try
  {
   String FileName = path + IntToStr(t) + ".bmp";
   bmp->LoadFromFile(FileName);

   tmp->Width = bmp->Width;
   tmp->Height = 1;

   for(a = 0; a < bmp->Height; a += 2)
   {
    if(BStart)
    {
     tmp->Canvas->CopyRect(Rect(0, 0, bmp->Width, 1), bmp->Canvas, Rect(0, a, bmp->Width, a + 1));
     Image1->Canvas->Draw(left, top + a, tmp);
     tmp->Canvas->CopyRect(Rect(0, 0, bmp->Width, 1), tlo->Canvas, Rect(0, a + 1, bmp->Width, a + 2));
     Image1->Canvas->Draw(left, top + a + 1, tmp);
    }
    Application->ProcessMessages();
    }
   for(b = bmp->Height - 1 ; b > 0; b -= 2)
   {
    if(BStart)
    {
     tmp->Canvas->CopyRect(Rect(0, 0, bmp->Width, 1), bmp->Canvas, Rect(0, b, bmp->Width, b + 1));
     Image1->Canvas->Draw(left, top + b, tmp);
    }
    Application->ProcessMessages();
   }
   Timer->Enabled = true;
  }catch(...){;}
 } else Timer->Enabled = false;
}
//--------------------------------

Opiszę tylko pokrótce jak to funkcjonuje, a więc w programie w którym chcemy wykorzystać naszą klasę umieszczamy na formularzu obiekt Image, następnie wczytujemy do tego obiektu jakiś plik graficzny w formacie bmp, potem tworzymy sobie kilka mniejszych plików (również w formacie bmp), które będą rysowane na tle (obiekcie Image), więc muszą one zawierać również fragment tła z właściwą grafiką. Wszystkie pliki przeznaczone do animacji muszą mieć taką samą nazwę różniącą się tylko numerami np.: img1.bmp, img2.bmp, img3.bmp, itd...
    Należy utworzyć również plik graficzny tylko z fragmentem tła (bez żadnej dodatkowej grafiki), plik ten musi nosić taką samą nazwę jak pliki pozostałe, ale musi być numerowany jako 0, np.: img0.bmp. Plik ten posłuży do wymazywania tła. W kodzie, w tabeli powyżej zaznaczyłem fragment kodu na żółto, określa on szerokość plików przeznaczonych do animacji i co ważne jeśli szerokość pliku jest liczną parzystą to od szerokości grafiki (bmp->Height) trzeba odjąć 1, gdyby natomiast szerokość grafiki była liczbą nieparzystą wtedy nic nie odejmujemy od szerokości pliku.
Jeżeli nic nie zrozumieliście z mojego wywodu to proponuję teraz ściągnąć plik z programem i źródłem (utworzono w BCB 4) ściągnij.
    Pozostało jeszcze utworzenie funkcji Stop, która będzie zatrzymywała animację. Należy ją zadeklarować w sekcji public pliku nagłówkowego a następnie zdefiniować w pliku źródłowym:

// Plik nagłówkowy SFXViewer.h
//--------------------------------
#ifndef Unit1H
#define Unit1H

#include <Graphics.hpp>
//--------------------------------
class TViewerSFX
{
public:
       __fastcall TViewerSFX(); // konstruktor
       __fastcall ~TViewerSFX(); // destruktor
    
  void __fastcall ViewSFXImage(TImage *Image, String PathName, int X, int Y, int count);
      
void __fastcall Stop(); 
private:

};
//--------------------------------
#endif

 

// Plik żródłowy SFXViewer.cpp
//--------------------------------
void __fastcall TViewerSFX::Stop()
{
 BStart = false;
 Timer->Enabled = false;
 bmp->Height = 0;
 b = 0;
 a = 1000;
}
//--------------------------------

Jeśli o niczym nie zapomniałem to klasa jest już gotowa.
Pliki źródłowy.
Plik nagłówkowy.

    2. Dołączanie klasy TViewerSFX do projektu aplikacji.

    Wybieramy z menu File | New Aplication, zostanie utworzony nowy projekt. Zapisujemy go pod dowolną nazwą, a następnie umieszczamy na formularzu komponent Image1 i wczytujemy do niego wcześniej przygotowany plik w formacie bmp. Potem wybieramy z menu Project | Add to Project... i włączamy do naszego projektu plik SFXViewer zawierający klasę TViewerSFX. Następnie przechodzimy do pliku nagłówkowego (np.: Unit1.h) naszego projektu i w sekcji include importujemy plik SFXViewer.h:

// Plik nagłówkowy np.: Unit1.h
//--------------------------------
#ifndef Unit1H
#define Unit1H

//--------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ExtCtrls.hpp>
#include <Graphics.hpp>
#include "SFXViewer.h"

//--------------------------------

Następnie w sekcji private również pliku nagłówkowego (np.: Unit1.h) deklarujemy obiekt typu TViewerSFX o nazwie np.: sfxView:

// Plik nagłówkowy np.: Unit1.h
//--------------------------------
private:
        TViewerSFX *sfxView;
//--------------------------------

Przechodzimy do pliku źródłowego (np.: Unit1.cpp) i wewnątrz konstruktora klasy TForm1 definiujemy zadeklarowany obiekt:

// Plik źródłowy np.: Unit1.cpp
//--------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 sfxView = new TViewerSFX;
}
//--------------------------------

No i na koniec pozostało już tylko zainicjować funkcję ViewSFXImage będącą częścią klasy TViewerSFX. można to zrobić w dowolnym zdarzeniu, w przykładzie zainicjowałem animację w zdarzeniu OnShow formularza Form1:

// Plik źródłowy np.: Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormShow(TObject *Sender)
{
 sfxView->ViewSFXImage(Image1, ExtractFileDir(Application->ExeName) + "\\img", 126, 0, 3);
}
//--------------------------------

I to by było na tyle...

Opracował: Cyfrowy Baron