Delphi/Dynamiczne tworzenie komponentów

Z Wikibooks, biblioteki wolnych podręczników.

Dynamiczne tworzenie komponentów[edytuj]

Dotychczas umieszczaliśmy komponenty podczas tworzenia programu (ang. design mode) kładąc je na formularz i programując ich cechy: właściwości oraz metody. Delphi tworzy program, który automatycznie dodaje polecone przez Ciebie komponenty. Drugą możliwość stanowi dodanie komponentów w trakcie wykonywania programu (ang. run-time mode). Takie dodawanie niezadeklarowanych wcześniej w programie komponentów jest nazywane dynamicznym tworzeniem komponentów.

Utwórz nowy projekt. Formularz jest pusty. Skompilowanie lub uruchomienie programu oczywiście spowoduje pokazanie pustego formularza.

Kiedy Delphi tworzy formularz? Każdy komponent ma swoją metodę Create. Jest to konstruktor (ang. constructor) obiektu. Wywołanie polecenia tworzenia formularza wywołuje jego zdarzenie OnCreate, które możesz zaprogramować.

Wybierz formularz, jeśli nie jest wybrany. W Inspektorze Objektu znajdź zdarzenie OnCreate i kliknij je dwukrotnie. Równocześnie OnCreate jest domyślną metodą formularza i możesz po prostu dwukrotnie kliknąć formularz.

W edytorze otworzyło się okienko modułu z wprowadzoną procedurą (metodą) OnCreate:

  procedure TForm1.FormCreate(Sender: TObject);
  begin
  
  end;

Podobnie jak w przypadku OnClick przycisku, Sender zawiera obiekt (ściślej: wskaźnik na ten obiekt), który wydał polecenie utworzenia formularza. Tej zmiennej nie będziemy wykorzystywać w tym przykładzie.

W metodzie OnCreate możesz umieścić dowolny kod programu. Najczęściej to miejsce wykorzystuje się do zainicjowania jakichś zmiennych programu. My wykorzystamy je do dynamicznego utworzenia przycisku (przycisków).

Żeby wygodniej obsługiwać nasz przycisk, dodajmy deklarację zmiennej, np. przycisk:

  procedure TForm1.FormCreate(Sender: TObject);
  var
    przycisk: TButton;
  begin
  
  end;


Zmienna "przycisk" jest wskaźnikiem na pewien (nieistniejący jeszcze) komponent - Button, czyli standardowy przycisk systemu Windows. Aby móc wykorzystywać przyciski w swojej aplikacji przyciski, dodaj do sekcji uses moduł StdCtrls:

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

Zauważ, że wprowadzenie przycisku przez wstawienie komponentu z palety automatycznie dodawało informację o tym module dla Ciebie; teraz musisz o to zadbać sam.

Aby utworzyć przycisk, trzeba wywołać jego konstruktor - metodę Create:

  procedure TForm1.FormCreate(Sender: TObject);
  var
    przycisk: TButton;
  begin
    przycisk:=TButton.Create(self);
  end;

Wyjaśnienia wymagają tu trzy rzeczy:

  • utworzenie dynamiczne komponentu polega na podaniu typu (TButton) i wywołaniu metody (Create). Jest to jeden z nielicznych przypadków w Delphi, kiedy funkcją jest wyrażenie zawierające typ;
  • konstruktor Create przycisku (lub ogólniej: komponentu) wymaga podania właściciela komponentu. Właścicielem tym będzie nasz formularz (to znaczy, że nasz formularz będzie prowadził w jego imieniu komunikację z systemem za pomocą wiadomości - o czym dalej, oraz zajmie się zwolnieniem zajmowanej pamięci przez komponent przy zamykaniu programu);
  • self jest zmienną, która wskazuje na obiekt, którego deklaracja metody dotyczy. Obiektem tym jest obiekt typu TForm1. Wcześniej wspomnieliśmy, że po deklaracji typu, tworzona jest zmienna o nazwie Form1. Jeżeli chcesz, możesz w tym miejscu odwołać się do tej zmiennej, przez TButton.Create(Form1). Jest to trochę nieeleganckie rozwiązanie, gdyż w przypadku, gdybyś miał kilka takich samych formularzy (tego samego typu), wszystkie odnosiłyby się do tego jednego - do Form1. Self powoduje odwołanie się do tego, w którym aktualnie się znajdujemy (który własnie obsługuje tę metodę).


Jeśli chcesz, uruchom teraz program i sprawdź, czy działa. Działa, ale... przycisk się nie pokazał. Dlaczego?

Zapomnieliśmy dodać jeszcze parametrów (właściwości) przycisku. A więc zróbmy to:

  procedure TForm1.FormCreate(Sender: TObject);
  var
    przycisk: TButton;
  begin
    przycisk:=TButton.Create(self);
    przycisk.Left:=10; // pozycja X przycisku na formularzu
    przycisk.Top:=10; // pozycja Y przycisku na formularzu
    przycisk.Width:=140; // szerokość przycisku
    przycisk.Height:=40; // wysokość przycisku
    przycisk.Caption:='Przycisk dynamiczny'; // podpis przycisku
    przycisk.Parent:=self; // rodzic przycisku
    przycisk.Visible:=True; // czy przycisk widoczny?
  end;

(Kolejność opisywania właściwości jest dowolna.)

Zwróć uwagę na dwie ostatnie linie. Pierwsza przypisuje rodzica (parent) przycisku do formularza (zamiast self możesz w tym przypadku wpisać zmienną Form1). Rodzic jest komponentem, który odpowiada za wyświetlanie naszego przycisku. Właściwość Visible (widoczny) jest typu Boolean i określa, czy przycisk jest widoczny czy nie (jesli przycisk jest niewidoczny, to zajmuje przypisaną mu pamięć, ale nie reaguje na zdarzenia wywołane przez użytkownika i sam nie generuje żadnych zdarzeń). Te dwie linie są konieczne do wyświetlenia komponentu i nie zapominaj ich dodawać!

Uruchom teraz program.

Przycisk został dodany dynamicznie: mimo że nie dodawaliśmy przycisku z palety komponentów, wpisaliśmy kod, który dokonał tego samego. Jaki jest sens dodawania dynamicznego przycisków? Zmodyfikuj wspomnianą metodę w następujący sposób:

procedure TForm1.FormCreate(Sender: TObject);
var
i,n: Integer; //usunęliśmy zmienną "przycisk"
begin
n:=10; // liczbę przebiegów pętli możesz umieścić w innym miejscu programu
for i:=1 to n do
with TButton.Create(self) do begin
Width:=140;
Height:=40;
Left:=10;
Top:=10+i*(Height+10); // Height jest wysokością przycisku, opisaną 2 linijki wyżej
Caption:='Przycisk '+IntToStr(i); // IntToStr zamienia liczbę typu Integer na jej postać tekstową, typu string
Parent:=self;
Visible:=True;
end;
end;

Załóżmy, że Twój program ma n różnych wersji językowych, a każdym przyciskiem możesz zmienić wersję językową na inną. Tymczasem ktoś przetłumaczył Twój program i powstała nowa wersja językowa. W ten sposób nie musisz od razu deklarować liczby dostępnych wersji, a jedynie trzymać gdzieś (w zewnętrznym pliku, który odczytasz) liczbę swoich wersji językowych. Utworzysz akurat tyle przycisków, ile potrzeba.

"No, dobrze" - powiesz. - "Ale te przyciski nic nie robią". Zaraz to naprawimy.

Jak już wiesz, każdy przycisk wywołuje zdarzenie OnClick na formularzu (a ściślej: swoim właścicielu). Utwórzmy więc najpierw metodę formularza, która będzie zawierała obsługę przycisków.

Aby dodać metodę do obiektu, musimy ją wpierw zadeklarować. Znajdź w listingu deklarację typu TForm1 i dodaj oznaczoną linijkę:

type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure KlikPrzycisku(Sender: TObject); // dodaj tę linię
private
{ Private declarations }
public
{ Public declarations }
end;

Utworzyliśmy nową metodę typu TForm1 o nazwie KlikPrzycisku. W poprzednich przykładach, gdy definiowaliśmy zdarzenie OnClick, Delphi tworzyło deklarację tej metody za nas. Domyślną nazwą tej metody byłoby Button1Click, ale oczywiście nazwa może być dowolna.

Teraz musimy zaprogramować tę metodę. Dodaj w wolnym miejscu programu tekst tej metody:

procedure TForm1.KlikPrzycisku(Sender: TObject);
begin
Label1.Caption:=TButton(Sender).Caption;
end;

Etykieta Label1 będzie zawierać opis przycisku, który został wciśnięty. Ta etykieta nie istnieje, więc dodaj ją do formularza - przez normalne wklejenie jej z palety komponentów, jak w poprzednich lekcjach.

Zauważ również, że w deklaracji metody wymagane jest podanie, którego typu obiektu dana metoda dotyczy, a więc procedure TForm1.KlikPrzycisku(Sender: TObject) zamiast procedure KlikPrzycisku(Sender: TObject).

Teraz musimy przypisać jeszcze każdemu przyciskowi w jego zdarzeniu OnClick tę metodę formularza:

procedure TForm1.FormCreate(Sender: TObject);
var
i,n: Integer; //usunęliśmy zmienną "przycisk"
begin
n:=10; // ta linia może znajdować się w innym miejscu programu
for i:=1 to n do
with TButton.Create(self) do begin
Width:=140;
Height:=40;
Left:=10;
Top:=10+i*(Height+10);
Caption:='Przycisk '+IntToStr(i);
Parent:=self;
Visible:=True;
OnClick:=@KlikPrzycisku; // dodaj tę linię
end;
end;

W rzeczywistości, OnClick jest właściwością typu proceduralnego. Zauważ, że nie potrzeba podawać parametrów tej metody.

Uruchom teraz program.

Każdy z przycisków otrzymał obsługę tej samej metody w swoim zdarzeniu OnClick. Dopiero w tej metodzie następuje rozróżnienie, który z nich został wciśnięty.

I teraz ciekawostka: zamień w metodzie KlikPrzycisku TButton(Sender).Caption na TButton(Sender).Name i uruchom.

Tak, żaden z przycisków nie ma nazwy! W naszej sytuacji nazwy przycisków nie były do niczego potrzebne. Ale może się zdarzyć, że będziesz używać komponentów, które będzie można rozróżnić tylko po nazwie (np. komponenty typu TListiew). Stąd nadajmy jeszcze nazwy wszystkim przyciskom. W metodzie FormCreate dodaj linię:

(...)
with TButton.Create(self) do begin
Width:=140;
Height:=40;
Name:='przycisk'+IntToStr(i); // nadanie nazwy przyciskowi
(...)

Nazwa komponentu może być dowolna. Nie mogą istnieć dwa komponenty o tej samej nazwie.

Teraz każdy przycisk ma swoją nazwę. Jak ją wykorzystać?

Kliknij dwukrotnie na etykiecie Label1. Wywołasz w ten sposób jej metodę OnClick (możesz również wybrać etykietę, przejść do "Events" w Inspektorze Obiektu i dwukrotnie kliknąć na jej zdarzenie OnClick). Zauważ też, że jej deklaracja automatycznie dodała się w seksji type modułu, co nie ma miejsca przy dynamicznym tworzeniu komponentów.

W metodzie Label1Click wpisz:

procedure TForm1.Label1Click(Sender: TObject);
var
przycisk: TButton;
begin
przycisk:=TButton(self.FindComponent(Label1.Caption));
przycisk.Visible:=False;
end;

Podobnie jak wyżej, zamiast self, które wskazuje na nasz formularz, możesz użyć zmiennej Form1.

Każdy formularz ma metodę FindComponent. Zwraca ona (o ile istnieje) wskaźnik na obiekt o podanej nazwie (nazwa jest typu string). Nasz program ma więc znaleźć komponent, którego nazwa jest zapisana w opisie etykiety Label1. Następnie wynik tej funkcji (metody) będzie zrzutowany na typ TButton i przypisany zmiennej przycisk.

W kolejnej linijce komponent, na który wskazuje zmienna "przycisk" ma zostać schowany (zrobiony niewidocznym).

Uruchom program.

Jeśli od razu klikniesz np. na przycisk 1, to opis etykiety zmieni się na nazwę przycisku. Teraz kliknij etykietę. Wciśnięty uprzednio przycisk, którego nazwę zawiera etykieta, zostanie schowany.

Jeśli uruchomisz program jeszcze raz, zauważysz, że etykieta ma swój opis (Caption) "Label1". Taka sama jest również nazwa tej etykiety. Teraz kliknij ją (nie klikając uprzednio żadnych przycisków), a ona... również się schowa. Dlaczego?

Odpowiedź na to pytanie jest w linijce

przycisk:=TButton(self.FindComponent(Label1.Caption));

Oba obiekty, TButton i TLabel są pochodnymi tego samego przodka, TComponent. Jeżeli wywołasz FindComponent, to oczywiście etykieta o nazwie Label1 zostanie odnaleziona. Następnie, następuje rzutowanie typu TComponent na TButton. Jednak zarówno TButton jak i TLabel mają właściwości Visible, więc w tym wypadku nie ma znaczenia, że zmienna przycisk (typu TButton) tak naprawdę wskazuje na etykietę. Staraj się jednak zawsze rzutować na właściwy typ.

Podsumowanie[edytuj]

W niniejszych przykładach tworzyliśmy dynamicznie komponenty w metodzie obsługującej zdarzenie OnCreate formularza. Pamiętaj, że możesz tak tworzyć komponenty niemal w dowolnym miejscu programu. Tworzenie niewizualnych obiektów może odbywać się tylko w sposób dynamiczny.

Spróbuj napisać program, który po każdym kliknięciu przycisku doda do formularza nową etykietę.

(rozwiązanie)


« Poprzednia lekcjaSpis treściNastępna lekcja »