Przy wizualnym programowaniu takim jakie oferuje c++ Builder nie można było by sobie wyobrazić nieużywania formularzy. Przywykliśmy do tych okien tak bardzo że stały sie one czymś tak oczywistym ze stały sie one pewnym standardem w programowaniu.
Wykorzystując różnego rodzaju studia programistyczne ( w tym przypadku będziemy rozpatrywac na przykładzie narzędzia firmy borland - buildera) nie interesuje nas jak do końca działa formularz. Każdy formularz to osobna klasa, czyli nie jest to tak jak w przypadku innych obiektów że pochodza z tej samej klasy.
Z formularzami cala zabawa wygląda inaczej. Najlepiej będzie jeśli załaczysz teraz buildera i sam o kilku rzeczach sie przekonasz.
W sowim projekcie utworz 3 formularze. Czyli posiadasz Form1, Form2 i Form3.
Przejdz teraz do pliku nagłowkowego pierwszego formularza i zapewne ukaże Ci sie klasa
| 1 2 3 4 5 6 7 8 9 10 11 12 | class TForm1 : public TForm { __published: // IDE-managed Components TButton *Button1; TPanel *Panel1; TButton *Button2; void __fastcall Button1Click(TObject *Sender); void __fastcall Button2Click(TObject *Sender); private: // User declarations public: // User declarations __fastcall TForm1(TComponent* Owner); }; |
Klasa TForm1 jest klasą pochodną TForm, czyli wszystkie klasy formularzy pochodzą od tej głownej.
Jeśli przejdziesz do pliku źródłowego to zauważysz coś takiego:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <vcl.h> #pragma hdrstop #include "Unit1.h" #include "Unit2.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //<<-------CHODZI NAM O TO //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } |
Obiekt tworzony jest juz z klasy pochodnej a nie z głownej. Zauwaz że w każdym innym formularzu jest tak samo - tzn. że tworzony jest z osobnej klasy.
Taki stan rzeczy pozwala nam bardzo wydajnie ten mechanizm wykorzystać.
Wyobraźmy sobie sytuacje że mamy formularz główny i dodatkową formę która musiala by zostać otworzona kilkukrotnie.
Utwórz na Form1 przycisk i zaprogramuj go tak:
| 1 2 | TForm2 *tmp=new TForm2(this); tmp->Show(); |
Oczywiście w Unit1.cpp musimy miec dokłaczony nagłowego Unit2 (#include "Unit2.h").
Klikając na przycisk tworzymy wskaźnik na obiekt klasy TForm2,a poźniej przez ten wskaźnik uruchamiamy utoworzony formularz.
MOżemy tak pokazać formularzy ile chcemy kazdy identyczny dzięki temu że tworzony z klasy TForm2.
Zauważ parametr jaki został przekazany do konstruktora a mianowicie "this" jest to wskaźnik na nasz formularz. Pokaże teraz dlaczego jest istotne przydzielanie własciciela tworzonego obiektu.
Przejdz na Form2 w trybie projektownia i utowórz na formie przycisk i zaprogramuj go tak:
| 1 2 | TForm3 *tmp=new TForm3(this); tmp->Show(); |
w zdarzeniu OnClose naszej Form2 wpisz następującą linijke:
| 1 | delete this; |
Co się teraz stanie?
Uruchom aplikacje i otwórz 2 razy Form2, teraz z pierwszej Form2 uruchom 2 razy Form 3 i to samo z drugiej Form2, a teraz zamknij jedną z Form2....
No właśnie Form3 utworzone przez Form2 która została zamknieta znikają.
Dlaczego tak sie dzieje?
Jako właściciela Form3 przez wskaźnik this podaliśmy Form2, w momencie kiedy Form2 jest zamykana usuwamy poprzez wskaźnik obiekt z pamięci co spowoduje usunięcie obiektów potomnych.
Utwórz teraz na Form1 panel o nazwie Panel1 oraz jeszcze jeden przycisk i dodaj taki kod:
| 1 2 3 | TForm2 *tmp=new TForm2(this); tmp->Parent=Panel1; tmp->Show(); |
Co sie teraz stanie?
Utworzymy Form2 ale nie pojawi sie ona tak normalnie tylko w Panel1 i tylko na obszarze tego obiektu będzie mogła być wykorzystana, ponieważ ustaliliśmy pole Parent, czyli jego rodzica na wybrany komponent.
Rozbudujmy nieco naszą przykładową aplikacje.
Załóżmy że po przyciśnięciu na przycisk otwiera sie okno z którego wybieramy plik tekstowy a poźniej otwiera sie okno w panelu i wczytuje ten wybrany plik.
Na Form1 utwórz 3 przycisk i z panelty Dialogs utwórz OpenDialog1.
Na Form2 daj Memo1.
Nalezy sie zastanowić jak przekazac ścieżke do pliku do Form2 aby ten po załadowaniu go otworzył.
Wystarczy przerobić konstruktor.
W pliku źródlowym Form2 dodajemy jeden parametr:
| 1 2 3 4 | __fastcall TForm2::TForm2(AnsiString Path, TComponent* Owner) //<- dodaliśmy parametr : TForm(Owner) //Path { } |
i w pliku nagłowkowym uaktualniamy prototyp konstruktora
| 1 | __fastcall TForm2(AnsiString Path, TComponent* Owner); |
teraz zajemimy sie oprogramowaniem przycisku na Form1:
| 1 2 3 4 5 6 | if(OpenDialog1->Execute()==ID_OK) { TForm2 *tmp=new TForm2(OpenDialog1->FileName,this); tmp->Parent=Panel1; tmp->Show(); } |
jednak w dalszym ciągu nasza Form2 nie wie co zrobić z parametrem Path.
Dlatego wpliku źródłowym Form2 robimy następujące zmiany;
| 1 2 3 4 5 6 7 | AnsiString Path; //--------------------------------------------------------------------------- __fastcall TForm2::TForm2(AnsiString Path, TComponent* Owner) : TForm(Owner) { ::Path=Path; } |
Teraz po utworzeniu form@ Parametr Path zostanie zostanie zapisany do globalnej zmennej Path. Teraz wystarczy juz tylko w Form 2 obsłużyć zdarzenie OnCreate:
| 1 2 3 4 5 | void __fastcall TForm2::FormCreate(TObject *Sender) { if(Path!="") Memo1->Lines->LoadFromFile(Path); } |
Komponent Memo który znajduje sie na Form2 wczyta plik.
Pamiętaj że kod w pierwszym i drugim przycisku na Form1 jest juz nieprawidłowy ponieważ zmienił sie konstruktor dlatego nalepiej go wykomentowac.
Myśle że ten artykuł pomogł zrozumieć klasy formularzy i pozwoli lepiej je wykorzystać czytelnikom w projektowaniu swoich aplikacji.