Przekazywanie obiektów i funkcji pomiędzy formularzami.
Czyli o tym co już powinieneś wiedzieć, nie wiesz, ale wstydzisz się o to zapytać.
Ten artykuł jest przeznaczony w zasadzie dla początkujących programistów, albowiem jeżeli ktoś już programuje od pewnego czasu i tworzy dość skomplikowane projekty, potrafi tworzyć klasy, to taka osoba musiała się już zetknąć z przekazywaniem obiektów pomiędzy formularzami, co w istocie jest po prostu przekazywaniem zmiennych, obiektów i funkcji pomiędzy klasami.
Postaram się opisać to językiem zrozumiałym dla laika, ponieważ zakładam, że artykuł ten czytają osoby dopiero zaczynające swoją przygodę z programowaniem. Do napisania tegoż artykułu skłoniły mnie liczne e-mail'e od Was z prośbą o pomoc w tejże właśnie kwestii. Nie ma właściwie czego ukrywać, niektórzy nie wiedzą jak przekazać zmienną zadeklarowaną w jednej klasie (formularzu) do drugiej klasy (formularza), więc co tu mówić o przekazywaniu funkcji.
W
artykule będę posługiwał się licznymi przykładami i zacznę od najprostszego.
Jak przekazać zmienną z jednego formularza do drugiego.
Proponuje przygotować sobie projekt z dwoma formularzami Form1 i Form2, dla takiego projektu zostaną automatycznie utworzone cztery pliki Unit1.cpp, Unit1.h, Unit2.cpp i Unit2.h. Wyjaśnijmy sobie co jest czym. Otóż pliki posiadające rozszerzenie *.cpp nazywane są plikami źródłowymi, ponieważ to właśnie w nich znajduje się kod źródłowy programu. Pliki z rozszerzeniem *.h są plikami nagłówkowymi ponieważ zawierają między innymi nagłówki bibliotek dołączanych do programu, to w nich znajduje się deklaracja klasy, oraz deklaracje wszystkich funkcji, obiektów i zmiennych przynależnych do tejże klasy. Spójrzmy jak wygląda plik nagłówkowy w jednostce Unit1.h tuż po utworzeniu nowego projektu:
// Plik źródłowy np. Unit1.h //--------------------------------------------------------------------------- #ifndef Unit1H #define Unit1H //--------------------------------------------------------------------------- #include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> //--------------------------------------------------------------------------- class TForm1 : public TForm { __published: // IDE-managed Components private: // User declarations public: // User declarations __fastcall TForm1(TComponent* Owner); }; //--------------------------------------------------------------------------- extern PACKAGE TForm1 *Form1; //--------------------------------------------------------------------------- #endif |
Żółtym tłem została zaznaczona klasa TForm1 dla której klasą bazową jest klasa TForm. Wszystkie deklaracje zamieszczone wewnątrz nawiasów należą do tej klasy. Załóżmy, że w sekcji private zadeklaruję zmienną typu int:
// Plik źródłowy np. Unit1.h //--------------------------------------------------------------------------- class TForm1 : public TForm { __published: private: int x; // << - deklaracja zmiennej typu int public: __fastcall TForm1(TComponent* Owner); }; //--------------------------------------------------------------------------- |
Tak zadeklarowana zmienna zostaje przypisana do klasy TForm1 i przynależy do niej. Jeżeli jakiś obiekt - bo zmienna jest również obiektem, chociaż dość specyficznym - zostanie zadeklarowany w sekcji private to jest widoczny dla klasy wewnątrz której został zadeklarowany, mówiąc prościej taki obiekt, w przykładzie zmienna x, jest rozpoznawany przez klasę TForm1 i ta klasa może go używać, co można sprawdzić tworząc np. zdarzenie OnCreate dla formularza Form1 i przypisując zmiennej x jakąś wartość:
// Plik źródłowy np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender) { x = 10; } //--------------------------------------------------------------------------- |
Rozważmy jednak sytuację, gdy zmienna x zostanie zadeklarowana w pliku nagłówkowym ale poza deklaracją klasy TForm1:
// Plik źródłowy np. Unit1.h //--------------------------------------------------------------------------- #ifndef Unit1H #define Unit1H //--------------------------------------------------------------------------- #include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> //--------------------------------------------------------------------------- int x; class TForm1 : public TForm { __published: // IDE-managed Components private: // User declarations public: // User declarations __fastcall TForm1(TComponent* Owner); }; //--------------------------------------------------------------------------- extern PACKAGE TForm1 *Form1; //--------------------------------------------------------------------------- #endif |
Otóż co się zmieniło, wydaje się że niewiele ale nic bardziej mylnego. Tak zadeklarowana zmienna jest również rozpoznawana przez klasę TForm1, ale już nie przynależy do niej, właściwie nie należy do żadnej klasy, jednak można ją wykorzystać wewnątrz klasy TForm1 i nie chcę uprzedzać wypadków, ale nie tylko wewnątrz tej klasy.
Podsumowując, jeżeli zadeklarujemy dowolną zmienną wewnątrz klasy w sekcji private to taka zmienna staje się własnością tejże klasy, jednakże zmienna zadeklarowana w sekcji private nie jest widoczna dla innych klas w projekcie aplikacji.
Teraz postąpmy jeszcze inaczej i umieśćmy deklarację zmiennej wewnątrz klasy w sekcji public:
// Plik źródłowy np. Unit1.h //--------------------------------------------------------------------------- class TForm1 : public TForm { __published: private: public: int x; // << - deklaracja zmiennej typu int __fastcall TForm1(TComponent* Owner); }; //--------------------------------------------------------------------------- |
Czy tym razem coś się zmieniło, otóż tak naprawdę niewiele. Zmienna x wciąż przynależy do klasy TForm1 i może być przez nią wykorzystywana, jednak tym razem będzie widoczna dla innych klas projektu i będzie mogła być przez nie wykorzystana.
Rozważmy taką sytuację, utworzyliśmy dwa formularze Form1 i Form2, w pliku nagłówkowym Unit1.h, w sekcji public zadeklarowaliśmy zmienną x typu int i teraz chcemy przypisać jej jakąś wartość, ale w zdarzeniu OnCreate dla formularza Form2. Żeby to było możliwe, formularz Form2 będący obiektem klasy TForm2 zadeklarowanej w pliku nagłówkowym Uni2.h, musi zostać powiadomiony o istnieniu klasy TForm1. Dlaczego? Dlatego, że chcemy wykorzystać zmienną x należącą do klasy TForm1. Żeby powiadomić klasę TForm2 o istnieniu klasy TForm1 wystarczy podać nazwę pliku zawierającego deklarację klasy TForm1, robimy to w pliku źródłowym Unit2.cpp w sekcji
include:
// Plik źródłowy np. Unit2.cpp //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit2.h" #include "Unit1.h" // << - powiadomienie pliku Unit2.cpp o istnieniu pliku Unit1.h //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm2 *Form2; //--------------------------------------------------------------------------- __fastcall TForm2::TForm2(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- |
Jak widać cała sztuka polega na tym, żeby jeden plik wiedział o istnieniu drugiego. Proszę zwrócić uwagę na deklarację: #include "Unit2,h". Otwórz w ten sposób plik Unit2.cpp zostaje powiadomiony o istnieniu pliku Unit2.h.
A teraz jak wykorzystać zmienną x w zdarzeniu OnCreate formularza Form2:
// Plik źródłowy np. Unit2.cpp //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit2.h" #include "Unit1.h" // << - powiadomienie pliku Unit2.cpp o istnieniu pliku Unit1.h //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm2 *Form2; //--------------------------------------------------------------------------- __fastcall TForm2::TForm2(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm2::FormCreate(TObject *Sender) { Form1->x = 10; } //--------------------------------------------------------------------------- |
Tak więc, jak widać cała filozofia polega na tym, żeby pokazać klasie TForm2 gdzie znajduje się zmienna x. Jako adresu używamy nazwy formularza Form1 i tutaj istnieje pewna, z pozoru niezrozumiała nieścisłość. Dlaczego jako wskaźnika adresu użyłem nazwy formularza a nie nazwy klasy. Otóż klasa TForm1 nie jest obiektem, to właśnie Form1 jest obiektem klasy TForm1, a zatem wszystko to co zostanie zadeklarowane i zdefiniowane wewnątrz klasy TFrom1 należy również do obiektu Form1.
Co się stanie jeżeli zmienię nazwę formularza Form1 na MyForm. Zmieni się również nazwa klasy. Teraz nie będzie to już klasa TForm1 tylko TMyForm, a obiekt tej klasy to właśnie MyForm, żeby teraz wykorzystać zmienną x przez obiekt Form2, trzeba zmienić nazwę adresu:
// Plik źródłowy np. Unit2.cpp //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit2.h" #include "Unit1.h" // << - powiadomienie pliku Unit2.cpp o istnieniu pliku Unit1.h //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm2 *Form2; //--------------------------------------------------------------------------- __fastcall TForm2::TForm2(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm2::FormCreate(TObject *Sender) { MyForm->x = 10; } //--------------------------------------------------------------------------- |
To jest już chyba zrozumiałe.
Co się jednak zmieni jeżeli zmienna x zostanie zadeklarowana w pliku nagłówkowym Unit1.h, ale poza klasą TForm1:
// Plik źródłowy np. Unit1.h //--------------------------------------------------------------------------- int x; // << - deklaracja zmiennej typu int class TForm1 : public TForm |
Żeby teraz wykorzystać zmienną x w pliku Unit2.cpp, trzeba również powiadomić tenże plik o istnieniu pliku Unit1.h, ale zmiennej x nie trzeba już adresować:
// Plik źródłowy np. Unit2.cpp //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit2.h" #include "Unit1.h" // << - powiadomienie pliku Unit2.cpp o istnieniu pliku Unit1.h //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm2 *Form2; //--------------------------------------------------------------------------- __fastcall TForm2::TForm2(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm2::FormCreate(TObject *Sender) { x = 10; } //--------------------------------------------------------------------------- |
Tego sposobu nie polecam, może wystąpić niebezpieczeństwo w pewnych sytuacjach.
Właściwie taką zmienną nie przynależącą do żadnej klasy można również zadeklarować wewnątrz pliku źródłowego, np. Unit1.cpp:
// Plik źródłowy np. Unit1.cpp //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; int x; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- |
W takiej sytuacji trzeba będzie dokonać również zmian wewnątrz pliku Unit2.cpp:
// Plik źródłowy np. Unit2.cpp //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit2.h" #include "Unit1.cpp" // << - powiadomienie pliku Unit2.cpp o istnieniu pliku Unit1.cpp //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm2 *Form2; //--------------------------------------------------------------------------- __fastcall TForm2::TForm2(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm2::FormCreate(TObject *Sender) { x = 10; } //--------------------------------------------------------------------------- |
Tego sposobu również nie polecam.
Przekazywanie funkcji z jednego formularza do drugiego nie różni się wielce od tych samych operacji na zmiennych, więc nie będę opisywał wszystkiego od początku, lecz skupię się na różnicach. Załóżmy, że w pliku nagłówkowym Unit1.h deklarujemy w sekcji public jakąś funkcję, ja ją nazwę MyFunction:
// Plik źródłowy np. Unit1.h //--------------------------------------------------------------------------- class TForm1 : public TForm { __published: private: public: void __fastcall MyFunction(String Text); // << - deklaracja funkcji __fastcall TForm1(TComponent* Owner); }; //--------------------------------------------------------------------------- |
Jak zapewne wszyscy wiedzą funkcja, która zostanie zadeklarowana musi być zdefiniowana, dlatego też w pliku źródłowym Unit1.cpp umieszczamy definicję funkcji MyFunction:
// Plik źródłowy np. Unit1.cpp //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::MyFunction(String Text) { ShowMessage(Text); } //--------------------------------------------------------------------------- |
Żeby wykorzystać taką funkcję w pliku nagłówkowym Unit2.cpp trzeba postąpić tak samo jak w przypadku zmiennej:
// Plik źródłowy np. Unit2.cpp //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit2.h" #include "Unit1.h" // << - powiadomienie pliku Unit2.cpp o istnieniu pliku Unit1.h //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm2 *Form2; //--------------------------------------------------------------------------- __fastcall TForm2::TForm2(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm2::Button1Click(TObject *Sender) { Form1->MyFunction("Ta funkcja wyświetla komunikat."); } //--------------------------------------------------------------------------- |
I to w zasadzie cała filozofia. Rozważmy jednak sytuację, w której funkcja nie jest deklarowana w pliku nagłówkowym, lecz jest od razu definiowana w pliku źródłowym Unit1.cpp:
// Plik źródłowy np. Unit1.cpp //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall MyFunction(String Text) { ShowMessage(Text); } //--------------------------------------------------------------------------- |
Tak zdefiniowana funkcja nie przynależy do żadnej klasy, żeby ją wykorzystać w pliku Unit1.cpp wystarczy zrobić tak:
// Plik źródłowy np. Unit1.cpp //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall MyFunction(String Text) { ShowMessage(Text); } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { MyFunction("Ta funkcja wyświetla komunikat."); } //--------------------------------------------------------------------------- |
Bardzo proste. Równie łatwo jest ją wykorzystać w pliku Unit2.cpp, wystarczy tylko dokonać drobnych zmian, tym razem w sekcji include importujemy plik Unit1.cpp i nie adresujemy wywołania funkcji:
// Plik źródłowy np. Unit2.cpp //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit2.h" #include "Unit1.cpp" // << - powiadomienie pliku Unit2.cpp o istnieniu pliku Unit1.cpp //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm2 *Form2; //--------------------------------------------------------------------------- __fastcall TForm2::TForm2(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm2::Button1Click(TObject *Sender) { MyFunction("Ta funkcja wyświetla komunikat."); } //--------------------------------------------------------------------------- |
Co jednak gdybyśmy zdecydowali się umieścić w pliku nagłówkowym deklarację funkcji MyFunction poza deklaracją klasy, w takiej sytuacji, by funkcja mogła być dostępna dla wszystkich klas w projekcie, jej deklaracja musi być poprzedzona słowem kluczowym extern:
// Plik źródłowy np. Unit1.h //--------------------------------------------------------------------------- #ifndef Unit1H #define Unit1H //--------------------------------------------------------------------------- #include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> //--------------------------------------------------------------------------- extern void __fastcall MyFunction(String Text); class TForm1 : public TForm { __published: // IDE-managed Components private: // User declarations public: // User declarations __fastcall TForm1(TComponent* Owner); }; //--------------------------------------------------------------------------- extern PACKAGE TForm1 *Form1; //--------------------------------------------------------------------------- #endif |
Definicja funkcji w pliku źródłowym Unit1.cpp będzie wyglądała następująco:
// Plik źródłowy np. Unit1.cpp //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall MyFunction(String Text) { ShowMessage(Text); } //--------------------------------------------------------------------------- |
Żeby ją wykorzystać w pliku źródłowym Unit2.cpp trzeba w sekcji include zaimportować pliku Unit1.h, bo to właśnie w tym pliku znajduje się deklaracja tej funkcji. Funkcję wywołuje się bez adresowania:
// Plik źródłowy np. Unit2.cpp //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit2.h" #include "Unit1.h" // << - powiadomienie pliku Unit2.cpp o istnieniu pliku Unit1.h //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm2 *Form2; //--------------------------------------------------------------------------- __fastcall TForm2::TForm2(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm2::Button1Click(TObject *Sender) { MyFunction("Ta funkcja wyświetla komunikat."); } //--------------------------------------------------------------------------- |
I to by było już wszystko na ten temat.
Wszelkie pytania proszę kierować na forum, lub na moją skrzynkę: cyfrowy.baron@wp.pl.
Opracował: Cyfrowy Baron.