Jak zmienić atrybuty plików i folderów?

    Było już o odczytywaniu atrybutów pliku za pomocą funkcji FileGetAttr, jeśli chodzi o zmianę atrybutu pliku lub folderu to służy do tego funkcja FileSetAttr i stosuje się ją tak:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  FileSetAttr("nazwa pliku.roz", faReadOnly);
}
//--------------------------------

 

W podanym przykładzie atrybut pliku został zmieniony na tylko do odczytu, więc sprawa wydaje się bardzo prosta, ale taka nie jest ponieważ jeżeli będziemy za każdym razem wywoływać tą funkcję z innym atrybutem to wcześniejszy atrybut zostanie zastąpiony nowym, więc jeżeli chcemy zmienić więcej niż jeden atrybut na raz, to należy to zrobić wewnątrz tego samego zdarzenia:

 

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  FileSetAttr("nazwa pliku.roz", faReadOnly | faHidden); // atrybuty tylko do odczytu i ukryty
}
//--------------------------------

 

    Ta metoda się sprawdza, jednak przed zmianą atrybutu pliku, dobrze jest odczytać atrybut jaki ona aktualnie posiada i zmieniać go lub pozostawić bez zmian a dodać nowy, do realizacji tego zadania najlepiej nadają się komponenty typu TCheckBox, będziemy potrzebowali cztery tego typu obiekty, jak cztery rodzaje atrybutów, posłużymy się również dwoma przyciskami typu TButton z których jeden będzie odczytywał atrybuty pliku, a drugi będzie je zmieniał w zależności od ustawień obiektów TCheckBox, stworzymy również dwie funkcje jedną odczytującą atrybuty pliku i ustawiającą odpowiednio obiekty TCheckBox i drugą funkcję zmieniającą atrybuty pliku w zależności od ustawień obiektów TCheckBox, może wydaje się to trochę skomplikowane, ale takie nie jest:

 

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::ReadAttrib(String FileName)
{
 int attrib = FileGetAttr(FileName);
 if(attrib & faArchive)  CheckBox1->Checked = true; // archiwalny
 if(attrib & faReadOnly) CheckBox2->Checked = true; // tylko do odczytu
 if(attrib & faHidden)   CheckBox3->Checked = true; // ukryty
 if(attrib & faSysFile)  CheckBox4->Checked = true; // systemowy
}
//--------------------------------
void __fastcall TForm1::ChangeAttrib(String FileName)
{
 int i1 = 0, i2 = 0, i3 = 0, i4 = 0;
 if(CheckBox1->Checked) i1 = faArchive;
 if(CheckBox2->Checked) i2 = faReadOnly;
 if(CheckBox3->Checked) i3 = faHidden;
 if(CheckBox4->Checked) i4 = faSysFile;

 FileSetAttr(FileName, i1 | i2 | i3 | i4);
}
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  ReadAttrib("nazwa pliku.roz");
}
//--------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
  ChangeAttrib("nazwa pliku.roz");
}
//--------------------------------

No i nic dodać, nic ująć, to po prostu działa i jest banalnie proste. Atrybuty folderów zmienia się dokładnie tak samo, czyli traktuje się je jako pliki.

Atrybuty folderów i plików kasuje się przekazując funkcji FileSetAttr jako drugi argument wartość NULL.

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  FileSetAttr("nazwa pliku.roz", NULL);
}
//--------------------------------

...powrót do menu. 

   Pobieranie informacji o pamięci.

W celu pobrania informacji o pamięci RAM, pliku wymiany i pamięci wirtualnej należy posłużyć się klasą TMemoryStatus, klasa ta udostępnia osiem parametrów dotyczących pamięci komputera. Na przykładzie pobierania całkowitej wielkości pamięci RAM pokażę jak stosować tą klasę:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 TMemoryStatus *MS = new TMemoryStatus;
 MS->dwLength = sizeof(MEMORYSTATUS); // tego można nie stosować.
 GlobalMemoryStatus(MS);
 Label6->Caption = FormatFloat("Całkowita pamięć: #,### KB", MS->dwTotalPhys / 1024);
 delete MS;
}
//--------------------------------

Jak widać stosowanie klasy jest bardzo proste, niżej przedstawiam wszystkie funkcje tej klasy:

...powrót do menu. 

    Pobieranie informacji o dyskach.

Istnieje szereg gotowych funkcji służących do pobierania istotnych informacji na temat dysków zainstalowanych w komputerze. A oto co udało mi się zebrać:

Odczytywanie litery dysku z którego uruchomiono program:

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

Odczytywanie nazwy dysku:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 char aName[255] = "";
 char aFileSystem[255] = "";
 DWORD dwTemp;
 if(GetVolumeInformation("c:\\", aName, sizeof(aName), NULL, &dwTemp, &dwTemp, aFileSystem, sizeof(aFileSystem)))
 {
  Label1->Caption = aName;
 }
}
//--------------------------------

Odczytywanie systemu plików:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 char aName[255] = "";
 char aFileSystem[255] = "";
 DWORD dwTemp;
 if(GetVolumeInformation("c:\\", aName, sizeof(aName), NULL, &dwTemp, &dwTemp, aFileSystem, sizeof(aFileSystem)))
 {
  Label1->Caption = aFileSystem;
 }
}
//--------------------------------

Odczytywanie numeru seryjnego dysku (chyba):

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 char aName[255] = "";
 char aFileSystem[255] = "";
 DWORD dwTemp;
 unsigned long aSerial;
 if(GetVolumeInformation("c:\\", aName, sizeof(aName), &aSerial, &dwTemp, &dwTemp, aFileSystem, sizeof(aFileSystem)))
 {
  Label1->Caption = aSerial;
 }
}
//--------------------------------

Odczytywanie całkowitego rozmiaru dysku:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 double Total = DiskSize(3);
 Label1->Caption = "Rozmiar dysku: " + FloatToStr(Total /1024 /1024) + " MB";
}
//--------------------------------

Funkcja DiskSize pobiera jako parametr wartość typu unsigned char, inaczej mówiąc nie literę ani nazwę dysku, lecz kolejny numer dysku, np. 1 = A; 2 = B; 3 = C ... i tak dalej, można podać wartość zero wtedy będzie to dotyczyło dysku z którego program został uruchomiony.
Odczytywanie informacji o wolnej przestrzeni na dysku:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 double Total = DiskFree(3);
 Label1->Caption = "Wolna przestrzeń na dysku " + FloatToStr(Total /1024 /1024) + " MB";
}
//--------------------------------

Odczytywanie procentowej wolnej i pozostałej przestrzeni na dysku:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 __int64 AmtFree = DiskFree(0);
 __int64 Total = DiskSize(0);
 AnsiString S;
 S.sprintf(("%I64d% wolnej przestrzeni na dysku (" + ExtractFileDrive(Application->ExeName) + "\\): %I64d MB").c_str(), AmtFree * 100 / Total, AmtFree / 1024 / 1024 );
 Label1->Caption = S;
}
//--------------------------------

...powrót do menu. 

    Kopiowanie, przenoszenie i kasowanie katalogów, i plików za pomocą "latających folderów".

Pisząc o "latających folderach" mam na myśli to co widać na rysunku:

Opisywałem już jak kopiować pliki, jak tworzyć i kasować katalogi i pliki, ale umknęło mi kopiowanie katalogów wraz ze znajdującymi się w nich plikami i podkatalogami. Oczywiście można przerobić podane przeze mnie funkcję i napisać własną kopiującą całe katalogi z plikami, poprzez pobranie nazwy kopiowanego katalogu, utworzenie takiego samego w miejscu docelowym, zaindeksowanie całej zawartości katalogu źródłowego i przekopiowanie do katalogu docelowego pliku za plikiem. Takie kopiowanie wymaga jednak stworzenia odpowiedniej funkcji, dlatego pokażę znacznie prostszy sposób i krótszy, więc zaczynamy od kopiowania pojedynczego pliku za pomocą "latających folderów", do projektu należy włączyć bibliotekę #include <shellapi.h>, to właśnie ona odwali całą robotę:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 SHFILEOPSTRUCT fos;
 fos.hwnd = Handle;
 //operacja kopiowania
 fos.wFunc = FO_COPY;
 //plik źródłowy
 fos.pFrom = "C:\\Katalog\\nazwa_pliku.roz\0";
 //plik docelowy
 fos.pTo = "D:\\Katalog\nazwa_pliku.roz\0";
 fos.fFlags = 0;
 SHFileOperation(&fos);
}
//--------------------------------

Zanim przejdziemy dalej, krótkie objaśnienie. Plik źródłowy musi istnieć żeby można go było skopiować, nazwa pliku docelowego nie musi być taka sama, jeśli podamy inną nazwę niż nazwa pliku źródłowego, to plik docelowy zostanie zapisany z inną nazwą. Na końcu każdej ścieżki dostępu znajduje się znak "\0", jest to oznaczenie końca łańcucha znaków i zawsze musi tam być. Element struktury fos.hwnd pobiera uchwyt do czegoś, w przykładzie jest to uchwyt do okna programu, czyli okienko z latającym folderem pojawi się na tle naszego programu, jeżeli podamy tam jako parametr wartość NULL, to okienko pojawi się w lewym górnym rogu pulpitu, można również przekazać tam parametr this, wtedy okienka w ogóle nie będzie widać, ale nie będzie też widać kiedy kopiowanie zostało zakończone. Podobny, ale lepszy efekt uzyskamy jeśli ustawimy odpowiednio flagę: fos.fFlags = FOF_SILENT;. Jeżeli w miejscu docelowym będzie się już znajdował plik o takiej samej nazwie, wyskoczy okienko z zapytaniem, czy chcemy zastąpić istniejący pliki.
    Chcą przekopiować cały katalog wraz z zawartością, czyli ze wszystkimi podkatalogami i plikami ni należy podawać nazwy pliku, a jedynie nazwę katalogu źródłowego i nazwę dla katalogu docelowego:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 SHFILEOPSTRUCT fos;
 fos.hwnd = Handle;
 //operacja kopiowania
 fos.wFunc = FO_COPY;
 //plik źródłowy
 fos.pFrom = "C:\\Katalog\0";
 //plik docelowy
 fos.pTo = "D:\\Katalog\0";
 fos.fFlags = 0;
 SHFileOperation(&fos);
}
//--------------------------------

W ten sposób zostanie przekopiowana cała zawartość katalogu i podobnie jak to miało miejsce w przypadku plików nazwa katalogu źródłowego i nazwa katalogu docelowego nie muszą być takie same. Jeżeli podając ścieżkę dostępu do katalogu źródłowego podamy na końcu symbol gwiazdki, to przed skopiowaniem pojawi się dialog z zapytanie czy utworzyć katalog w miejscu docelowym:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 SHFILEOPSTRUCT fos;
 fos.hwnd = Handle;
 //operacja kopiowania
 fos.wFunc = FO_COPY;
 //plik źródłowy
 fos.pFrom = "C:\\Katalog\\*\0"; // można też tak: d:\\Katalog\\*.*\0 ale to nic nie zmienia.
 //plik docelowy
 fos.pTo = "D:\\Katalog\0";
 fos.fFlags = 0;
 SHFileOperation(&fos);
}
//--------------------------------

Teraz krótkie przykłady na przenoszenie plików i katalogów:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 SHFILEOPSTRUCT fos;
 fos.hwnd = Handle;
 //operacja kopiowania
 fos.wFunc = FO_MOVE;
 //plik źródłowy
 fos.pFrom = "C:\\Katalog\0";
 //plik docelowy
 fos.pTo = "D:\\Katalog\0";
 fos.fFlags = 0;
 SHFileOperation(&fos);
}
//--------------------------------

Kasowanie plików i folderów (niestety u mnie to nie działa) wyskakuje komunikat o błędzie, więc jeśli i u Ciebie to nie zadziała, to należy zastosować tradycyjną metodę podaną w poradzie kasowania plików i folderów:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 SHFILEOPSTRUCT fos;
 fos.hwnd = Handle;
 //operacja kopiowania
 fos.wFunc = FO_DELETE;
 //plik źródłowy
 fos.pFrom = "C:\\Katalog\\nazwa_pliku.roz\0";
 fos.fFlags = FOF_ALLOWUNDO;
 SHFileOperation(&fos);
}
//--------------------------------

Zmiana nazwy pliku lub folderu:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 SHFILEOPSTRUCT fos;
 fos.hwnd = Handle;
 //operacja kopiowania
 fos.wFunc = FO_MOVE;
 //plik źródłowy
 fos.pFrom = "C:\\Stary_Katalog\0"; // dla pliku: c:\\Katalog\\stara_nazwa.roz\0
 //plik docelowy
 fos.pTo = "C:\\Nowy_Katalog\0"; // dla pliku: c:\\Katalog\\nowa_nazwa.roz\0
 fos.fFlags = 0;
 SHFileOperation(&fos);
}
//--------------------------------

Zmiana nazwy katalogu nie wpływa w żaden sposób na pliki które się w nim znajdują.

Na zakończenie przykład kopiowania całych katalogów poprzez podanie nazwy katalogu źródłowego w Edit1 i katalog docelowego Edit2. Tutaj od razu zaznaczam, że kod ulegnie drobnej modyfikacji w związku z problemami jakie występowały przy kopiowaniu katalogów (nie dotyczy kopiowania plików) przy przekazywaniu nazwy katalogu źródłowego nie bezpośrednio do struktury, lecz właśnie poprzez obiekt Edit:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 String CopyFrom = Edit1->Text + "\0";
 String CopyTo   = Edit2->Text + "\0";
 CopyFrom        = CopyFrom.Insert("\\", 3);

SHFILEOPSTRUCT fos;
 fos.hwnd = Handle;
 //operacja kopiowania
 fos.wFunc = FO_COPY;
 //plik źródłowy
 fos.pFrom = CopyFrom.c_str();
 //plik docelowy
 fos.pTo = CopyTo;
 fos.fFlags = 0;
 SHFileOperation(&fos);
}
//--------------------------------

a...powrót do menu. 

    Sprawdzanie rozmiaru pliku.

O pobieraniu rozmiaru pliku było już w poradzie odczytywanie rozmiaru pliku i katalogu, ale tym razem chcę pokazać prosty kod w języku C odczytujący rozmiar pliku, żeby zrealizować to zadanie należy włączyć do projektu bibliotekę #include <io.h>, a kod jest banalnie prosty i nie wymaga wyjaśnień:

// Plik źródłowy np. Unit1.cpp.
#include <io.h>
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 int fp;
 fp = _open("nazwa_pliku.roz", _O_BINARY | _O_RDONLY );
 int DataLen = filelength( fp );
 _close(fp);
 Label1->Caption = "Rozmiar pliki: " + IntToStr(DataLen);
}
//--------------------------------

Inny sposób w C++ to skorzystanie z funkcji GetFileSize, niestety nie wiem czy występuje w środowisku BCB 6 czy też dopiero w BDS 2006.

// Plik źródłowy np. Unit1.cpp.
#include <io.h>
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 DWORD sizeFile;
 FILE *file = fopen("c:\\plik.txt", "r+");

 sizeFile = GetFileSize(file, NULL);

 Caption = FloatToStrF(((sizeFile/1024.00)/1024.00), ffNumber, 7, 0 ) + " KB";
 fclose(file);
}
//--------------------------------

 

...powrót do menu. 

    Zapisywanie i odczytywanie komponentów do/z pliku.

    W tej poradzie chcę przedstawić bardzo użyteczną rzecz, a mianowicie, jak to wynika z tytułu, chodzi o zapisywanie komponentów do pliku. Co to właściwie znaczy? Załóżmy że mamy na formularzu trzy różne komponenty Edit1, Memo1 i Image1, istnieje sposób na to żeby zapisać te trzy komponenty wraz z ich właściwościami i zawartością do jednego pliku, a potem to odczytać. Zapisując komponenty do pliku, jak już wspomniałem zapisujemy ich właściwości, takie jak rozmiar, położenie na formularzu, typ czcionki itp., ale zapisujemy również to co zostanie do tych komponentów wprowadzone, czyli w przypadku np. Edit1 i Memo1, będzie to tekst który został do nich wprowadzony, przy czym nie musi to być tekst wprowadzony jeszcze przed skompilowaniem programu, lecz w dowolnym momencie już w trakcie działania programu. Kolejna ciekawostka jest taka, że jeśli zmienimy położenie i właściwości komponentów takie jak np. rozmiar to po wczytaniu takiego komponentu z pliku, właściwości tegoż komponentu zostaną ustawione ponownie na takie jakie zostały do tego pliku zapisane. Mam nadzieję, że już wszyscy to zrozumieli, dodam tylko że tak stworzony plik, jest plikiem binarnym, a zapisanie i odczytanie komponentów jest banalnie proste ponieważ ogranicza się do wykorzystania funkcji WriteComponent(TComponent *) i ReadComponent(TComponent *), a oto jak wyglądają kod zapisywanie i kod odczytywania:

// Plik źródłowy np. Unit1.cpp.
#include <jpeg.hpp> // jeżeli chcemy żeby komponent Image obsługiwał pliki jpeg należy włączyć tą bibliotekę do pliku nagłówkowego.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 // deklaracja wskaźnika do TFileStream
 TFileStream *FStream;

 if(SaveDialog1->Execute())
 {
  // użycie konstruktora TFileStream do utworzenia pliku
  try
  {
   FStream = new TFileStream(SaveDialog1->FileName, fmCreate);
   // wskaźnik FStream pobiera zawartość Memo1
   FStream->WriteComponent(Memo1);
   // pobranie zawartości Edit1
   FStream->WriteComponent(Edit1);
   // pobranie zawartości Image1
   FStream->WriteComponent(Image1);
  }
  __finally
  {
   delete FStream;
  }
 }
}
//--------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 TFileStream *FStream;

 if(OpenDialog1->Execute())
 {
  try
  {
   FStream = new TFileStream(OpenDialog1->FileName, fmOpenRead | fmShareExclusive);
   FStream->ReadComponent(Memo1);
   FStream->ReadComponent(Edit1);
   FStream->ReadComponent(Image1);
  }
  __finally
  {
   delete FStream;
  }
 }
}
//--------------------------------

    Komponenty zapisywane i odczytywane muszą istnieć na formularzu, kolejność odczytywania komponentów z pliku zawsze musi być taka sama jak kolejność zapisywania, można zapisywać formularz np. Form1, ale przy próbie odczytywania wyskoczą komunikaty o błędach. Jeżeli umieścimy na formularzu np. Panel1 a na nim inne komponenty, to Panel1 stanie się ich "rodzicem", zapisując Panel1 do pliku nie zapisujemy jednak komponentów, które się na nim znajdują, tak więc można zapisywać tylko każdy komponent oddzielnie. Można odczytać tylko jeden komponent z pliku (jeśli zachodzi taka potrzeba) nawet jeśli do jednego pliku zapisaliśmy ich więcej, jednak wciąż obowiązuje kolejność w jakiej zostały zapisane, jeśli jako pierwszy zapiszemy komponent Memo1, to możemy go odczytać z pliku jako pierwszy, ale jeśli zapisaliśmy jako pierwszy Memo1, jako drugi Edit1 to żeby odczytać zawartość tylko Edit1 należy odczytać najpierw zawartość Memo1 i dopiero potem Edit1. Zapisywanie komponentów działa na wszystkie komponenty, ale nie wszystkie komponenty da się odczytać, problemy występują z komponentami wykorzystującymi wirtualne klasy i tak np. nie da się odczytać komponentu TStringGrid.
    Poradę można wykorzystać przy tworzeniu programów, w których chcemy zmieniać "skórki", wystarczy utworzyć kilka różnych skórek i zapisać je do wielu plików, a potem można je już bez problemu odczytywać i nie trzeba pobierać oddzielnie konfiguracji każdego komponentu. Po odpowiednim zaprogramowaniu można umożliwić użytkownikowi tworzenie własnych skórek i jest to w zasadzie banalnie proste.

...powrót do menu. 

    Zapisywanie i odczytywanie bufora do/z pliku

Pisząc o zapisywaniu bufora do pliku mam na myśli zapisywanie i odczytywanie danych różnych typów do/z jednego pliku. Zadanie to można zrealizować za pomocą klasy TFileStream, wykorzystując funkcję WriteBuffor i ReadBuffor. Pokażę na przykładzie jak zapisać do jednego pliku łańcuch znaków (char), data (TDateTime) i liczbę (Integer). Umieszczamy na formularzu trzy obiekty Edit1 dla łańcucha znaków, Edit2 dla daty i ComboBox1 zapiszemy numer wybranego w tym obiekcie indeksu:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 TFileStream *Streamer;
 char FullName[40]; // maksymalna długość łańcucha znaków wynosi 40;
 TDateTime DOB;
 Integer Gender;

 strcpy(FullName, Edit1->Text.c_str()); // pobiera łańcuch znaków np. Imię i Nazwisko
 DOB = StrToDate(Edit2->Text);          // pobiera datę z obiektu Edit2, format daty 2006-02-28
 Gender = ComboBox1->ItemIndex;         // pobiera numer indeksu z ComboBox1

 if(SaveDialog1->Execute())
 {
  try
  {
   Streamer = new TFileStream(SaveDialog1->FileName, fmCreate);
   Streamer->WriteBuffer(&FullName, 40);
   Streamer->WriteBuffer(&DOB, 10);
   Streamer->WriteBuffer(&Gender, 4);
  }
  __finally
  {
   delete Streamer;
  }
 }
}
//--------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 TFileStream *Streamer;
 char FullName[40];
 TDateTime DOB;
 Integer Gender;

 if(OpenDialog1->Execute())
 {
  try
  {
   Streamer = new TFileStream(OpenDialog1->FileName, fmOpenRead);
   Edit1->Text.Delete(0, 40);
   Edit2->Text.Delete(0, 10);
   ComboBox1->ItemIndex = -1;

   Streamer->ReadBuffer(&FullName, 40);
   Streamer->ReadBuffer(&DOB, 10);
   Streamer->ReadBuffer(&Gender, 4);

   Edit1->Text = FullName;
   Edit2->Text = DateToStr(DOB);
   ComboBox1->ItemIndex = StrToInt(Gender);
  }
  __finally
  {
   delete Streamer;
  }
 }
}
//--------------------------------

Funkcje WriteBuffer i ReadBuffer pobierają dwa argumenty, pierwszy argument to zmienna, obiekt lub struktura, przy czym zmienna AnsiString jest niedopuszczalna, drugi parametr to rozmiar zapisywanego obiektu. Kolejność odczytywania musi być taka sama jak kolejność zapisywania. Rozmiary odczytywanych i zapisywanych danych muszą być takie same.

...powrót do menu. 

    goto Instrukcja skoku do...

Istnieje instrukcja goto umożliwiająca wykonanie skoku do wybranej pozycji w kodzie. Instrukcja działa w obrębie tej samej funkcji, zdarzenia, występuje ona zawsze z etykietą, czyli najpierw w jakimś miejscu kodu należy umieścić etykietę, do której wykonany będzie skok za pomocą goto, etykieta jest to dowolny wyraz jednoczłonowy zakończony nie średnikiem lecz dwukropkiem. Najlepiej to widać na przykładzie:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 lab_1:
 Edit1->Text = "GOTO";

 if(Edit1->Text != "GOTO")
  goto lab_1;
}
//--------------------------------

Przedstawiony przykład jest banalny i nie przychodzi  mi do głowy, żadne praktyczne zastosowanie tej instrukcji. Z korzystaniem z tej instrukcji wiąże się niebezpieczeństwo, ponieważ nieprawidłowo użyta będzie działać w nieskończoność powodując "zawieszenie" programu, dlatego powinna ona występować zawsze z jakimś warunkiem, który będzie ograniczał jej zasięg.

...powrót do menu. 

    Tworzenie przestrzeni dla zmiennych i funkcji.

W języku C++ istnieje możliwość tworzenia przestrzeni wewnątrz których można umieszczać zmienne lub funkcje, takie przestrzenie przypominają trochę klasy, ale są znacznie prostsze w użyciu. Słowem kluczowym do tworzenia przestrzeni jest namespace po którym występuje nazwa przestrzenia, a potem nawiasy. Wewnątrz nawiasów umieszcza się zmienne lub funkcje, nie są one przypisane do żadnej klasy ani obiektu podobnie jak sama przestrzeń. Nazwa przestrzeni jest dowolna, ale jednoczłonowa. Przestrzeń można tworzyć zarówno w pliku źródłowym jak i nagłówkowym, jednak zawsze poza obrębem klasy, funkcji czy zdarzenia, nigdy wewnątrz:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------

namespace Przestrzen
{
 int zmienna = 10;
 void __fastcall AddText(TRichEdit *REdit)
 {
  for(int i = 0; i < 10; i++)
   REdit->Lines->Add("Linia: " + (AnsiString)i);
 }
}

void __fastcall TForm1::Button17Click(TObject *Sender)

{
 Przestrzen::AddText(RichEdit1);
 Edit1->Text = Przestrzen::zmienna;
}
//--------------------------------

Lub

// Plik nagłówkowy np. Unit1.h.
//--------------------------------
#ifndef Unit1H
#define Unit1H

//--------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>

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

namespace Przestrzen
{
 int zmienna = 10;
 void __fastcall AddText(TRichEdit *REdit)
 {
  for(int i = 0; i < 10; i++)
   REdit->Lines->Add("Linia: " + (AnsiString)i);
 }
}

class TForm1 : public TForm
{
__published: // IDE-managed Components
        TEdit *Edit1;
        TRichEdit *RichEdit1;
//--------------------------------

 

// Plik źródłowy np. Unit1.cpp.
//--------------------------------

void __fastcall TForm1::Button17Click(TObject *Sender)

{
 Przestrzen::AddText(RichEdit1);
 Edit1->Text = Przestrzen::zmienna;
}
//--------------------------------

Proszę zwrócić uwagę, że do adresowania zmiennych będących częścią przestrzeni używa się nazwy przestrzeni i podwójnego dwukropka. Można zdefiniować dowolną liczbę przestrzeni o dowolnych nazwach, ale w granicach zdrowego rozsądku. Stosowanie przestrzeni ma tą zaletę, że można tworzyć zmienne i funkcje o takich samych nazwach i odpowiednio je adresować w zależności od potrzeb.

...powrót do menu. 

    Odczyt i zmiana daty utworzenia pliku.
    nadesłał: KamyQ

W celu odczytania daty utworzenia pliku należy posłużyć się funkcją GetFileTime, jednak żeby wyświetlić datę w zrozumiałej formie należy dokonać konwersji najpierw do formatu SystemTime, a następnie do DateTime:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 FILETIME aFT, bFT, cFT;
 SYSTEMTIME ST;
 TDateTime DT;

 AnsiString name = "C:\\nazwa_pliku.roz";
 HANDLE hFile = CreateFile(name.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
 GetFileTime(hFile, &aFT, &bFT, &cFT);

 FileTimeToSystemTime(&aFT, &ST);
 DT = SystemTimeToDateTime(ST);

 Label1->Caption = FormatDateTime("dd.mm.yyy  hh:nn:ss", DT);
 CloseHandle(hFile);
}

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

 

Gwoli wyjaśnienia najpierw został utworzony uchwyt do pliku (hFile), następnie pobierana jest do struktury FILETIME data utworzenie, modyfikacji i ostatniego użycia pliku, poprzez funkcję GetFileTime, potem jest przeprowadzana konwersja do struktury SYSTEMTIME, a w ostatecznym rachunku wszystko zostaje przepisane do wirtualnej klasy TDateTime i sformatowane za pomocą funkcji FormatDateTime do wartości typu AnsiString. Zmienna aFT pobiera informację o dacie utworzenia pliku, bFT pobiera informację o dacie modyfikacji pliku, a cFT o dacie ostatniego użycia pliku. W przykładzie zostanie wyświetlona informacja o dacie utworzenia pliku, ponieważ to właśnie zmienna aFT zostaje przepisana za pomocą funkcji FileTimeToSystemTime do struktury SYSTEMTIME, ale oczywiście można w ten sam sposób pobrać wszystkie trzy daty dotyczące pliku.
    Zmiana daty utworzenia pliku będzie przebiegać podobnie, tym razem jednak skorzystamy z funcji SetFileTime:

 

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 FILETIME aFT, bFT, cFT;
 TSystemTime ST;

 TDateTime DT;
 DT = EncodeDate(2030, 7, 15) + EncodeTime(12, 0, 0, 0);

 DateTimeToSystemTime(DT, ST);

 AnsiString name = "C:\\clony.txt";
 HANDLE hFile = CreateFile(name.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);

 GetFileTime(hFile, 0, &bFT, &cFT);

 SystemTimeToFileTime(&ST, &aFT);

 SetFileTime(hFile, &aFT, &bFT, &cFT);

 CloseHandle(hFile);
}
//--------------------------------

 

Jak widać kod wygląda podobnie, różnica polega na sposobie wprowadzenia nowej daty do klasy TDateFile za pomocą funkcji EncodeDate i EncodeFile, należy zawsze wprowadzać datę i czas w przeciwnym razie konwersja może mieć nieprawidłowy przebieg. To co się rzuca w oczy to, że przed zmianą daty utworzenia została przywołana funkcja odczytująca daty GetFileTime, jest to konieczne, ponieważ przy zmianie daty utworzenia należy wprowadzić wszystkie trzy rodzaje daty, a w przykładzie zmieniamy tylko datę utworzenia pliku, więc pozostałe powinny być wprowadzone takie jakie już ten plik posiada, więc oczywistym wydaje się, że najpierw trzeba te daty odczytać. Podobnie jak to było z odczytywaniem daty, struktura aFT odczytuje datę utworzenia pliku, bFT datę modyfikacji pliku, a cFT datę ostatniego użycia pliku.

...powrót do menu. 

    Sprawdzanie stanu klawiszu Caps Lock i Insert.

Do sprawdzania stanu klawiszy służy funkcja GetKeyState, która pobiera jako argument klawisz stan którego chcemy sprawdzić. W przykładzie stan klawisza będzie sprawdzany w funkcji przechwytującej komunikaty, dlatego trzeba umieścić na formularzu komponent ApplicationEvents1 z zakładki Additionals. Na formularzu należy również umieścić komponent StatusBar1 i utworzyć w nim dwa panele. W panelu pierwszym będzie wyświetlany stan klawisza Caps Lock, a w panelu drugim stan klawisza Insert. Dla komponentu ApplicationEvents1 tworzymy zdarzenie OnMessage i umieszczamy w nim kod przechwytujący stan klawiszy:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::ApplicationEvents1Message(tagMSG &Msg,
        bool &Handled)
{
 if(GetKeyState(VK_CAPITAL))
  StatusBar1->Panels->Items[0]->Text = "CapsLock"; // Caps lock on
 else
  StatusBar1->Panels->Items[0]->Text = ""; // CapsLock off

 if(GetKeyState(VK_INSERT))
  StatusBar1->Panels->Items[1]->Text = "Insert"; // Insert on
 else
  StatusBar1->Panels->Items[1]->Text = "Overwrite"; // Insert off
}
//--------------------------------


W podanym przykładzie stan klawisza będzie sprawdzany tylko przy aktywnym formularzu. Jeżeli chcemy przechwytywać stan klawisza nawet wtedy gdy program jest nieaktywny należy posłużyć się zegarem (TTimer) lub umieścić pętlę for wykonującą się w nieskończoność w nowym wątku. Można sprawdzać stan każdego klawisz podając np. dla klawisza A:
'A', jednak przy każdym wciśnięciu będzie rejestrowany inny stan tego klawisza, więc od razu rozwieję wątpliwości, tej funkcji nie da się wykorzystać do rejestrowania jakie klawisze zostały wciśnięte.

...powrót do menu. 

    Rezerwowanie pliku wymiany na potrzeby programu.

 

Istnieje funkcja GetMemory która rezerwuje, a właściwie przypisuje sobie na własność pewien obszar pliku wymiany, funkcja pobiera tylko jeden argument określający w bajtach wielkość rezerwowanego obszaru:
 

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  GetMemory(200000000); //zarezerwowano 200MB
}
//--------------------------------


...powrót do menu. 

    Zmiana położenia ScrollBox w drugim obiekcie w oparciu o położenie ScrollBox w obiekcie pierwszym.

 

    Swego czasu na forum pojawiło się pytanie jak przesuwając w obiekcie Memo1 ScrollBox przesunąć go do takiej samej pozycji w obiekcie Memo2. Do przesuwania ScrollBox można posłużyć się funkcją SetScrollInfo lub SetScrollPoz, ale te funkcje pozwalają tylko na przesunięcie samego ScrollBox, nie przewijają natomiast strony w obiekcie np. Memo1. Do realizacji tego zadania niezbędna jest struktura SCROLLINFO i dwie funkcje: GetScrollInfo pobierająca informacje o położeniu ScrollBox w jednym obiekcie i przepisująca je do stuktury, oraz funkcja PostMessage wysyłająca odpowiedni komunikat do drugiego obiektu, w którym chcemy zmienić położenie ScrollBox. Funkcja PostMessage jest bliźniaczą funkcją SendMessage, różni się od niej tylko tym, że czeka na wykonanie komunikatu:

 

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 SCROLLINFO lpsi;
 lpsi.fMask = SIF_ALL;
 GetScrollInfo(Memo1->Handle, SB_VERT, &lpsi);
 PostMessage(Memo2->Handle, WM_VSCROLL, SB_THUMBTRACK + (0x10000 * lpsi.nPos), 0);
}
//--------------------------------

W przedstawionym przykładzie zmiana położenia ScrollBox w obiekcie Memo2 nastąpi dopiero po wywołaniu zdarzenia OnClick dla przycisku Button1, ale można zrobić to w taki sposób, że przesuwanie ScrollBox w  obiekcie Memo1 będzie powodowało jednoczesne przesuwanie ScrollBox w obiekcie Memo2. Należy w tym celu stworzyć funkcję przechwytującą komunikat o przesuwaniu ScrollBox i powiązać go z obiektem Memo1, w tym celu deklarujemy w pliku nagłówkowym w sekcji private lub public obiekt typu TWndMethod i funkcję przechwytującą komunikat:

// Plik nagłówkowy np. Unit1.h.
//--------------------------------
private:
       
TWndMethod Sc;
        void __fastcall MoveScroll(TMessage &Msg);

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

Teraz należy tylko powiązać obiekt Memo1 z funkcją MoveScroll i stworzyć definicję tej funkcji:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 Sc = Memo1->WindowProc;
 Memo1->WindowProc = MoveScroll;
}
//--------------------------------
void __fastcall TForm1::MoveScroll(TMessage &Msg)
{
 if(Msg.Msg == WM_VSCROLL)
 {
  SCROLLINFO lpsi;
  lpsi.fMask = SIF_ALL;
  GetScrollInfo(Memo1->Handle, SB_VERT, &lpsi);
  PostMessage(Memo2->Handle, WM_VSCROLL, SB_THUMBTRACK + (0x10000 * lpsi.nPos), 0);
 }
 Sc(Msg);
}
//--------------------------------

Przedstawiony sposób pozwala na przesuwanie paska pionowego, dla paska poziomego należy zmienić SB_VERT na SB_HORZ, a WM_VSCROLL na WM_HSCROLL, może to wyglądać tak:

// Plik źródłowy np. Unit1.cpp.
void __fastcall TForm1::MoveScroll(TMessage &Msg)
{
 if(Msg.Msg == WM_VSCROLL | WM_HSCROLL)
 {
  SCROLLINFO lpsi, hpsi;
  lpsi.fMask = SIF_ALL;
  hpsi.fMask = SIF_ALL;
 
  GetScrollInfo(Memo3->Handle, SB_VERT, &lpsi);
  GetScrollInfo(Memo3->Handle, SB_HORZ, &hpsi);

  PostMessage(Memo2->Handle, WM_VSCROLL, SB_THUMBTRACK + (0x10000 * lpsi.nPos), 0);
  PostMessage(Memo2->Handle, WM_HSCROLL, SB_THUMBTRACK + (0x10000 * hpsi.nPos), 0);
 }
 Sc(Msg);
}
//--------------------------------

...powrót do menu. 

       Tworzenie pliku z zasobami i zapisywanie zasobów do pliku.

 

    O sposobie tworzenia plików z zasobami i włączania ich do programu pisałem już wielokrotnie w serwisie dlatego teraz przedstawię to pokrótce, utworzymy plik z różnymi zasobami by można je było później z niego wydobyć i ponownie zapisać do pliku.
W celu utworzenie pliku z zasobami przygotowujemy sobie zasoby, które chcemy do niego włączyć, mogą to być dowolne pliki, ja w przykładzie posłużyłem się plikiem czcionki, bitmapą, ikoną i animowanym kursorem. Otwieramy Notatnik i umieszczamy w nim następujące wpisy:
 

ID_1         RCDATA         "czcionka.ttf"
ID_2         RCDATA         "kursor.ani"
ID_3         RCDATA         "bitmapa.bmp"
ID_4         RCDATA         "ikona.ico"

Jak widać dla każdego zasobu został utworzony w pliku kod na który składają się trzy elementy. Pierwszy element to identyfikator zasobu od ID_1 do ID_4, identyfikator to po prostu dowolna nazwa po której zasób będzie rozpoznawalny, nazwa ta musi być jednoczłonowa i może zawierać tylko znaki języka angielskiego. Drugi element to typ zasobu, występują różne typy zasobów, jeżeli jednak umieszczamy w zasobach pliki różnych rodzajów i zamierzamy je później z tych zasobów wydobywać to należy użyć typu RCDATA, który określa plik jako binarny plik zasobu i nie rozróżnia typu pliku, co jest o tyle wygodne, że każdy plik zostanie wydobyty z zasobu w takiej postaci pod jaką został w nim umieszczony. Trzeci element to nazwa pliku i ścieżka dostępu do pliku, który chcemy umieścić w zasobach, jeżeli plik znajduje się w tym samym katalogu co plik zasobu to można podać tylko nazwę pliku.
    Tak utworzony plik tekstowy zapisujemy pod dowolną nazwą z rozszerzeniem *.rc, np. zasoby.rc. Występuje tutaj duża dowolność ponieważ równie dobrze można ten plik zapisać jako plik tekstowy czyli np. jako zasoby.txt. Gdy plik jest już gotowy przystępujemy do jego kompilacji w tym celu uruchamiamy wiersz poleceń, tzw. konsolę CMD i wpisujemy polecenie: brcc32 <nazwa wejściowego pliku dla zasobów> <nazwa wyjściowego pliku zasobów>, dla przykładu będzie to wyglądało tak: brcc32 zasoby.rc zasoby.res
Gdy plik zasobów, w przykładzie zasoby.res jest gotowy, włączamy go do naszego projektu poprzez menu Project | Add To Project...
I na tym kończy się przygotowanie pliku z zasobami został on włączony do projektu i jest gotowy do użycia. Teraz stworzymy funkcję, która wydobędzie poszczególne pliki z pliku zasobów:

// Plik źródłowy np. Unit1.cpp.
bool SaveResourceToFile(char *FileName, char *res)
{
 HRSRC hrsrc = FindResource(HInstance, res, RT_RCDATA);
 if(hrsrc == NULL) return false;
 DWORD size = SizeofResource(HInstance, hrsrc);
 HGLOBAL hglob = LoadResource(HInstance, hrsrc);
 LPVOID rdata = LockResource(hglob);
 HANDLE hFile = CreateFile(FileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
 DWORD writ;
 WriteFile(hFile, rdata, size, &writ, NULL);
 CloseHandle(hFile);
 return true;
}
//--------------------------------

Jeśli wydobycie zasobu się powiedzie funkcja zwraca wartość true, w przeciwnym razie false. Przedstawię teraz przykład na wydobycie pojedynczego zasobu i wszystkich zasobów:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 if(!SaveResourceToFile((ExtractFilePath(Application->ExeName) + "mycursor.ani").c_str(), "ID_2"))
  Application->MessageBox("Nie odnaleziono zasobu: \"ID_2\"", "Zasoby", MB_OK | MB_ICONSTOP);
}
//--------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 for(int i = 1; i <= 4; i++)
 {
  String id = "ID_" + IntToStr(i);
  String FileName = "";
  switch(i)
  {
   case 1: FileName = "myfont.ttf"; break;
   case 2: FileName = "mycursor.ani"; break;
   case 3: FileName = "mybitmap.bmp"; break;
   case 4: FileName = "myicon.ico"; break;
  }
  if(!SaveResourceToFile((ExtractFilePath(Application->ExeName) + FileName).c_str(), id.c_str()))
   Application->MessageBox(("Nie odnaleziono zasobu: \"" + id + "\"").c_str(), "Zasoby", MB_OK | MB_ICONSTOP);
 }
}
//--------------------------------

Na zakończenie pokażę jeszcze jeden sposób na wywołanie tej funkcji:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
 AnsiString id[] = {"ID_1", "ID_2", "ID_3", "ID_4"};
 AnsiString fn[] = {"fnFont.ttf", "fnCursor.ani", "fnBitmap.bmp", "fnIcon.ico"};

 for(int i = 0; i <= 3; i++)
 {
  String sfn = ExtractFilePath(Application->ExeName) + fn[i];

  if(!SaveResourceToFile(sfn.c_str(), id[i].c_str()))
   Application->MessageBox(("Nie odnaleziono zasobu: \"" + id[i] + "\"").c_str(), "Zasoby", MB_OK | MB_ICONSTOP);
}
}
//--------------------------------

Powyższa porada może okazać się bardzo użyteczna przy tworzeniu wersji instalacyjnej jakiegoś własnego programu, ponieważ w zasobach można w ten sposób umieszczać pliki dowolnego rodzaju, w tym również pliki programów, czyli *.exe. Co więcej tak stworzony instalator można spakować jakimś exepackerem, np. UPX zmniejszając tym samym rozmiar pliku instalacyjnego, pomimo takiego spakowania zasoby wciąż będą mogły być wydobywane bez problemu. Spis identyfikatorów i plików do wypakowania z zasobów można umieszczać w odrębnym pliku i z niego wczytywać przed instalacją, można równie dobrze taki plik umieścić w zasobach i wczytywać jego zawartość bezpośrednio z zasobów o czym pisałem juz w serwisie w poradach Umieszczanie plików tekstowych w zasobach programu i Tworzenie tablicy łańcuchów znaków i umieszczanie jej w zasobach programu.
Na zakończenie informacja dla osób korzystających ze środowiska Borland C++ Builder 6 Enterprise TRIAL, w tej wersji prawdopodobnie brakuje pliku brcc32.exe dlatego należy przenieść go do katalogu [..]\Program Files\Borland\CBuilder6\Bin\ ze środowiska BCB 6 Personal, program będzie działał bez problemu.

...powrót do menu. 

    Jak dodać deinstalator programu do listy Dodaj usuń programy?

 

Tworząc wersję instalacyjną własnego programu, dobrze jest stworzyć również program odinstalowujący, taki program będzie w stanie odinstalować wszystkie zainstalowane składniki,  a na koniec usunie sam siebie poprzez stworzony przez plik *.BAT no i na koniec usunie sam plik *.BAT.
Najpierw należy utworzyć program odinstalowujący, nazwa jest oczywiście dowolna np. Uninstall.exe. W takim programie należy umieścić instrukcje usuwające aplikację i wpisy z rejestru, trzeba również dodać instrukcje usuwające sam program odinstalowujący. Tutaj pojawia się pewien problem ponieważ program sam siebie nie usunie, gdyż nie można usunąć otwartego procesu, dlatego wymyśliłem sposób na usunięcie deinstalatora poprzez stworzenie pliku *.BAT z odpowiednimi wpisami. Plik *.BAT ma to do siebie, że jest otwierany za pomocą programu cmd.exe więc zawarte w nim instrukcje mogą usunąć nie tylko sam deinstalator, ale również sam plik *.BAT. Na prostym przykładzie pokaże jak stworzyć taki prosty deinstalator, będzie on usuwał przykładowy program o nazwie Lekcja4.exe i utworzy odpowiedni plik *.BAT. Przykładowy deinstalator w efekcie swojego działania usunie trzy pliki, plik aplikacji Lekcja4.exe, plik deinstalatora Uninstall.exe, oraz pomocniczy plik wsadowy Uninstall.bat, oczywiście usunięciu ulegnie również wpis w rejestrze dodający odpowiedni wpis do apletu panelu sterowania Dodaj usuń programy. O tym jak dodawać program do listy Dodaj usuń programy napiszę w dalszej części tej porady, a tera przykład deinstalatora:
 

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
#include <fstream.h>
#include <registry.hpp>

void Uninstall(void)
{
 String path = ExtractFilePath(Application->ExeName);
 TRegistry *Rejestr = new TRegistry();
 Rejestr->RootKey = HKEY_LOCAL_MACHINE;
 Rejestr->LazyWrite = false;
 Rejestr->DeleteKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Lekcja4"); //Usunięcie programu z listy Dodaj usuń programy
 delete Rejestr;

 // tworzymy plik bat z odpowiednimi wpisami.
 ofstream outfile;
 outfile.open((path + "uninstall.bat").c_str());
 outfile << ("del " + path + "uninstall.exe").c_str() << endl;  //instrukcja usuwająca deinstalator
 outfile << ("del " + path + "uninstall.bat").c_str() << endl;  //instrukcja usuwająca tworzony plik uninstall.bat
 outfile.close();

 if(DeleteFile((path + "Lekcja4.exe").c_str())) //odinstalowanie aplikacji Lekcja4.exe
 {
  WinExec((path + "uninstall.bat").c_str(), SW_HIDE); //uruchomienie pliku wsadowego Uninstall.bat
  Application->Terminate();
 }
 else
 {
  //jeśli odinstalowywana aplikacja nie istnieje w podanej lokalizacji.
  ShowMessage("Nie udało się odinstalować aplikacji!");
  Application->Terminate();
 }
}
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Uninstall(); //wywołanie funkcji odinstalowującej
}
//--------------------------------

Jak widać jest to maksymalnie uproszczona wersja deinstalatora. Zwracam uwagę na pewien bardzo istotny fakt, ścieżki dostępu do wszystkich kasowanych plików powinny być bezwzględne (nie względne), w przykładzie wszystkie ścieżki zostają obliczone w odniesieniu do aplikacji deinstalatora (Uninstall.exe). Użycie bezwzględnych ścieżek dostępu jest istotne z tego powodu, że deinstalatorowi uruchomionemu poprzez aplet panelu sterowania Dodaj usuń programy, zostanie przypisana ścieżka dostępu przez sam aplet, więc względne ścieżki dostępu nie będą odwoływały się do odinstalowywanych plików, użycie bezwzględnych ścieżek dostępu zawsze przypisze prawidłowe ścieżki dostępu do plików.
Tworząc wersję instalacyjną programu można utworzyć plik tekstowy np. Install.log w którym zostaną podane bezwzględne ścieżki dostępu do wszystkich zainstalowanych plików, które w późniejszym czasie mogą zostać odinstalowane. Deinstalator może odczytać zawartość takiego pliku i w ten sposób usunąć wszystkie zainstalowane pliki. Niezależnie od tego jakie pliki odinstaluje, żeby nie pozostawiać zbędnych plików należy utworzyć plik *.BAT usuwający pozostałości deinstalatora. Zamiast usuwać pliki za pomocą funkcji DeleteFile można równie dobrze utworzyć w pliku wsadowym polecenie del c:\nazwa_katalogu /Q /S kasujące całą zawartość folderu. Oczywiście nie ma w tym nic nowego plik *.BAT zawiera komendy DOS dla konsoli CDM.exe. Dla tych którzy DOS'a nie znają przypominam (bo może się przydać) komenda kasujaca katalog to RMDIR, np. RMDIR C:\Windows, ale przed skasowanie katalogu należy usunąć całą jego zawartość, np: DEL C:\WINDOWS /Q /S w tym miejscu ostrzegam mniej bystrych, nie należy kasować katalogu WINDOWS. Jeśli chcecie uzyskać spis wszystkich poleceń dla konsoli CMD należy wpisać w niej komendę HELP.
    Gdy już wyjaśniliśmy sobie jak stworzyć program odinstalowujący, możemy przystąpić do tworzenie funkcji dodającej deinstalator do listy Dodaj usuń programy, cała sztuczka polega na dodaniu odpowiednich wpisów do rejestru:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
#include <registry.hpp>
void __fastcall
TForm1::Button1Click(TObject *Sender)
{
 String path = ExtractFilePath(Application->ExeName);
 TRegistry *Rejestr = new TRegistry();
 Rejestr->RootKey = HKEY_LOCAL_MACHINE;
 Rejestr->LazyWrite = false;
 Rejestr->OpenKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Lekcja4", true);
 Rejestr->WriteString("DisplayIcon", path + "Lekcja4.exe");
 Rejestr->WriteString("DisplayName", "Lekcja 4");
 Rejestr->WriteString("UninstallString", path + "Uninstall.exe");
 delete Rejestr;
}
//--------------------------------

Nazwa klucza Lekcja4 jest w zasadzie dowolna (jednoczłonowa, tylko angielskie znaki). Wartość DisplayIcon przechowuje informacje o ikonie wyświetlanej na liście Dodaj usuń programy. Może to być ścieżka dostępu do pliku *.ICO lub do innego pliku zawierającego ikony. Wartość DisplayName jest umieszczana jako nazwa programu odinstalowującego na liście Dodaj usuń programy.  Wartość UninstallString to ścieżka dostępu do deinstalatora.
Istnie jeszcze kilka innych sposobów na odinstalowanie, można się np. posłużyć systemowymi programami IsUninst.exe lub Uninst.exe, jednak wymagają one plików z informacjami o odinstalowywanych plikach, są to pliki *.isi i *.log, ale w tej chwili nie wiem jak się tworzy takie pliki.

...powrót do menu.