Umieszczanie programu w programie, czyli coś w rodzaju instalatora.

     Ostatnio ktoś pytał mnie jak można umieścić jeden program w drugim programie, wtedy nie znałem odpowiedzi, ale nie dałem za wygraną i wciąż szukałem jakieś sposobu, aż w końcu znalazłem. Ta porada jest o tym jak umieścić jeden program w zasobach innego programu i może być wykorzystana do tworzenia wersji instalacyjnych programu. Nie będę się tutaj zagłębiał w szczegóły, ponieważ metoda, którą chcę tutaj zaprezentować jest połączeniem metod zawartych w dwóch poradach: umieszczanie grafiki w formacie JPEG w zasobach programu i efekt rozjaśnienie obrazka, więc jeżeli ktoś nie zrozumie jak działa zaprezentowany poniżej mechanizm to proponuję zapoznać się dodatkowo z wymienionymi wyżej poradami.
    Zaczynamy od tego, że tworzymy np. za pomocą notatnika dwa pliki, nazwy plików są dowolne, jednak muszą posiadać odpowiednie rozszerzenia. Tak więc tworzymy pierwszy plik, który nazwałem Application.rh, rozszerzenie RH ma tutaj istotne znaczenie i nie można go zmienić, w tym pliku umieszczamy następujące instrukcje:

Plik: Application.rh

#ifndef APPLICATION_RH
#define APPLICATION_RH

#define ID_APPL 1000
#endif

Teraz tworzymy drugi plik z rozszerzeniem *.rc, np. Application.rc, ten plik musi zawierać pewne odwołania do pliku Application.rh i wygląda następująco:

Plik: Application.rc

#include "application.rh"

ID_APPL RCDATA "MyProgram.exe"

Gdy plik Application.rc jest już gotowy, trzeba go skompilować do formatu Application.res, czyli właściwego pliku zasobów. W tym celu uruchamiamy konsolę (DOS) i w linii komend wpisujemy polecenie:

brcc32 application.rc application.res

Jeżeli nie popełniono żadnego błędu powinien zostać utworzony plik Application.res, tak utworzony plik należy włączyć do projektu poprzez menu Project | Add to Project...

Skoro plik zasobów został włączony do projektu, przystępujemy do kodowania. Przechodzimy do pliku źródłowego i w sekcji include importujemy dwa pliki: memory.h i utworzony wcześniej plik Application.rh:

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

#include "Unit1.h"
#include <memory>
#include "application.rh"

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

Teraz utworzymy funkcję ExtractExe (nazwa jest dowolna), która będzie pobierała dwa argumenty, pierwszy to identyfikator zasobu natomiast drugi to nazwa pliku do którego ma być wypakowany zasób:

// Plik źródłowy np. Unit1.cpp
// PIERWSZY PRZYKŁAD.

//--------------------------------
void __fastcall ExtractExe(unsigned short ID, AnsiString FileName)
{
 HRSRC rsrc = FindResource(HInstance, MAKEINTRESOURCE(ID), RT_RCDATA);
 if(!rsrc)
 {
  Application->MessageBox("Nie można przeprowadzić operacji. Taki zasób nie istnieje!",
                          "BŁĄD!", MB_OK | MB_ICONSTOP);
  return;
 }

 DWORD Size = SizeofResource(HInstance, rsrc);
 HGLOBAL MemoryHandle = LoadResource(HInstance, rsrc);

 if(MemoryHandle == NULL) return;

 BYTE *MemPtr = (BYTE *)LockResource(MemoryHandle);

 std::auto_ptr<TMemoryStream>stream(new TMemoryStream);
 stream->Write(MemPtr, Size);
 stream->Position = 0;

 TMemoryStream *Ms = new TMemoryStream;
 Ms->Position = 0;
 Ms->LoadFromStream(stream.get());
 Ms->Position = 0;
 Ms->SaveToFile(FileName);
 Ms->Free();
}
//--------------------------------

Można też krócej, bez tworzenia pośredniego obiektu typu TMemoryStream:

// Plik źródłowy np. Unit1.cpp
// DRUGI PRZYKŁAD.

//--------------------------------
void __fastcall ExtractExe(unsigned short ID, AnsiString FileName)
{
 HRSRC rsrc = FindResource(HInstance, MAKEINTRESOURCE(ID), RT_RCDATA);
 if(!rsrc)
 {
  Application->MessageBox("Nie można przeprowadzić operacji. Taki zasób nie istnieje!",
                          "BŁĄD!", MB_OK | MB_ICONSTOP);
  return;
 }

 DWORD Size = SizeofResource(HInstance, rsrc);
 HGLOBAL MemoryHandle = LoadResource(HInstance, rsrc);

 if(MemoryHandle == NULL) return;

 BYTE *MemPtr = (BYTE *)LockResource(MemoryHandle);

 std::auto_ptr<TMemoryStream>stream(new TMemoryStream);
 stream->Write(MemPtr, Size);
 stream->Position = 0;
 stream->SaveToFile(FileName); // <----
}
//--------------------------------

Tylko, że w tym drugim przykładzie w pewnych sytuacjach występują problemy i nie bardzo wiem z czego to wynika.
Tak utworzoną funkcję można wywołać w dowolnym zdarzeniu np. OnClick dla przycisku Button1:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 ExtractExe(ID_APPL, "C:\\Wypakowane.exe");
}
//--------------------------------

    To już prawie wszystko na ten temat, na zakończenie zauważę tylko, że funkcja ExtractExe nie posiada deklaracji w pliku nagłówkowym (np. Unit1.h) więc kolejność zapisu funkcji i zdarzeń ją wywołujących jest istotna, czyli najpierw powinno się umieścić w pliku źródłowym funkcję, a dopiero pod nią można umieścić zdarzenie w którym ta funkcja jest wywoływane, w przeciwnym razie zdarzenie nie będzie "widziało" funkcji, np:

ŻLE!

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 ExtractExe(ID_APPL, "C:\\Wypakowane.exe");
}
//--------------------------------
void __fastcall ExtractExe(unsigned short ID, AnsiString FileName)
{
 // tutaj znajdują się instrukcje...
}

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


DOBRZE!

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall ExtractExe(unsigned short ID, AnsiString FileName)
{
 // tutaj znajdują się instrukcje...
}
//--------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
 ExtractExe(ID_APPL, "C:\\Wypakowane.exe");
}
//--------------------------------

I ostatnia uwaga, ten sposób umieszczania pliku w zasobach może być stosowany dla wszystkich rodzajów plików, czyli można utworzyć program instalacyjny zawierający wszystkie pliki wchodzące w skład dowolnej aplikacji, zarówno włączanie jak i wydobywanie zasobów będzie identyczne dla każdego rodzaju pliku.

...powrót do menu. 

Wygląd kontrolek w Windows XP. Jak umieścić manifest w zasobach programu.

    W poradzie jak nadać w Windows XP kontrolkom odpowiedni wygląd został przedstawiony sposób polegający na dołączaniu do programu pliku z manifestem. Wadą tej metody jest to, że manifest musi być zawsze dołączany do programu. Istnieje jednak sposób na włączenie manifestu bezpośrednio w zasoby programu, wtedy będzie on zawsze obecny w programie i w przypadku uruchomienia programu w systemie Windows XP, jego kontrolki będą wyglądały jak potrzeba.
Najpierw należy napisać manifest, powtórzę jeszcze raz jego treść:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
        <assemblyIdentity
         version="1.0.0.0"
         processorArchitecture="x86"
         name="MyProgram"
         type="win32"
        />
<description>Archiwizator</description>
    <dependency>
        <dependentAssembly>
                <assemblyIdentity
                 type="win32"
                 name="Microsoft.Windows.Common-Controls"
                 version="6.0.0.0"
                 processorArchitecture="x86"
                 publicKeyToken="6595b64144ccf1df"
                 language="*"
                />
        </dependentAssembly>
    </dependency>
</assembly>
 

Można też spróbować z takim manifestem, który będzie o tyle lepszy, że jeśli zmienimy nazwę pliku zawierającego ten manifest to wciąż będzie on działał prawidłowo:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
        <assemblyIdentity
         type="win32"
         name="DelphiApplication"
         version="1.0.0.0"
         processorArchitecture="*"/>
    <dependency>
        <dependentAssembly>
                <assemblyIdentity
                 type="win32"
                 name="Microsoft.Windows.Common-Controls"
                 version="6.0.0.0"
                 publicKeyToken="6595b64144ccf1df"
                 processorArchitecture="*"/>
        </dependentAssembly>
    </dependency>
</assembly>
 

W miejscu zaznaczonym na zielono wpisujemy nazwę naszego programu, a następnie zapisujemy manifest pod taką nazwą jaką nosi nasz program (bez rozszerzenia) i z rozszerzeniem *,manifest. Jeżeli więc nasz program nosi nazwę MyProgram.exe to plik z manifestem musi nosić nazwę MyProgram.manifest. Manifest zapisujemy w katalogu z programem.
Następnie otwieramy notatnik i tworzymy plik typu RC, wprowadzamy do niego następujący kod:


1    24    "MyProgram.manifest"
 

Elementy zaznaczone na zielono są niezmienne i muszą być dokładnie takie jak podałem, natomiast element zaznaczony na żółto to nazwa naszego manifestu. Plik RC zapisujemy pod dowolną nazwą, ale koniecznie z rozszerzeniem *.rc, np. WinXP.rc. Gdy manifest i plik RC są juź gotowe należy włączyć plik WinXP.rc do programu za pomocą menu Project | Add to Project...

I to już wszystko, jeżeli nie popełniono błędu to po skompilowaniu programu wszystkie jego kontrolki powinny wyglądać tak jak wyglądają w środowisku Windows XP.

...powrót do menu. 

Tworzenie tablicy łańcuchów znaków i umieszczenie jej w zasobach programu.

    Pisząc programy cały czas wykorzystujemy łańcuchy znaków, czyli inaczej mówiąc zmienne typu AnsiString i String. Używamy ich przy nadawaniu nazw etykietom, np: Label1->Caption = "Nazwa etykiety", czy też wywołując jakiś komunikat, np: ShowMessage("Treść komunikatu."), to oczywiście nie wyczerpuje wszystkich przypadków gdy korzystamy z tego typu zmiennych.
Istnieje możliwość przygotowania sobie wcześniej możliwych do wykorzystania w programie treści i wywoływania ich tylko poprzez podanie odpowiedniego identyfikatora. Dokładniej rzecz ujmując chodzi mi o przygotowanie specjalnego pliku z tekstem i włączenie go do zasobów programu. Przygotowanie takiego pliku opiera się dokładnie na tych samych zasadach jakie były wykorzystywane w np. poradzie umieszczanie programu w programie, czyli coś w rodzaju instalatora, dlatego nie będę się teraz zagłębiał w szczegóły, a jedynie pokażę krok po kroku jak to zrobić.
    Zaczynamy od przygotowania sobie pliku o dowolnej nazwie z rozszerzeniem RH, np. Messages.rh i umieszczamy w nim następujący kod:

Plik: Messages.rh

#ifndef MESSAGES_RH
#define MESSAGES_RH

#define ID_MSG1 (100)
#define ID_MSG2 (101)
#define ID_MSG3 (103)


#endif

Proszę zwrócić uwagę na kod zaznaczony na żółto jest to dokładnie nazwa pliku Messages.rh jednak zapisana wielkimi literami, a kropka została zastąpiona dolną kreską, czyli MESSAGES_RH. Kolejna sprawa to kod zaznaczony na zielono, słowo kluczowe #define zawsze pozostaje takie samo, natomiast to co po nim następuje to są identyfikatory treści jakie będą umieszczane w drugim pliku, który za chwile utworzymy. Identyfikatory muszą się różnić między sobą, jednak ich nazwa jest dowolna, ale musi być jednoczęściowa, po identyfikatorach występują liczby i każdy identyfikator musi posiadać własną wartość liczbową, czyli różną od pozostałych. Można umieścić dowolną liczbę identyfikatorów.
    Gdy plik Messages.rh jest już gotowy przygotowujemy drugi plik z rozszerzeniem RC np. Messages.rc i umieszczamy w nim kod następującej treści:

Plik: Messages.rc

#include "messages.rh"

STRINGTABLE
BEGIN

     ID_MSG1 "Pierwszy Komunikat";
     ID_MSG2 "Drugi komunikat";
     ID_MSG3 "Nazwa etykiety";
END

    Na żółto została zaznaczona instrukcja, która jest w istocie tylko odwołaniem do pliku Messages.rh. Na pomarańczowo zostały zaznaczone słowa kluczowe, które zawsze pozostają niezmienne, czyli tak musi być. Na zielono zostały zaznaczone identyfikatory i są to dokładnie te same identyfikatory, które zostały użyte w pliku Messages.rh i inaczej być nie może. Na niebiesko zostały zaznaczone łańcuchy znaków i jak widać każdy posiada własny identyfikator, jest ujęty w cudzysłów i zakończony średnikiem.
    Jeżeli obydwa pliki są gotowe należy włączyć do projektu za pomocą menu Project | Add to Project... plik RC, czyli w tym przypadku chodzi o plik Messages.rc. Następnie umieszczamy w pliku źródłowym w sekcji include odwołanie do pliku RH, czyli w tym przypadku do pliku Messages.rh:

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

#include "Unit1.h"
#include "Messages.rh"

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

Tak włączone teksty do zasobów programu można wykorzystywać w dowolnym momencie i w dowolnym zdarzeniu odwołując się tylko do identyfikatora wybranej treści:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Label1->Caption = AnsiString::LoadStr(ID_MSG3);

 ShowMessage(AnsiString::LoadStr(ID_MSG1));

 char buf[255];
 ::LoadString(HInstance, ID_MSG2, buf, 255);
 Application->MessageBox(buf, "Komunikat", MB_OK | MB_ICONINFORMATION);

 Application->MessageBox(AnsiString::LoadStr(ID_MSG1).c_str(), "Komunikat", MB_OK | MB_ICONINFORMATION);
}
//--------------------------------

...powrót do menu. 

Umieszczanie plików tekstowych w zasobach programu.

    Umieszczanie plików tekstowych w zasobach programu nie różni się wiele od umieszczania innych zasobów opisanych w poprzednich poradach. Dlatego nie będę znów wszystkiego dokładnie opisywał, a skupię się jedynie na istotnych szczegółach.
Przygotowujemy sobie dwa pliki jeden RH i jeden RC, np. Zasoby.rh i Zasoby.rc:

Plik: Zasoby.rh

#ifndef ZASOBY_RH
#define ZASOBY_RH

#define ID_TXT1 1000
#define ID_TXT2 1001
#define ID_TXT3 1002
#define ID_TXT4 1003

#endif


Plik: Zasoby.rc

#include "zasoby.rh"

ID_TXT1 RCDATA "Plik1.txt"
ID_TXT2 RCDATA "Plik2.txt"
ID_TXT3 RCDATA "Plik3.txt"
ID_TXT4 RCDATA "Plik4.txt"

Jak widać w przykładzie zostały włączone w zasoby cztery pliki tekstowe.
W pliku źródłowym w sekcji include włączamy plik RH:

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

#include "Unit1.h"
#include "Zasoby.rh"

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

Włączamy do projektu za pomocą menu Project | Add to Project... plik RC. Zasoby są już gotowe do wykorzystania i można je wczytywać do takich obiektów jak TMemo, TRichEdit, TStringList itp:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
TMemoryStream *LoadTxtFromResource(unsigned short ID)
{
 HRSRC rsrc = FindResource(HInstance, MAKEINTRESOURCE(ID), RT_RCDATA);
 if(!rsrc)
 {
  Application->MessageBox("Nie można przeprowadzić operacji. Taki zasób nie istnieje!", "BŁĄD!", MB_OK | MB_ICONSTOP);
  return 0;
 }

 DWORD Size = SizeofResource(HInstance, rsrc);
 HGLOBAL MemoryHandle = LoadResource(HInstance, rsrc);

 if(MemoryHandle == NULL) return 0;

 BYTE *MemPtr = (BYTE *)LockResource(MemoryHandle);

 std::auto_ptr<TMemoryStream>stream(new TMemoryStream);
 stream->Write(MemPtr, Size);
 stream->Position = 0;

 TMemoryStream *Ms = new TMemoryStream;
 Ms->Position = 0;
 Ms->LoadFromStream(stream.get());
 
return Ms;
}
//---------------------------------
void __fastcall TForm1::Button5Click(TObject *Sender)
{
 Memo1->Lines->LoadFromStream(LoadTxtFromResource(ID_TXT1));

 RichEdit1->Lines->LoadFromStream(LoadTxtFromResource(ID_TXT2));

 TStringList *Lista = new TStringList;
 Lista->LoadFromStream(LoadTxtFromResource(ID_TXT3));
}
//--------------------------------

W przypadku plików formatowanych, np. RTF postępujemy dokładnie tak samo. Właściwie format pliku jest bez znaczenia ponieważ w zasobach można umieszczać dowolne pliki, nie tylko tekstowe, więc podana funkcja LoadTxtFromResource (nazwa jest dowolna) załaduje tekst do dowolnego obiektu (posiadającego funkcję LoadFromStream), załaduje nawet plik binarny i będzie go traktowała jak plik tekstowy. Jeżeli jednak obiekt do którego zostanie załadowany plik formatowany, np. RTF będzie rozpoznawał formatowanie pliku to zostanie on wyświetlony ze wszystkimi właściwościami, takim obiektem jest np. TRichEdit. Dodam od razu, że o ile można umieścić w zasobach pliki w formacie HTML to nie ma sposobu na ich wczytanie do obiektów THTML i TWebBrowser ponieważ nie posiadają one funkcji odczytującej zawartość pamięci, czyli chodzi tutaj o funkcję LoadFromStream.

...powrót do menu. 

Dynamiczne tworzenie obiektów.

    To co jest proste dla jednych, innym sprawia nie lada  problemy. Z całą pewnością, tworzenie obiektów dynamicznych jest takim problemem. Obiekty dynamiczne są tworzone w oparciu o już istniejące klasy, tego typu obiekty powstają w trakcie działania programu, czyli np. podczas uruchomienia programu obiektu jeszcze niema, ale gdy zostanie wywołane jakieś zdarzenie, obiekt zostaje utworzony - prościej nie da się już tego wyjaśnić. Tworząc obiekty dynamicznie, najpierw należy stworzyć deklarację obiektu, a potem tworzy się definicję. Wyjaśnię to wszystko na przykładzie dynamicznie tworzonego obiektu typu TButton, tworzony obiekt będzie nosił nazwę MyButton.

    Więc najpierw tworzymy deklarację obiektu. Deklarację obiektu można utworzyć wewnątrz sekcji private lub public, w pliku nagłówkowym, w zależności od tego jak obiekt ma być dostępny. Jak wiadomo przy deklaracji w sekcji private obiekt będzie dostępny tylko w obrębie jednostki, w której jest deklarowany, np. Unit1. Jeżeli umieścimy deklarację obiektu w sekcji public, to taki obiekt będzie dostępny w obrębie całego programu, dla wszystkich jednostek występujących w programie, należy tylko pamiętać o prawidłowym adresowaniu takiego obiektu, dla innych jednostek niż ta w której jest umieszczona deklaracja obiektu. O adresowaniu obiektów można przeczytać więcej w artykule: przekazywanie obiektów i funkcji pomiędzy formularzami.

// Przykład tworzenia deklaracji obiektu w sekcji private
// Plik źródłowy np. Unit1.h

//--------------------------------
__published:
private:
      
 TButton *MyButton;

public:

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

    Po umieszczeniu deklaracji dynamicznie tworzonego obiektu w pliku nagłówkowym, należy utworzyć definicję obiektu w pliku źródłowym i dodam tutaj, że w pliku nagłówkowym nigdy nie można tworzyć definicji jakiegokolwiek obiektu, funkcji czy klasy, jest to po prostu niedopuszczalne i nie zadziała. Definicję obiektu, który został zadeklarowany w pliku nagłówkowym (bo można jeszcze inaczej, ale o tym dalej) tworzymy wewnątrz konstruktora klasy, wtedy taki obiekt zostanie utworzony w chwili uruchomienia programu:

// Przykład tworzenia definicji obiektu wewnątrz konstruktora klasy
// Plik źródłowy np. Unit1.cpp

//--------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
  MyButton = new TButton(this);
}
//--------------------------------

    Obiekt już istnieje, widać tutaj parametr this, przypisuje on obiekt MyButton do formularza, ponieważ wszystkie muszą być do czegoś przypisane, parametr this przypisuje go do formularz na którego jednostce jest tworzony nowy obiekt, czyli w przykładzie do formularza Form1.
    Definicję obiektu można również tworzyć w każdym dowolnym zdarzeniu innego istniejącego już obiektu, w przykładzie niżej utworzę obiekt MyButton w zdarzeniu OnClick dla przycisku Button1:

// Przykład tworzenia definicji obiektu wewnątrz zdarzenie OnClick
// Plik źródłowy np. Unit1.cpp

//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  MyButton = new TButton(this);
}
//--------------------------------

    Obiekty tworzone dynamicznie można przypisywać do innych dowolnych obiektów, które są już przypisane do formularza, a wszystkie obiekty - komponenty, umieszczone na formularzu są do niego przypisane. Pomimo, że obiekt został już zdefiniowany i przypisany do formularza, to nie będzie jeszcze widoczny, ponieważ trzeba przypisać mu rodzica, a rodzicem dla tak tworzonego obiektu może formularz, lub każdy inny obiekt "widzialny", czyli taki który jest reprezentowany graficznie na formularzu:

// Przykład przypisywania formularza jako rodzica dla obiektu MyButton
// Plik źródłowy np. Unit1.cpp

//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  MyButton = new TButton(this);
  MyButton->Parent = this;      // można by też napisać: MyButton->Parent = Form1;
}
//--------------------------------


// Przykład przypisywania obiektu Panel1 jako rodzica dla obiektu MyButton
// Plik źródłowy np. Unit1.cpp

//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  MyButton = new TButton(this);
  MyButton->Parent = Panel1;
}
//--------------------------------

    Teraz obiekt MyButton będzie już widoczny na formularzu lub na obiekcie Panel1, jednak pojawi się w pozycji domyślnej, czyli Left = 0 i Top = 0. Można to oczywiście zmienić:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  MyButton = new TButton(this);
  MyButton->Parent = this;
  MyButton->Left = 100;
  MyButton->Top = 50; 

}
//--------------------------------

    W ten sam sposób można ustawiać wszystkie właściwości dynamicznie tworzonego obiektu i nie muszą być one ustawiane tam gdzie jest tworzona definicja, lecz można je ustawiać w dowolnym zdarzeniu i w dowolnym momencie, dopóki obiekt istnieje:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  MyButton = new TButton(this);
  MyButton->Parent = this;
  MyButton->Left = 100;
  MyButton->Top = 50; 
}
//--------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
  MyButton->Width = 90;
  MyButton->Height = 30;

}
//---------------------------------

    Po utworzeniu takiego obiektu nie można go tworzyć ponownie, dopóki nie zostanie zniszczony, w przeciwnym razie program się "zawiesi". Obiekty tworzone dynamicznie można niszczyć w dowolnym momencie i w dowolnym zdarzeniu, jeżeli jednak obiekt ma być dostępny od uruchomienia programu do jego zamknięcia, to taki obiekt należy niszczyć w zdarzeniu OnClose dla formularza na którym dany obiekt został zdefiniowany. Jeżeli zniszczymy obiekt, to nie możemy go już używać ponieważ obiekt już nie istnieje, żeby znów użyć tego obiektu należy go ponownie zdefiniować i ponownie trzeba ustawiać wszystkie jego właściwości, dzieje się tak dlatego, że pomimo iż obiekt już raz był zdefiniowany, potem został zniszczony i ponownie zdefiniowany, to nie jest on już tym samym obiektem, to nowy obiekt budowany w oparciu o tą samą klasę i posiadający tą samą nazwę. W oparciu o jedną klasę można tworzyć wiele obiektów, podobnie jak można tworzyć wiele obiektów w oparciu o różne klasy, ale obowiązuje jedna zasada, każdy obiekt musi występować pod inną nazwą, przy czym obiekt zawsze posiada taką nazwę jaka jest podana w deklaracji i nie można tego zmieniać, chociaż istnieje właściwość Name to nie można jej zmieniać i dotyczy to wszystkich obiektów, nie tylko tych tworzonych dynamicznie, chociaż można oczywiście zmieniać nazwę komponentu, ale to tylko na poziomie tworzenia projektu aplikacji.
    Dynamicznie utworzony obiekt usuwa się za pomocą funkcji delete, przy usuwaniu obiektu należy uważać, ponieważ można usunąć obiekt, który jest w użyciu, lub taki który nam będzie jeszcze potrzebny:

// Przykład usuwania dynamicznie utworzonego obiektu
// Plik źródłowy np. Unit1.cpp

//--------------------------------
void __fastcall TForm1::Form1Close(TObject *Sender, TCloseAction &Action)
{
  delete MyButton;
}
//--------------------------------

    Można deklarować, definiować i usuwać obiekt wewnątrz jednego  zdarzenia, chociaż taka operacja wydaje się nielogiczna, ponieważ obiekt, który zostanie stworzony i zniszczony wewnątrz jednego zdarzenia, tak właściwie nic nie zrobi. Istnieje jednak pewna sytuacja, w której taka operacja ma swoje logiczne uzasadnienie. Sytuacja o której mówię zachodzi gdy chcemy wyświetlić dynamicznie tworzony formularz, np. z komunikatem, ale musi to być formularz modalny, po wyświetleniu komunikatu, można go zniszczyć ponieważ nie jest już potrzebny. W przykładzie utworzę dynamicznie formularz, wyświetlę go na środku ekranu, umieszczę na nim dynamicznie utworzony obiekt typu TLabel w którym będzie znajdowała się treść komunikatu, po wszystkim obydwa obiekty zostaną zniszczone:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  TForm *MyForm = new TForm(this);
  MyForm->Position = poScreenCenter;
  MyForm->BorderStyle = bsDialog;
  MyForm->Width = 200;
  MyForm->Height = 70;

  TLabel *MyLabel = new TLabel(MyForm);
  MyLabel->Parent = MyForm;
  MyLabel->Align = alClient;
  MyLabel->Alignment = taCenter;
  MyLabel->Layout = tlCenter;
  MyLabel->Caption = "Jakiś komunikat";

  MyForm->ShowModal();

  delete MyLabel;
  delete MyForm;
}
//--------------------------------

    Jak widać w przykładzie, obiekt MyLabel został przypisany do obiektu MyForm, jest to jak najbardziej logiczne ponieważ jest on wyświetlany na MyForm. Jedna bardzo ważna uwaga, jeżeli tworzymy więcej niż jeden obiekt dynamiczny i przypisujemy go do innego obiektu dynamicznego, to należy obiekt do którego są przypisane inne obiekty, niszczyć na końcu, w przeciwnym razie wyskoczy błąd. W przykładzie pierwszy niszczony jest obiekt MyLabel ponieważ jest przypisany do obiektu MyForm, który należy zniszczyć na końcu. Okno komunikatu powinno mieć co najmniej przycisk OK, którym by się je zamykało, jednak do taki przycisk również należałoby utworzyć dynamicznie i trzeba byłoby przypisać do niego obsługę zdarzenia OnClick, ale o tym w następnej poradzie.

...powrót do menu. 

Dołączanie zdarzeń do dynamicznie tworzonych obiektów.

    Żeby zrozumieć na czym polega dołączanie zdarzeń do dynamicznie tworzonych obiektów, należy najpierw przeczytać poradę dynamiczne tworzenie obiektów, w przeciwny razie czytanie tej porady nie ma sensu. Tworząc niektóre obiekty dynamiczne będziemy potrzebowali obsługi zdarzeń dla tychże obiektów, Zadanie jest bardzo proste, żeby obsłużyć jakieś zdarzenie należy utworzyć funkcję, która zostanie do tego zdarzenia podłączona i wywołana w tym zdarzeniu. Taka funkcja jest dokładnie tym samym, co zdarzenie w komponentach umieszczanych na formularzu. Podłączenie funkcji do zdarzenie polega na jej przypisaniu do tegoż zdarzenia. Przypisanie należy zrobić razem z definicją funkcji, chociaż można to równie dobrze zrobić w każdym innym dowolnym momencie, można do takiego zdarzenia przypisywać w różnych momentach różne funkcje, funkcją aktualnie obsługującą zdarzenie, będzie ta która została wywołana jako ostatnia.
    W celu dołączenia zdarzenia tworzymy deklarację nowego obiektu w pliku nagłówkowym (nie zawsze musi to być w pliku nagłówkowym, patrz porada dynamiczne tworzenie obiektów), tworzymy również deklarację funkcji:

// Plik źródłowy np. Unit1.h
//--------------------------------
__published:
private:
      
 TButton *MyButton;
        void __fastacll
MyFunction(TObject *Sender);

public:

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

   Następnie w pliku źródłowym tworzymy definicję obiektu i definicję funkcji, przypisujemy również do zdarzenia - w przykładzie OnClick - definiowaną funkcję. Definicję nowego obiektu wraz z przypisaniem funkcji do zdarzenia zrobię w konstruktorze klasy, ale można to zrobić równie dobrze w innym dowolnym momencie (patrz porada dynamiczne tworzenie obiektów):

// Plik źródłowy np. Unit1.cpp
//--------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
  MyButton = new TButton(this);
  MyButton->Parent = this;
  MyButton->OnClick = MyFunction;
}
//--------------------------------
void __fastcall TForm1::MyFunction(TObject *Sender)
{
  // tutaj wpisujemy jakieś instrukcje, które zostaną wywołane w zdarzeniu OnClick
}
//--------------------------------

    W funkcji MyFunction, jako parametr przekazałem odwołanie do obiektu TObject i można przekazywać do tak tworzonej funkcji tylko typowe dla niej argumenty.

...powrót do menu. 

Wyświetlanie ikon skojarzonych z plikiem lub folderem.

    Jak wiadomo do każdego rodzaju pliku przypisana jest jakaś ikona, która ten plik reprezentuje, przedstawiona tutaj porada pozwoli wyświetlić w obiekcie typu TImage ikonę przypisaną do konkretnego pliku, w tym celu w sekcji include pliku źródłowego (np. Unit1.cpp) umieszczamy następujący wpis: #include <shellapi.h>, następnie umieszczamy na formularzu obiekty 'Button1', 'Edit1' i 'ImageList1'. Do obiektu Edit1 wprowadzimy ścieżkę dostępu do pliku z którego chcemy pobrać ikonę, Button1 uruchomi pobieranie tej ikony, a obiekt ImageList1 pobierze uchwyt i zwraci index ikony w systemowej ImageList. Przechodzimy do pliku źródłowego i w konstruktorze klasy umieszczamy następującą instrukcję:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
   SHFILEINFO info;
   DWORD ImageHandle = SHGetFileInfo("", 0, &info, sizeof(info),
                                     SHGFI_ICON | SHGFI_SHELLICONSIZE |
                                     SHGFI_SYSICONINDEX);
   if(ImageHandle != 0)
   {
      ImageList1->Handle = ImageHandle;
      ImageList1->ShareImages = true;
   }
}
//---------------------------------

Następnie w zdarzeniu OnClick przycisku Button1 umieszczamy następującą instrukcję:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
   SHFILEINFO info;
   DWORD result = SHGetFileInfo(Edit1->Text.c_str(), 0, &info,
                                sizeof(info), SHGFI_ICON |
                                SHGFI_SHELLICONSIZE | SHGFI_SYSICONINDEX);
   if (result != 0)
      ImageList1->GetIcon(info.iIcon, Image1->Picture->Icon);
   else
      Image1->Picture->Icon->Assign(NULL);
}
//---------------------------------

W ten sposób można wyciągać również ikony z aplikacji, przy czym wyciągana będzie pierwsza ikona na liście, jeżeli chcemy pobrać inną (pod warunkiem, że aplikacja zawiera więcej ikon) wystarczy zmienić parametr info.icon np.:  ImageList1->GetIcon(info.iIcon + 1, Image1->Picture->Icon);

...powrót do menu.