RZUTOWANIE TYPÓW, PRZYPISANIE POLIMORFICZNE.

skrót przez artykuł dla leniwych.

    Rzutowanie typów stoi za każdym polimorfizmem. Możliwe jest obejrzenie operacji rzutowania typów za pomocą prostego programu, który przekazuje wszystkie kliknięcia w programie za pomocą jednej procedury obsługi zdarzenia OnClick. Ustawia on wówczas właściwość wspólną dla wszystkich obiektów sterujących wykorzystując odniesienia polimorficzne. Stwórzmy już na początku tego artykułu prosty program, dla lepszego zrozumienia, poprzez umieszczenie na formularzu kilku typowych obiektów sterujących: Label1, Edit1, Button1 oraz Panel1. Wszystkie one mają dynamiczne podpowiedzi uaktywniane przez ustawienie ich właściwości ShowHint na true. Właściwość Hint w każdym z tych obiektów ustawiamy tak, żeby wskazywała rodzaj obiektu, np. w Label1 ta właściwość może mieć wartość "To jest etykieta - Label", itp. W programie umieścimy jedną procedurę obsługi zdarzenia OnClick, ale w taki sposób, że kliknięcie na dowolny obiekt spowoduje zmianę właściwości Hint tego obiektu. Procedurę obsługi zdarzenia OnClick umieszczamy w formularzu Form1:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormClick(TObject *Sender)
{
 (reinterpret_cast<TControl *>(Sender))->Hint = "Zmieniona nazwa podpowiedzi";
}
//--------------------------------

Można również tak:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormClick(TObject *Sender)
{
 (reinterpret_cast<TControl *>(Sender))->Hint = "Obiekt: " + (reinterpret_cast<TControl *>(Sender))->Name + " po zmianie.";
}
//--------------------------------

Klikając w nazwie procedury występującej w liście rozwijanej Inspektora Obiektów, zakładka Events, można przyłączyć tę procedurę do zdarzenia OnClick dowolnego obiektu, który taką takie zdarzenie obsługuje. Dlatego teraz dla każdego z umieszczonych na formularzu obiektów wybieramy w Inspektorze Obiektów na zakładce Events zdarzenie OnClick i z rozwijanej listy wybieramy FormClick;

 

Jeśli ktoś nie zrozumiał może pobrać przykład: rzutowanie.rar - rozmiar archiwum 190 KB.

W ten oto sposób do zdarzenia OnClick dla każdego obiektu występującego na formularzu przypisaliśmy zdarzenie OnClick obiektu Form1.
    Każdy na pewno zauważył już parametr Sender, występujący we wszystkich funkcjach obsługi zdarzeń, jak jest jego rola? Kiedy występuje jakieś zdarzenie razem ze związaną z nim procedurą obsługi, procedura otrzymuje odniesienie do obiektu, który wygenerował zdarzenie. To odniesienie przekazywane jest procedurze poprzez parametr Sender. Kiedy procedura jest wywoływana, Sender zawiera obiekt który wywołał to zdarzenie. W tym przypadku, kiedy wywoływana jest procedura TForm1::FormClick z przykładu wyżej, Sender zawiera obiekt na który użytkownik kliknął. Ponieważ procedura jest wspólna dla formularza i czterech obiektów sterujących, może się przydać informacja o tym, na którym obiekcie kliknięto. Sender jest zadeklarowany w nagłówku funkcji jako TObject, jest więc on tym obiektem niezależnie od tego, jaka klasa jest przekazywana do funkcji.
    Egzemplarz dowolnej klasy wywodzącej się od TObject można przypisać zmiennej lub parametrowi TObject. Wszystkie obiekty C++ Buildera pochodzą od klasy TObject, która jest jak rodzić dla wszystkich klas, to protoplasta, od niego wywodzą się wszystkie klasy obiektów. Wszystkie więc obiekty są obiektami TObject (jabłko pada niedaleko od jabłoni), jak również wszystkim tym, czym mogą one być, to znaczy obiektami Button, Label, itp. Ponieważ na dole wewnątrz każdego obiektu są również obiekty klasy TObject, dlatego można przypisać obiekt dowolnej klasy obiektowi albo parametrowi typu TObject. Nazywane to jest przypisaniem polimorficznym.

    Sender jest w pewnym sensie maską, a obiekt wyzwalający zdarzenie wchodzi w procedurę obsługi niosąc tą maskę. Kiedy obiekt wchodzi do funkcji w masce Sender, niewiele można o nim powiedzieć. W ten sposób  można przekazać dowolną klasę dowolnego obiektu do funkcji obsługi zdarzenia.
    W przykładzie powyżej, chcąc zmienić wartość właściwości Hint należącej do obiektu, który wywołał zdarzenie, nie można zrobić po prostu tak:
Sender->Hint = "Obiekt Button1";
ponieważ Sender jest obiektem klasy TObject, a ta klasa nie ma właściwości Hint. W ten sposób próbujemy mówić do samej maski, a to nie ma sensu, należy mówić do obiektu skrywającego się za maską.
    W ten sposób dochodzimy do wyrażenia (reinterpret_cast<TControl*>(Sender)). Klasa TControl jest najbliższa wszystkim czterem obiektom sterującym (przykład wyżej). TControl ma właściwość Hint, a wszystkie obiekty sterujące dziedziczą od niej tą właściwość. Jeśli więc zastosujemy maskę TControl zamiast Sender będziemy mieć dostęp do właściwości Hint każdego obiektu, który pod przebraniem Sender wejdzie do funkcji.

 

    Ponieważ (w przykładzie) każdy obiekt przychodzący jako Sender jest przynajmniej obiektem TControl, można traktować Sender jako TControl, zatem niezależnie od tego, czy dany obiekt będzie przekazywany poprzez Sender jako TButton, TEdit, TLabel czy TPanel, ponieważ wszystkie one są obiektami TControl, możemy je traktować tak samo i przypisać nowy łańcuch znakowy do ich właściwości Hint.
    To wszystko co zostało dotychczas opisane jest przypisaniem polimorficznym, teraz przyszedł czas na wykonanie polimorficzne, mechanizm jest mniej więcej ten sam, a oto prosty przykład:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormClick(TObject *Sender)
{
 (reinterpret_cast<TControl *>(Sender))->Repaint();
}
//--------------------------------

Teraz kliknięcie na dowolnym obiekcie spowoduje jego odnowienie na skutek działania polimorficznego.
Nadszedł czas na kilka praktycznych przykładów zastosowania:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormClick(TObject *Sender)
{
 String name = (reinterpret_cast<TControl *>(Sender))->Name;

 ShowMessage(("Kliknięto w: " + name).c_str());
 (reinterpret_cast<TLabel *>(Sender))->Caption = "jakiś tekst";
}
//--------------------------------

a teraz coś nowego:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormClick(TObject *Sender)
{
 String name = (dynamic_cast<TControl *>(Sender))->Name;

 ShowMessage(("Kliknięto w: " + name).c_str());
}
//--------------------------------

Operator dynamic_cast działa podobnie do operatora reinterpret_cast, występują jednak pewne różnice, zasada stosowania jest identyczna, jednak dynamic_cast odnosi się tylko do klasy określonej: <TControl*> w tym przypadku nic się nie zmieni, ale jeśli już zrobimy coś takiego <TLabel*> to operator będzie rzutował tylko na obiekty wywodzące się z klasy TLabel, inaczej niż gdy w przypadku operatora reinterpret_cast, który niejako zignoruje <TLabel*> i będzie rzutował na parametr Sender, najlepiej sprawdzić to na przykładzie:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormClick(TObject *Sender)
{
 (reinterpret_cast<TLabel *>(Sender))->Caption = "jakiś tekst";
}
//--------------------------------

 

W powyższym przykładzie zostanie zmieniona właściwość Caption wszystkich obiektów, obsługujących zdarzenie OnClick i posiadających właściwość Caption, a nie tylko obiekty Label1.


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormClick(TObject *Sender)
{
 (dynamic_cast<TLabel *>(Sender))->Caption = "jakiś tekst";
}
//--------------------------------

W tym przykładzie zmieniona zostanie właściwość Caption tylko obiektów wywodzących się z klasy TLabel, pozostałe obiekty zostaną zignorowane. Jeżeli jednak zrobimy coś takiego:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormClick(TObject *Sender)
{
 (reinterpret_cast<TEdit *>(Sender))->Caption = "jakiś tekst";
}
//--------------------------------

lub:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormClick(TObject *Sender)
{
 (dynamic_cast<TEdit *>(Sender))->Caption = "jakiś tekst";
}
//--------------------------------

to kompilator zgłosi błąd ponieważ klasa TEdit nie posiada właściwości Caption, aczkolwiek coś takiego:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormClick(TObject *Sender)
{
 (reinterpret_cast<TButton *>(Sender))->Caption = "jakiś tekst";
}
//--------------------------------

lub takiego:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormClick(TObject *Sender)
{
 (reinterpret_cast<TLabel *>(Sender))->Caption = "jakiś tekst";
}
//--------------------------------

już zadziała i w przypadku wszystkich obiektów podłączonych do zdarzenia OnClick spowoduje zmianę właściwości Caption niezależnie od tego z jakiej klasy się wywodzą, pod warunkiem, że posiadają właściwość Caption, z jednym wyjątkiem w przypadku obiektu Edit1, pomimo iż nie posiada właściwości Caption zmianie ulegnie jego właściwość Text i nie wiem czemu tak się dzieje.
    Inny przykład zastosowania tych operatorów to odwołanie się do obiektu na podstawie jego nazwy, w ten sposób klikając w jeden obiekt można zmienić właściwość innego obiektu, wyszukując go na podstawie nazwy:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormClick(TObject *Sender)
{
 (dynamic_cast<TButton *>(FindComponent("Button1"))->Caption = "Button1";
}
//--------------------------------

Oczywiście obiekt którego właściwość zmieniamy nie musi być w tym przypadku podłączony do obsługi zdarzenia OnClick, ponieważ zmieniamy jego właściwość Caption (w przykładzie) poprzez kliknięcie na formularzu, może to być oczywiście dowolny inny obiekt, można zmienić dowolną właściwość jednego obiektu poprzez kliknięcie w inny obiekt i nie muszą one być powiązane ze sobą żadnym zdarzeniem, ani żadną procedurą czy parametrem, można w ten sposób zmieniać właściwość wielu obiektów, np. 10 obiektów Button1...10, należących do tej samej klasy:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button0Click(TObject *Sender)
{
 for(int i = 1; i <= 10; i++)
  dynamic_cast<TButton *>(FindComponent("Button" + IntToStr(i)))->Caption = "Przycisk" + IntToStr(i);
}
//--------------------------------

To przykład odwołania się do wielu obiektów jednego typu na podstawie ich nazwy, można również odwoływać się do obiektów różnych typów na podstawie ich nazwy o ile nazwa jest podobna. Umieśćmy na formularzu:  2 obiekty typu TButton o nazwach Obiekt1 i Obiekt2, 2 obiekty typu TLabel o nazwach Obiekt3 i Obiekt4 i 2 obiekty typu TPanel o nazwach Obiekt5 i Obiekt6, żeby zmienić dowolną właściwość tych wszystkich obiektów na raz, należy posłużyć się operatorem reinterpret_cast, dlaczego? Dlatego że odwołujemy się do konkretnej właściwości Caption którą te obiekty posiadają, ale klasy od których te obiekty się wywodzą już nie, czyli nie można odwołać się do klasy TObject czy TControl, ponieważ te klasy nie posiadają właściwości Caption:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button0Click(TObject *Sender)
{
 for(int i = 1; i <= 6; i++)
  reinterpret_cast<TForm1 *>(FindComponent("Obiekt" + IntToStr(i)))->Caption = "Obiekt" + IntToStr(i);
}
//--------------------------------

W przykładzie we wszystkich obiektach, których nazwy zaczynają się na "Obiekt" i kończą cyfrą od 1 do 6, właściwość Caption ulegnie zmianie, niezależnie od tego od jakiej klasy pochodzą, rzutowanie odbyło się na klasę TForm1, ale równie dobrze mogłem rzutować na każdą inną klasę pod warunkiem że posiada ona właściwość Caption. Gdybym zrobił coś takiego:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button0Click(TObject *Sender)
{
 for(int i = 1; i <= 6; i++)
  dynamic_cast<TForm1 *>(FindComponent("Obiekt" + IntToStr(i)))->Caption = "Obiekt" + IntToStr(i);
}
//--------------------------------

Zmianie nie uległa by żadna właściwość Caption, żadnego obiektu, ponieważ operator dynamic_cast rzutuje tylko na podaną klasę, natomiast w takim przypadku:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button0Click(TObject *Sender)
{
 for(int i = 1; i <= 6; i++)
  dynamic_cast<TButton *>(FindComponent("Obiekt" + IntToStr(i)))->Caption = "Obiekt" + IntToStr(i);
}
//--------------------------------

zmianie uległaby właściwość Caption tylko obiektów typu TButton, pozostałe obiekty zostałyby zignorowane.
Do niektórych właściwości obiektów można się odwoływać za pomocą operatora dynamic_cast o ile klasa na którą rzutujemy posiada tą właściwość:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button0Click(TObject *Sender)
{
 for(int i = 1; i <= 6; i++)
  dynamic_cast<TControl *>(FindComponent("Obiekt" + IntToStr(i)))->Enabled = false;
}
//--------------------------------

To byty przykłady przypisania polimorficznego, na zakończenie przykład wywołania polimorficznego, w tym celu umieśćmy na formularzu obiekt OpenDialog1, wywołamy jego funkcję Execute wykorzystując wywołanie polimorficzne, poprzez wyszukanie nazwy tego obiektu:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button0Click(TObject *Sender)
{
 dynamic_cast<TOpenDialog *>(FindComponent("OpenDialog1"))->Execute();
}
//--------------------------------

Oczywiście można by to - w tym przypadku - zrobić dużo prościej, ale przecież nie o to w tym artykule chodzi. Podsumowując, jeżeli chcemy odwołać się do wielu obiektów jednocześnie, należy nadać im podobną nazwę i odwoływać się do nich za pomocą operatora dynamic_cast lub reinterpret_cast, stosując funkcję wyszukiwania obiektu FindComponent.

Opracował: Cyfrowy Baron