Wyliczanie komponentów na formularzu, dostęp do komponentów poprzez pętlę.

Jednym ze sposobów dobrania się do wszystkich komponentów na formularzu jest wykorzystanie przypisania polimorficznego i funkcji FindComponent, o tym pisałem już w artykule rzutowanie typów, przypisanie polimorficzne, dla porządku jednak przypomnę. Funkcja FindComponent pobiera tylko jeden argument i jest to nazwa komponentu i jeżeli komponent o takiej nazwie zostanie znaleziony, to funkcja zwraca do niego uchwyt. Wykorzystując przypisanie polimorficzne można w ten sposób zmienić dowolną właściwość takiego komponentu. Zastosowanie takiego rozwiązania byłoby mało praktyczne, gdyby nie jedna rzecz, otóż jeżeli mamy na formularzu kilka komponentów o podobnej nazwie, np. Button1, Button2, Button3 itd. to można wykorzystując pętlę zmienić hurtem właściwość dla wszystkich takich obiektów. Można wykorzystać dwie metody z przypisania polimorficznego, pierwsza to dynamic_cast, pozwala rzutować typ (klasę) na wszystkie komponenty wywodzące się z klasy na którą jest rzutowana, czyli jeśli chcemy zmienić właściwości kilku obiektów typu TButton, to można to zrobić za pomocą właśnie tej metody:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::TButton1Click(TObject *Sender)
{
 for(int i = 0; i < 5; i++)
  dynamic_cast<TButton *>(FindComponent("Button" + (String)i))->Caption = "Przycisk" + (String)i;
}
//--------------------------------

W przykładzie zmianie ulegnie właściwość Caption obiektów typu TButton z Button na Przycisk.
Jeżeli jednak chcemy wpłynąć na właściwość kilku komponentów, ale należących do różnych klas, należy użyć metody reinterpret_cast, jednak rzutować należy na klasę wspólną dla tych komponentów, lub na inną klasę, ale taką która posiada właściwość którą chcemy zmienić, nazwy tych różnych komponentów muszą być jednak podobne, żeby można było dostać się do nich poprzez pętlę.

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::TBUtton1Click(TObject *Sender)
{
 for(int i = 0; i < 5; i++)
  reinterpret_cast<TForm1 *>(FindComponent("Object" + (String)i))->Caption = "Komponent" + (String)i;
}
//--------------------------------

W ten sposób możemy się jednak dobrać tylko do obiektów, których nazwy są nam poniekąd znane. Wykorzystując przypisanie polimorficzne można się jednak dobrać do wszystkich komponentów znajdujących się na formularzu. W przykładzie poniżej wyliczymy w obiekcie Memo1 wszystkie komponenty znajdujące się na formularzu i co najważniejsze, nie musimy wcale znać nazw tych komponentów, ani typów klas z których się one wywodzą:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::TBUtton1Click(TObject *Sender)
{
 Memo1->Lines->Clear();
 TComponent *Temp;
 for(int i = Form1->ComponentCount - 1; i >= 0; i--)
 {
  Temp = Form1->Components[i];
  Memo1->Lines->Add("Nazwa obiektu: " + reinterpret_cast<TForm *>(Temp)->Name + "\tKlasa obiektu: " + reinterpret_cast<TForm *>(Temp)->ClassName());
 }
}
//--------------------------------

W przykładzie, w obiekcie Memo1 zostaną wyświetlone nazwy wszystkich obiektów znajdujących się na formularzu i typy klas z których te obiekty się wywodzą. Możemy teraz dokonać zmiany dowolnej właściwości wszystkich obiektów na formularzu, można na przykład zmienić właściwość Anchors dla wszystkich obiektów znajdujących się na formularzu a posiadających właściwość Align. Dlaczego tak? A dlatego, że właściwość Anchors pozwala na automatyczną zmienę rozmiaru i położenia obiektów na formularzu w odniesieniu do rozmiaru formularz (dokładniej o tym w następnej poradzie):

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::TBUtton1Click(TObject *Sender)
{
 TComponent *Temp;
 for(int i = Form1->ComponentCount - 1; i >= 0; i--)
 {
  Temp = Form1->Components[i];
  TAnchors A;
  A.Clear();
  A = A << akRight << akBottom << akTop << akLeft;
  if(reinterpret_cast<TForm *>(Temp)->Align == alCustom)
   reinterpret_cast<TForm *>(Temp)->Anchors = A;
 }
}
//--------------------------------

...powrót do menu. 

Dostosowanie rozmiaru komponentów do zmieniającego się rozmiaru formularza.

Jednym ze sposobów na automatyczną zmianę rozmiaru i położenia komponentów na formularzu w zależności od zmieniającego się rozmiaru formularza, jest ustawienie właściwości Align tych komponentów na np. alClient, jednak nie wszystkie komponenty posiadają tą właściwość, a poza tym efekty takiej zmiany rozmiaru komponentów rzadko są zadowalające. Do pozycjonowania i zmiany rozmiaru w zależności od rozmiaru formularza służy właściwość Anchors pobierająca cztery parametry akLeft - wyrównanie komponentu do lewej strony, akTop - wyrównująca komponent do góry, akRight - wyrównująca komponent do prawej strony i akBottom wyrównująca komponent do dołu. Po umieszczeniu dowolnego komponentu na formularzu właściwości akLeft i akTop są ustawione na true, natomiast akRight i akBottom na false. Dzięki takiemu ustawieniu tych parametrów, komponent zawsze jest wyrównywany do lewej strony i do góry, w praktyce oznacza to, że niezależnie od rozmiaru formularza będzie on zawsze posiadał ten sam rozmiar i zawsze będzie wyrównany do pozycji w jakiej został umieszczony. Gdy zmienimy np. właściwość Align dowolnego obiektu na alClient to wszystkie parametry właściwości Anchors zostaną ustawione na true. W praktyce oznacza to, że komponent zawsze będzie rozciągnięty na cały formularz, ale nie oznacza to wcale, że rozmiar komponentu jest ustawiany na taki sam jak rozmiar formularz, tak naprawdę to komponent najpierw ma ustalany rozmiar na rozmiar formularza a potem zostają zmienione jego parametry właściwości Anchors i to właśnie te parametry sterują położeniem komponentu na formularzu. Wygląda to po prostu tak, że niezależnie od rozmiaru formularza, komponent z tak ustawionymi parametrami zawsze będzie wyrównywał się do lewego, prawego, górnego i dolnego marginesu.
    Teraz gdy już to wiemy, możemy przeprowadzić eksperyment, otóż umieszczamy na formularzu komponent Button1 i ustawiamy wszystkie cztery parametry Anchors (akLeft, akTop, akRight i akBottom) na true, komponent Button nie posiada właściwości Align więc nie można jej ustawić. Kompilujemy program i po uruchomieniu zmieniamy rozmiar formularza. Jak widać wraz ze zmianą rozmiaru formularza zmienia się proporcjonalnie rozmiar komponentu Button1.
W ten sposób można sterować rozmiarem dowolnego komponentu posiadającego właściwość Anchors, przy czym jeżeli obiekt posiada właściwość Align to należy ją ustawić na alNone lub alCustom. Proponuję również poeksperymentować i ustawić np. parametry akLeft i akTop na false, a akRight i akBottom na true, przy takim ustawieniu komponent nie będzie zmieniał rozmiaru, ale będzie się przemieszczał po formularzu wraz ze zmianą jego rozmiaru.

Ustawiając np. właściwość Anchors dowolnego obiektu alLeft = true; akTop = true; akRight = true; i ALign tego komponentu na
alCustom sprawimy, że komponent zawsze będzie dopasowywany do górnej, lewej i prawej krawędzi formlarza. Długość tego obiektu będzie ustalana poprzez wyrównanie do lewej i prawej krawędzi formularza, jednak zostaną zachowane proporcje tego obiektu przy zmianie rozmiaru okna. Dzięki temu nie trzeba pisać kodu skalującego by dopasować komponent do rozmiaru okna. W podanym przypadku nie należy ustawiać alBottom na true, gdyż nie chcemy by obiekt był wyrównywany do dołu okna, a więc by nie zmieniał swojej właściwości Height.

Jeżeli np. ustawimy Anchors tak: alLeft = true; akTop = false; akRight = true; alBottom = true i ALign tego komponentu na
alCustom, to komponent będzie wyrównywany zawsze względem dolnej krawędzi okna a nie górnej, ale właściwość Width będzie zmieniała się jak w przykładzie wyżej.

By to dobrze zrozumieć należy poeksperymentować z właściwością Ahchors przy Align ustawionym na alCustom.

Na zakończenie jeszcze podam przykład na zmianę właściwości Anchors za pomocą kodu, ponieważ nie jest to takie oczywiste:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::TBUtton1Click(TObject *Sender)
{

  TAnchors A;
  A.Clear();
  A = A << akRight << akBottom << akTop << akLeft;
  Button1->Anchors = A;
}
//--------------------------------

Jeżeli chcemy ustawić wszystkie parametry na false, należy to zrobić tak:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::TBUtton1Click(TObject *Sender)
{

  TAnchors A;
  A.Clear();
  Button1->Anchors = A;
}
//--------------------------------

Jeżeli chcemy ustawić tylko niektóre parametry, należy zrobić tak:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::TButton1Click(TObject *Sender)
{

  TAnchors A;
  A.Clear();
  A = A << akTop << akLeft;
  Button1->Anchors = A;
}
//--------------------------------

...powrót do menu. 

Wyliczanie dysków zainstalowanych w komputerze.

Nie istnieje żadna gotowa funkcja, która umożliwiałaby wyliczenie wszystkich dysków twardych, stacji dyskietek, flash dysków, napędów CD i DVD, istnieje jednak funkcja która może się do tego przydać, jest to GetDriveType. Funkcja pobiera jako argument nazwę dysku i zwraca informację o jego typie, wiedząc to możemy stworzyć funkcję, która będzie sprawdzała wszystkie litery alfabetu (angielskiego) i jeśli napotka jakiś napęd to go zaindeksuje.

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall EnumDrivers(TStrings *EnumList)
{
 int AddedIndex;
 char DriveName[4] = "A:\\";
 for(char Drive = 'A'; Drive <= 'Z'; Drive++)
 {
  DriveName[0] = Drive;
  switch(GetDriveType(DriveName))
  {
   case DRIVE_REMOVABLE:
                        DriveName[1] = '\0';
                        EnumList->Add("Stacja dyskietek " + (String)Drive + ":");
                        DriveName[1] = ':';
                        break;
   case DRIVE_FIXED:
                        DriveName[1] = '\0';
                        EnumList->Add("Dysk lokalny " + (String)Drive + ":");
                        DriveName[1] = ':';
                        break;
   case DRIVE_CDROM:
                        DriveName[1] = '\0';
                        EnumList->Add("Napęd CD DVD " + (String)Drive + ":");
                        DriveName[1] = ':';
                        break;
   case DRIVE_REMOTE:
                        DriveName[1] = '\0';
                        EnumList->Add("Dysk sieciowy " + (String)Drive + ":");
                        DriveName[1] = ':';
                        break;
   case DRIVE_RAMDISK:
                        DriveName[1] = '\0';
                        EnumList->Add("RAM dysk " + (String)Drive + ":");
                        DriveName[1] = ':';
                        break;
  }
 }
}
//--------------------------------

void __fastcall TForm1::TButton1Click(TObject *Sender)
{
 EnumDrivers(ListBox1->Items);
}
//--------------------------------

...powrót do menu. 

Odczytywanie nazwy dysku.

W celu odczytania nazwy dysku należy posłużyć się funkcją GetVolumeInformation pobierającą informacje o dysku. Stworzymy prostą funkcję pobierającą jako argument literę dysku (np. C:) i zwracającą jego nazwę.

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
#include <stdio.h>
String GetVolumeName(LPCTSTR FindDrive)
{
 TCHAR lpVolumeName[MAX_PATH];
 char szVolumeName[MAX_PATH];

 if(GetVolumeInformation(FindDrive, lpVolumeName, sizeof(lpVolumeName), NULL, NULL, NULL, NULL, 0) == 0)
    strcpy(szVolumeName, "");
 else
    sprintf(szVolumeName, "[%s]", lpVolumeName);

return (String)szVolumeName;
}
//--------------------------------

void __fastcall TForm1::TButton1Click(TObject *Sender)
{
 Label1->Caption = GetVolumeName("D:\\"); // wartość typu char
}
//--------------------------------

Można to połączyć z funkcją wyliczania dysków.

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
#include <stdio.h>
String GetVolumeName(LPCTSTR FindDrive)
{
 TCHAR lpVolumeName[MAX_PATH];
 char szVolumeName[MAX_PATH];

 if(GetVolumeInformation(FindDrive, lpVolumeName, sizeof(lpVolumeName), NULL, NULL, NULL, NULL, 0) == 0)
    strcpy(szVolumeName, "");
 else
    sprintf(szVolumeName, "[%s]", lpVolumeName);

 return (String)szVolumeName;
}
//--------------------------------
void __fastcall EnumDrivers(TStrings *EnumList)
{
 int AddedIndex;
 char DriveName[4] = "A:\\";
 for (char Drive = 'A'; Drive <= 'Z'; Drive++)
 {
  DriveName[0] = Drive;
  switch(GetDriveType(DriveName))
  {
   case DRIVE_REMOVABLE:
                        DriveName[1] = '\0';
                        EnumList->Add("Stacja dyskietek " + (String)Drive + ": " + GetVolumeName(((String)Drive + ":\\").c_str()));
                        DriveName[1] = ':';
                        break;
   case DRIVE_FIXED:
                        DriveName[1] = '\0';
                        EnumList->Add("Dysk lokalny " + (String)Drive + ": " + GetVolumeName(((String)Drive + ":\\").c_str()));
                        DriveName[1] = ':';
                        break;
   case DRIVE_CDROM:
                        DriveName[1] = '\0';
                        EnumList->Add("Napęd CD DVD " + (String)Drive + ": " + GetVolumeName(((String)Drive + ":\\").c_str()));
                        DriveName[1] = ':';
                        break;
   case DRIVE_REMOTE:
                        DriveName[1] = '\0';
                        EnumList->Add("Dysk sieciowy " + (String)Drive + ": " + GetVolumeName(((String)Drive + ":\\").c_str()));
                        DriveName[1] = ':';
                        break;
   case DRIVE_RAMDISK:
                        DriveName[1] = '\0';
                        EnumList->Add("RAM dysk " + (String)Drive + ": " + GetVolumeName(((String)Drive + ":\\").c_str()));
                        DriveName[1] = ':';
                        break;
  }
 }
}
//--------------------------------

void __fastcall TForm1::TButton1Click(TObject *Sender)
{
 EnumDrivers(ListBox1->Items);
}
//--------------------------------

...powrót do menu. 

Odczytywanie systemu plików dysku.

W tej poradzie wykorzystamy tą samą funkcję co w poradzie odczytywanie nazwy dysku, czyli GetVolumeInformation, ponieważ ta funkcja pozwala na pobranie znacznie większej liczby informacji. Za odczytywanie informacji o systemie plików dysku odpowiadają dwa ostatnie argumenty tej funkcji. W prostej formie wygląda to tak:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
#include <stdio.h>
String GetFileSystem(LPCTSTR FindDrive)
{
 TCHAR lpFileSystem[MAX_PATH];
 char szFileSystem[MAX_PATH];

 if(GetVolumeInformation(FindDrive, NULL, NULL, NULL, NULL, NULL, lpFileSystem, sizeof(lpFileSystem)) == 0)
    strcpy(szFileSystem, "");
 else
    sprintf(szFileSystem, "%s", lpFileSystem);

return (String)szFileSystem;
}
//--------------------------------

void __fastcall TForm1::TButton1Click(TObject *Sender)
{
 Label1->Caption = GetFileSystem("D:\\"); // wartość typu char
}
//--------------------------------

Teraz pokaże jak pobrać z jednej funkcji informacje o nazwie dysku i systemie plików, w zasadzie polega to na połączeniu porady o odczytywaniu nazw dysku z tą poradą. Funkcja nie może zwracać dwóch informacji jeśli jest typu String, dlatego stworzymy strukturę pomocniczą, która zrealizuje to zadanie, jest to dobry moment, żeby sobie przypomnieć do czego służą i jak działają struktury.

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
#include <stdio.h>
struct
VolumeInfo
{
 
char lpVolumeName[MAX_PATH];
 
char lpFileSystem[MAX_PATH];
};

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

VolumeInfo GetDriveInfo(LPCTSTR FindDrive)
{
 TCHAR lpVolumeName[MAX_PATH];
 TCHAR lpFileSystem[MAX_PATH];

 VolumeInfo Vi;

 if(GetVolumeInformation(FindDrive, lpVolumeName, sizeof(lpVolumeName), NULL, NULL, NULL, lpFileSystem, sizeof(lpFileSystem)) == 0)
 {
  strcpy(Vi.lpVolumeName, "");
  strcpy(Vi.lpFileSystem, "");
 }
 else
 {
  sprintf(Vi.lpVolumeName, "[%s]", lpVolumeName);
  sprintf(Vi.lpFileSystem, "system plików: %s", lpFileSystem);
 }
 return Vi;
}
//--------------------------------
void __fastcall EnumDrivers(TStrings *EnumList)
{
 int AddedIndex;
 char DriveName[4] = "A:\\";
 VolumeInfo Vi;
 for(char Drive = 'A'; Drive <= 'Z'; Drive++)
 {
  DriveName[0] = Drive;
  switch(GetDriveType(DriveName))
  {
   case DRIVE_REMOVABLE:
                       DriveName[1] = '\0';
                       Vi = GetDriveInfo(((String)Drive + ":\\").c_str());
                       EnumList->Add("Stacja dyskietek " + (String)Drive + ": " + (String)Vi.lpVolumeName + " " + (String)Vi.lpFileSystem);
                       DriveName[1] = ':';
                       break;
   case DRIVE_FIXED:
                       DriveName[1] = '\0';
                       Vi = GetDriveInfo((((String)Drive + ":\\").c_str());
                       EnumList->Add("Dysk lokalny " + (String)Drive + ": " + (String)Vi.lpVolumeName + " " + (String)Vi.lpFileSystem);
                       DriveName[1] = ':';
                       break;
   case DRIVE_CDROM:
                       DriveName[1] = '\0';
                       Vi = GetDriveInfo((((String)Drive + ":\\").c_str());
                       EnumList->Add("Napęd CD DVD " + (String)Drive + ": " + (String)Vi.lpVolumeName + " " + (String)Vi.lpFileSystem);
                       DriveName[1] = ':';
                       break;
   case DRIVE_REMOTE:
                       DriveName[1] = '\0';
                       Vi = GetDriveInfo((((String)Drive + ":\\").c_str());
                       EnumList->Add("Dysk sieciowy " + (String)Drive + ": " + (String)Vi.lpVolumeName + " " + (String)Vi.lpFileSystem);
                       DriveName[1] = ':';
                       break;
   case DRIVE_RAMDISK:
                       DriveName[1] = '\0';
                       Vi = GetVolumeName(((String)Drive + ":\\").c_str());
                       EnumList->Add("RAM dysk " + (String)Drive + ": " + (String)Vi.lpVolumeName + " " + (String)Vi.lpFileSystem);
                       DriveName[1] = ':';
                       break;
  }
 }
}
//--------------------------------

void __fastcall TForm1::TButton1Click(TObject *Sender)
{
 EnumDrivers(ListBox1->Items);
}
//--------------------------------

...powrót do menu. 

Sprawdzanie czy płyta znajduje się w napędzie.

Do sprawdzenia czy płyta znajduje się w napędzie można posłużyć się funkcją DirectoryExists sprawdzającą czy istnieje katalog:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::TButton1Click(TObject *Sender)
{
 if(DirectoryExists("D:\0"))
  ShowMessage("Płyta znajduje się w napędzie!");
 else
  ShowMessage("Brak płyty w napędzie!");
}
//--------------------------------

Ta metoda ma jednak pewną wadę, jeśli w napędzie nie ma płyty to zostanie wyświetlony komunikat z ostrzeżeniem "W stacji nie ma dysku. Włóż dysk do stacji D:". Żeby uniknąć tego uciążliwego komunikatu można w celu sprawdzenia czy dysk znajduje się w napędzie sprawdzać wywołać funkcję GetVolumeInformation tylko z nazwą sprawdzanego dysku:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::TButton1Click(TObject *Sender)
{
 if(GetVolumeInformation("f:\\", NULL, NULL, NULL, NULL, NULL, NULL, 0))
  ShowMessage("Płyta znajduje się w napędzie!");
 else
  ShowMessage("Brak płyty w napędzie!");
}
//--------------------------------

...powrót do menu. 

Wywołanie okna dialogowego umożliwiającego wybranie i stworzenie katalogu.

Wśród tych kilku nielicznych okien dialogowych jakie oferuje nam zakładka 'Dialogs' można znaleźć np. okno dialogowe OpenDialog umożliwiające wybranie pliku, jednak nie istnieje żaden komponent który umożliwiałby na wybranie tylko katalogu. Można oczywiście samemu stworzyć sobie takie okno dialogowe za pomocą komponentów dostępnych na zakładce 'Win 3.1', ale jest to zbędne, ponieważ w bibliotekach BCB (filectrl.hpp, qdialogs.hpp) znajduje się funkcja SelecdDirectory(...) umożliwiająca wywołanie takiego okna. Funkcja ta występuje pod dwiema postaciami. Pierwsza postać pozwala na wywołanie Windows'owego okna dialogowego, pozwalającego na wybranie katalogu, bez możliwości jego utworzenia:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::TButton1Click(TObject *Sender)
{
 String Dir;
 SelectDirectory("Wybierz katalog", "", Dir);
 Edit1->Text = Dir;
}
//--------------------------------

Jest to przykład prostego zastosowania tej funkcji. Jak widać pobiera ona trzy argumenty. Pierwszy to nazwa etykiety wyświetlanej w oknie dialogowym, drugi to nazwa początkowa przeszukiwania katalogów, brzmi to trochę skomplikowanie i wymaga wyjaśnienia. Jeżeli podamy tutaj pustą wartość, jak w przykładzie, to możliwe stanie się wybieranie katalogu na wszystkich dyskach komputera, jednak można podać w tym miejscu tylko nazwę dysku, np. "c:\\" wtedy okno dialogowe będzie umożliwiało wybranie katalogu tylko na dysku c: pozostałe dyski nie będą dostępne w tym oknie, jeżeli natomiast podamy ścieżkę dostępu do jakiegoś katalogu, np. "c:\\program files" to możliwe będzie wybieranie katalogu tylko w lokalizacji c:\Program Files\. Za pomocą tego parametru można ograniczać możliwości wybierania katalogu do konkretnej lokalizacji. Trzeci argument jest również typu AnsiString i podaje on i zarazem pobiera ścieżkę dostępu do wybranego katalogu. Jeżeli ten argument nie zawiera żadnej wartości lub zawiera ścieżkę dostępu do nieistniejącego katalogu to okno dialogowe będzie wyświetlało pozycję Mój komputer umożliwiając tym samym wybór katalogu w dowolnej lokalizacji, jeżeli jednak drugi argument będzie zawierał jakąś wartość, np. ścieżkę dostępu do jakiegoś katalogu, to w takiej sytuacji okno dialogowe będzie wyświetlało pozycję podaną w drugim argumencie. Jeżeli trzeci argument będzie zawierał ścieżkę dostępu do istniejącego katalogu to okno dialogowe wyświetli się w tej pozycji o ile nie będzie ona sprzeczna z drugim argumentem. Przykład sprzeczności: String Dir = "d:\\"; SelectDirectory("Wybierz katalog", "c:\\", Dir); W przypadku napotkania sprzeczności o wyświetlanej pozycji decyduje drugi argument, trzeci jest pomijany. Niżej przykład kilku sposobów wywołania tej funkcji.

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::TButton1Click(TObject *Sender)
{
 String Dir = "c:\\windows";
 SelectDirectory("Wybierz katalog", "c:\\", Dir);
 Edit1->Text = Dir;
}
//--------------------------------
void __fastcall TForm1::TButton2Click(TObject *Sender)
{
 String Dir = "c:\\windows";
 if(SelectDirectory("Wybierz katalog", "", Dir))
  Edit1->Text = Dir;
}
//--------------------------------

 

Na początku wspomniałem, że funkcja ta występuje pod dwiema postaciami. W tej drugiej wersji możliwe jest stworzenie katalogu, ale typ wyświetlanego okna dialogowego jest zupełnie inny. Przykład zastosowania funkcji:

 

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::TButton1Click(TObject *Sender)
{
 String Dir;
 if(SelectDirectory(Dir, TSelectDirOpts() << sdAllowCreate << sdPerformCreate << sdPrompt, NULL))
  Edit1->Text = Dir;
}
//--------------------------------


Pierwszy argument podaje i zarówno pobiera ścieżkę dostępu do katalogu, działa tak samo jak trzeci argument w pierwszej wersji tej funkcji. Drugi argument określa w jaki sposób ma się zachowywać okno dialogowe. Dostępne są tutaj trzy parametry typu TSelectDirOpts(), a oto ich opis:

Trzeci argument to numer indeksu ID w pliku pomocy. Jeśli nie dołączamy do programu pliku pomocy należy podać wartość NULL.

Jeżeli program nie rozpoznaje funkcji SelectDirectory, należy włączyć do projektu bibliotekę #include <filectrl.hpp>.

...powrót do menu. 

Dostęp do właściwości komponentów poprzez nazwę klasy.

Przedstawiona tutaj porada może być bardzo przydatna w sytuacji, gdy chcemy dokonać zmiany jakiejś właściwości wielu komponentów znajdujących się na formularzu. Podobne rozwiązanie przedstawiłem w poradzie 'wyliczanie komponentów na formularzu, dostęp do komponentów poprzez pętlę'. W tamtej poradzie pokazałem jak dobrać się do wielu komponentów poprzez nazwę tych komponentów i przypisanie polimorficzne, ta porad różni się jednak od tamtej tym, że dostęp do komponentów zostanie zrealizowany poprzez nazwę klasy komponentu, a więc nazwa samego komponentu nie ma znaczenia. W praktyce oznacza to, że można w ten sposób zmieniać właściwości wielu komponentów wywodzących się z tej samej klasy. Przedstawię to na przykładzie komponentów typu TEdit, w tym celu umieśćmy na formularzu jakąś ich ilość, ile ich będzie nie ma zupełnie znaczenia, zmienimy wszystkim komponentom typu TEdit właściwość Text, zostanie to zrealizowane poprzez pętlę, a informację o liczbie komponentów pobierze sobie pętla z właściwości formularza ComponentCount:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::TButton1Click(TObject *Sender)
{
 TComponent *Temp;
 for(int i = ComponentCount - 1; i >= 0; i--)
 {
  Temp = Components[i];
  if(Temp->ClassNameIs("TEdit"))
  {
   dynamic_cast<TEdit *>(Temp)->Text = "Edit" + (String)i;
  }
 }
}
//--------------------------------

W podobny sposób można zmienić właściwości różnego rodzaju komponentów na formularzu, obowiązuje jednak zasada, że wszystkie komponentu do których się odwołujemy muszą posiadać tą właściwość, czyli gdybyśmy mieli na formularzu np. komponenty typu TLabel i TEdit i próbowalibyśmy zmienić im wszystkim właściwość Text to przy komponentach typu TLabel wyskoczy błąd, ponieważ nie posiadają one takiej właściwości. W przykładzie posłużę się właściwością Visible, ponieważ posiadają ją niemal wszystkie obiekty:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::TButton1Click(TObject *Sender)
{
 TComponent *Temp;
 for(int i = ComponentCount - 1; i >= 0; i--)
 {
  Temp = Components[i];
  reinterpret_cast<TControl *>(Temp)->Visible = false;
 }
}
//--------------------------------

W przykładzie wykonałem rzutowanie polimorficzne na klasę TControl ponieważ jest ona wspólna dla prawie wszystkich komponentów, czyli inaczej mówiąc w prostej linii niemal wszystkie komponenty wywodzą się z tej klasy i co ważne klasa ta posiada właściwość Visible. Należy pamiętać, że wykonując rzutowanie na jakąś klasę, ta klasa musi posiadać właściwość którą zmieniamy. W podanym wyżej przykładzie równie dobrze można by wykonać rzutowanie na klasę TForm, ponieważ posiada ona właściwość Visible:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::TButton1Click(TObject *Sender)
{
 TComponent *Temp;
 for(int i = ComponentCount - 1; i >= 0; i--)
 {
  Temp = Components[i];
  reinterpret_cast<TForm *>(Temp)->Visible = false;
 }
}
//--------------------------------

Kolejna ważna sprawa to, że formularz nie jest komponentem i pomimo iż posiada on właściwość Visible, to nie zostanie ona zmieniona, mówiąc prościej tego rozwiązania nie da się zastosować w odniesieniu do formularza.
Właściwości wielu komponentów różnego typu można realizować poprzez wykluczenie komponentów, którym tych właściwości nie chcemy zmienić, lub komponentów, które tych właściwośći nie posiadają, np. gdy umieścimy na formularzu komponenty typu TEdit, TMemo i TLabel, komponenty TMemo i TEdit posiadają właściwość Text, ale TLabel już nie, więc należy go wykluczyć:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::TButton1Click(TObject *Sender)
{
 TComponent *Temp;
 for(int i = ComponentCount - 1; i >= 0; i--)
 {
  Temp = Components[i];
  if(!Temp->ClassNameIs("TLabel"))
  {
   reinterpret_cast<TEdit *>(Temp)->Text = "Jakiś tekst"; // rzutowanie można by równie dobrze wykonać na klasę TMemo, ponieważ ona również posiada właściwość Text
  }
 }
}
//--------------------------------

...powrót do menu. 

Zapisywanie i odczytywanie stylu czcionki do/z pliku INI.

Ta porada jest o tym jak zapisać do pliku konfiguracyjnego (*.ini) właściwość Style dowolnej czcionki. Można bez problemu zapisać nazwę czcionki, czy rozmiar, a o zapisywaniu kolorów można przeczytać w poradzie zapisywanie koloru do pliku w dziale grafika. Nie znam żadnej funkcji, która potrafiłaby zrealizować to zadanie, dlatego stworzyłem własne funkcje, jedna wspomaga zapisywanie stylu do pliku druga odczytywanie. Informacje o stylu zostają zapisane jako wartości typu AnsiString:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
#include <inifiles.hpp>
AnsiString GetFontStyles(TFontStyles fs)
{
 String result = "";
 if(fs.Contains(fsBold))      result  = "B";
 if(fs.Contains(fsItalic))    result += "I";
 if(fs.Contains(fsUnderline)) result += "U";
 if(fs.Contains(fsStrikeOut)) result += "S";
 return result;
}
//---------------------------------------------------------------------------
TFontStyles SetFontStyles(AnsiString Value)
{
 TFontStyles fs;
 unsigned short x = Value.Pos("B");

 if(x > 0) fs << fsBold;
  x = Value.Pos("I");
 if(x > 0) fs << fsItalic;
  x = Value.Pos("U");
 if(x > 0) fs << fsUnderline;
  x = Value.Pos("S");
 if(x > 0) fs << fsStrikeOut;
 
 return fs;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 TIniFile *Ini = new TIniFile(ExtractFilePath(ParamStr(0)) + "\\test.ini");
 Ini->WriteString("FONT", "STYLE", GetFontStyles(Memo1->Font->Style));
 delete Ini;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 TIniFile *Ini = new TIniFile(ExtractFilePath(ParamStr(0)) + "\\test.ini");
 Memo1->Font->Style = SetFontStyles(Ini->ReadString("FONT", "STYLE", ""));
 delete Ini;
}
//--------------------------------

...powrót do menu. 

Trochę inny ProgressBar.

Taki ProgressBar jaki widać na powyższym rysunku można uzyskać tylko w systemie WindowsXP i to, tylko wtedy gdy dołączy się do programu manifest. O dołączaniu manifestu do programu pisałem w poradach 'Jak w Windows XP nadać kontrolkom odpowiedni wygląd?' i 'Wygląd kontrolek w windows XP. jak umieścić manifest w zasobach programu.'.

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::TButton1Click(TObject *Sender)
{
 long style = GetWindowLong(ProgressBar1->Handle, GWL_STYLE);
 style = style | PBS_MARQUEE;
 SetWindowLong(ProgressBar1->Handle, GWL_STYLE, style);

 // Rozpoczęcie odliczania
 ProgressBar1->Perform(PBM_SETMARQUEE, true, 100);

 for(int i = 0; i < 100; i++)
 {
  ProgressBar1->Position += 1;
  Sleep(100);
  Application->ProcessMessages();
 }
 // Zakończenie odliczania
 ProgressBar1->Perform(PBM_SETMARQUEE, false, 100);
}
//--------------------------------

Jeżeli kompilator z jakichś powodów nie rozpoznaje wartości PBS_MARQUEE i PBS_SETMARQUE, to zamiast tych zdefiniowanych zmiennych, trzeba je podać jawnie:

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::TButton1Click(TObject *Sender)
{
 long style = GetWindowLong(ProgressBar1->Handle, GWL_STYLE);
 long oldstyle = GetWindowLong(ProgressBar1->Handle, GWL_STYLE); // zapamiętuje pierwotny styl
 style = style | 0x08;
 SetWindowLong(ProgressBar1->Handle, GWL_STYLE, style);
 ProgressBar1->Perform((WM_USER + 10), true, 100);

 for(int i = 0; i < 500; i++)
 {
  ProgressBar1->Position += 1;
  Sleep(10);
 }
 ProgressBar1->Perform((WM_USER + 10), false, 100);
 SetWindowLong(ProgressBar1->Handle, GWL_STYLE, oldstyle); // przywraca pierwotny styl, żeby wyzerować ProgressBar
 ProgressBar1->Position = 0;
}
//--------------------------------

 

...powrót do menu. 

Przeliczanie czasu w milisekundach na godziny, minuty, sekundy i milisekundy.

Kod przedstawiony w tej poradzie może mieć zastosowanie w aplikacji odmierzającej czas, czyli np. w stoperze. O ile skonstruowanie stopera nie powinno nastręczać większych problemów, o tyle przeliczenie czasu odmierzanego w sekundach może niejednemu spędzić sen z powiek.
W tej poradzi pokażę kompletny kod stopera, nie będę jednak opisywał co jest czym, z wyjątkiem funkcji timeGetTime(). Funkcja ta podaje czas w milisekundach jaki upłynął od uruchomienia systemu, jeżeli stoper zapamięta tą wartość w chwili uruchomienia zegara, wtedy w odpowiedziach na każde zdarzenie OnTimer będzie pobierał nowy czas od funkcji timeGetTime i będzie odejmował zachowany czas początkowy w celu wyliczenia czasu jaki upłynął od uruchomienia zegara. Przy odmierzaniu dokładnego czasu nie można polegać na właściwości Interval obiektu Timer ponieważ poddaje się on opóźnieniom wynikającym z "zajętości" procesora, czyli jeżeli np. w czasie odmierzania czasu procesor zacznie nagle wykonywać jakieś zadanie, to może to powodować opóźnienia w działaniu zegara. Opóźnienia mogą być również spowodowane przez kod umieszczony w zdarzeniu OnTimer. Funkcja timeGetTime eliminuje ten błąd.

// Plik nagłówkowy np. Unit1.h
//--------------------------------
private:
        int STime;
        int ETime;
        int LTime;
        int _h, _n, _s, _t;

 

// Plik źródłowy np. Unit1.cpp.
//--------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
 STime = 0;
 ETime = 0;
 LTime = 0;
 Edit1->Text = "0.0";
 Edit2->Text = "0.0";
}
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 if(Timer1->Enabled) Timer1->Enabled = false;
 else
 {
  STime = timeGetTime();
  ETime = 0;
  LTime = 0;
  Timer1->Enabled = true;
 }
}
//--------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 long mSec;
 mSec = ETime - LTime;
 LTime = ETime;
 Edit2->Text = FloatToStrF(((float)mSec)/1000.00/60.0, ffNumber, 7, 2);
}
//--------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
 STime = timeGetTime();
 ETime = 0;
 LTime = 0;
 Edit1->Text = "0.0";
 Edit2->Text = "0.0";
 _h = 0; _n = 0; _s = 0; _t = 0;
}
//--------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
 ETime = timeGetTime() - STime;
 int h = ETime/1000/3600 - _h;
 int n = ETime/1000/60 - _n;
 int s = ETime/1000 - _s;
 int t = ETime - _t;
 if(t >= 999){_t += 999; t = 999;}
 if(s == 60) {_s += 60; s = 0;}
 if(n == 60) {_n += 60; n = 0;}
 if(h == 24) {_h += 24; h = 0;}

 TTime Time = EncodeTime((WORD)h, (WORD)n, (WORD)s, (WORD)t);

 if(t >= 500)
  Edit1->Text = Time.FormatString("hh:nn:ss:zzz");
 else
  Edit1->Text = Time.FormatString("hh nn ss zzz");
}
//--------------------------------

Przedstawiony tutaj stoper wymaga dopracowania, ma on tylko posłużyć do pokazania sposobu przeliczania milisekund na interwały czasu, przeliczanie odbywa się w zdarzeniu OnTimer.

...powrót do menu. 

Blokowanie Menu Start.

Nie wiem do czego mogłoby się to przydać, ale przycisk menu start można zablokować za pomocą funkcji: EnableWindow, przekazując jej jako pierwszy argument uchwyt do "okna menu start", a jako drugi argument przekazujemy wartość typu bool, gdzie false blokuje przycisk, a true odblokowuje.  Dodam tutaj od razu, że za pomocą funkcji EnableWindow można zablokować dowolne okno lub element okna, jeżeli tylko jako pierwszy argument przekażemy prawidłowy uchwyt do tego okna:
Niżej przykłady blokowania i odblokowania przycisku Menu Start:

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 EnableWindow(FindWindowEx(FindWindow("Shell_TrayWnd", NULL), 0, "Button", NULL), false);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 EnableWindow(FindWindowEx(FindWindow("Shell_TrayWnd", NULL), 0, "Button", NULL), true);
}
//---------------------------------------------------------------------------

...powrót do menu. 

Pobieranie ścieżki dostępu do domyślnego programu powiązanego z plikiem.

Jak wiadomo niemal wszystkie pliki z wyjątkiem wykonywalnych i kilku innych są powiązane z programami, w których są otwierane, np. plik tekstowy jest powiązany z programem Notepad. Za pomocą funkcji FindExecutable można pobrać ścieżkę dostępu do do takiego programu, wystarczy przekazać funkcji jako argument ścieżkę dostępu do pliku, dla którego chcemy sprawdzić powiązanie.

HINSTANCE FindExecutable
(
 LPCTSTR lpFile,
 LPCTSTR lpDirectory,
 LPTSTR lpResult
);

Jak widać funkcja pobiera trzy argumenty, pierwszy to o czym wspomniałem ścieżka dostępu do pliku, dla którego chcemy sprawdzić powiązanie, drugi to ścieżka dostępu do katalogu z tym plikiem, ten argument można pomijać poprzez wstawianie wartości NULL, trzeci argument to wartość zwracana przez funkcję, a funkcja jeżeli pierwszy argument jest prawidłowy zawsze zwróci ścieżkę dostępu do pliku przekazanego funkcji jako pierwszy argument.

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 AnsiString FileName = "e:\\cyfbar\\index.html";
 char Exec[MAX_PATH];
 FindExecutable(FileName.c_str(), NULL, Exec);
 Edit1->Text = Exec;
}
//---------------------------------------------------------------------------

W powyższym przykładzie zostanie zwrócona nazwa do przeglądarki internetowej powiązanej z plikiem index.html, w moim przypadku będzie to ścieżka dostępu do przeglądarki Firefox gdyż ta jest u mnie domyślną i z tą są powiązane wszystkie pliki *.html.
Jeżeli chcemy tylko sprawdzić jaki program jest powiązany z danym typem pliku, bez konieczności wskazywania ścieżki dostępu do takiego pliku, to możemy to zrobić poprzez utworzenie takiego pliku do testu i usunięciu go:

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 char Exec[MAX_PATH];
 String TestFile = ExtractFilePath(ParamStr(0)) + "test.html";
 FileClose(FileCreate(TestFile));
 
 FindExecutable(TestFile.c_str(), NULL, Exec);

 DeleteFile(TestFile);

 Edit1->Text = (String)Exec;
}
//---------------------------------------------------------------------------

Niemal wszyscy wiedzą zapewne, że można otworzyć plik w domyślnym programie za pomocą funkcji ShellExecute, jest ona jednak niepraktyczna, gdyż może nie działać prawidłowo w przypadku plików powiązanych z przeglądarką internetową, szczególnie gdy Internet Explorer nie jest przeglądarką domyślną, poza tym funkcja ShellExecute po wywołaniu i zakończeniu zadania nie zwalnia pamięci, co w przypadku aplikacji nastawionych na oszczędne gospodarowanie pamięcią może stanowić kłopot. Można rozwiązać ten kłopot stosując funkcję FindExecutable w połączeniu funkcją CreateProcess.

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 AnsiString FileName = "e:\\cyfbar\\index.html";
 char Exec[MAX_PATH];
 FindExecutable(FileName.c_str(), NULL, Exec);
 strcat(Exec, (" " + FileName).c_str());

 STARTUPINFO StartupInfo = {0};
 StartupInfo.cb = sizeof(STARTUPINFO);
 PROCESS_INFORMATION ProcessInfo;

 if(CreateProcess(NULL, Exec, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL,
                  NULL, &StartupInfo, &ProcessInfo))
 {
  CloseHandle(ProcessInfo.hProcess);
  CloseHandle(ProcessInfo.hThread);
 }
}
//---------------------------------------------------------------------------

...powrót do menu. 

Umieszczanie kursorów (*.cur, *.ani) w zasobach programu.

W poradzie wczytywanie i używanie animowanego kursora podałem sposób w jaki można wczytać z dysku animowany kursor i przypisać go do dowolnego obiektu w programie, przedstawiony tam sposób ma również zastosowanie dla zwykłych kursorów. W tej poradzie pokażę jak można umieścić w zasobach programu kursory zwykłe (*.cur) i animowane (*.ani).
O umieszczaniu plików w zasobach rozpisywałem się już wielokrotnie, więc nie będę teraz tego powtarzał. Zaczniemy od tego, że umieszczamy w katalogu z projektem aplikacji pliki kursorów, dla przykładu niech to będą dwa kursory jeden zwykły (u mnie 3dsmove.cur) i jeden animowany (u mnie appstar2.ani). Gdy kursory są już gotowe, otwieramy notatnik i wpisujemy w nim:
 

#define ANICURSOR 21

KURSOR_1 ANICURSOR "appstar2.ani"
KURSOR_2 CURSOR    "3dsmove.cur"

 

Krótkie wyjaśnienie: na samym początku w pliku zostaje utworzona definicja zasobu ANICURSOR, jest to nazwa przypisana do typu zasobu 21, gdyż w odróżnieniu od zasobu zwykłego kursora, który ma typ CURSOR, kursor animowany ma typ zasobu o numerze 21. Można by zrezygnować z tej definicji i zamiast podawać ANICURSOR użyć wartości 21, czyli ten plik mógłby mieć również taką postać:

 

KURSOR_1 21        "appstar2.ani"
KURSOR_2 CURSOR    "3dsmove.cur"

 

Efekt będzie taki sam. Tak utworzony plik w Notatniku zapisujemy pod dowolną nazwą, ale zawsze z rozszerzeniem *.RC, np. zasoby.rc, plik oczywiście zapisujemy w tym samym katalogu w którym znajdują się wspomniane kursory. Przechodzimy do naszego projektu i wybieramy menu Project | Add to Project..., i włączamy do projektu nasz plik zasoby.rc. Od teraz nie musimy się już martwić kompilacją zasobów, gdyż podczas kompilacji całego projektu zasoby również zostaną automatycznie skompilowane i włączone do projektu.
Dalsze postępowanie jest podobne do tego przedstawionego w poradzie
wczytywanie i używanie animowanego kursora z tą różnicą, że zamiast funkcji LoadCursorFromFile używamy funkcji LoadCursor wczytującej kursor z zasobów. W przedstawionym niżej przykładzie zostają wczytane kursory jeden zwykły i jeden animowany, kursor animowany zostaje przypisany do formularza, a kursor zwykły zostaje przypisany do obiektu StringGrid znajdującego się na formularzu:

 

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 HCURSOR Kursor_1 = LoadCursor((void *)HInstance, "KURSOR_1");
 HCURSOR Kursor_2 = LoadCursor((void *)HInstance, "KURSOR_2");

 Screen->Cursors[1] = Kursor_1;
 Screen->Cursors[2] = Kursor_2;

 this->Cursor        = (Controls::TCursor)1;
 StringGrid1->Cursor = (Controls::TCursor)2;
}
//---------------------------------------------------------------------------

Kursory wczytane z zasobów zostają najpierw umieszczone w tablicy zasobów Screen->Cursors[x], tablica ta składa się z nieokreślonej liczby elementów do której można wczytywać kursory. Przypisanie dowolnego kursora z tablicy do wybranego obiektu w programie odbywa się nie poprzez podanie nazwy tego kursora, lecz poprzez podanie numeru elementu tablicy w której ten kursor został umieszczony. Jeżeli umieszczamy w zasobach dużą ilość kursorów, to można nieco zmodyfikować kod i wczytywać je wszystkie do tablicy w pętli. Przypisanie kursorów do wybranych obiektów może się odbywać w dowolnym momencie, czyli niekoniecznie wtedy, gdy zostaną one wczytane do tablicy z zasobów. Poniższy przykład pokazuje jak wczytać z zasobów 10 kursorów i jak w zdarzeniu OnClick przycisku Button1 przypisać jeden z tych kursorów do obiektu Memo1. Trzeba tutaj spełnić następujące warunki: w zasobach musi znajdować się co najmniej 10 kursorów, a nazwy zasobów tych kursorów muszą się kończyć wartością od 1 do 10, czyli np. KURSOR_1, KURSOR_2 ... KURSOR_10.

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 for(int i = 1; i <= 10; i++)
 {
  Screen->Cursors[i] = LoadCursor((void *)HInstance, ("KURSOR_" + IntToStr(i)).c_str());
 }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Memo1->Cursor = (Controls::TCursor)2;
}
//---------------------------------------------------------------------------

Istotne jest, żeby przed użyciem kursora wczytać go do tablicy i najlepiej jest to zrobić wewnątrz konstruktora  klasy tak jak w powyższych przykładach, potem można dowolnie korzystać z tych kursorów w obrębie programu podając tylko numer tego kursora w zasobach.
Jeżeli korzystamy z niewielkiej liczby kursorów, np. tylko dwóch to dla ułatwienia możemy sobie zdefiniować nazwy tych kursorów i posługiwać się nazwami:

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

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"


#define APPSTAR2 1
#define DSMOVE   2


TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 Screen->Cursors[APPSTAR2] = LoadCursor((void *)HInstance, "KURSOR_1");
 Screen->Cursors[DSMOVE]   = LoadCursor((void *)HInstance, "KURSOR_2");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Memo1->Cursor = (Controls::TCursor)APPSTAR2;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Edit1->Cursor = (Controls::TCursor)DSMOVE;
}
//---------------------------------------------------------------------------

Stosowanie definicji ma sens tylko w przypadku niewielkiej liczby kursorów, gdy chcemy sobie ułatwić pracę nad projektem, ponieważ łatwiej posługiwać się nazwami (definicjami) kursorów niż numerami. Nie ma konieczności nadawania definicjom kolejnych numerów, ale najlepiej jest zacząć od 1 i ciągnąć to po kolei. W powyższym przykładzie nadałem definicjom prawie takie same nazwy jak noszą kursory umieszczone w zasobach, czyli appstar2.ani - APPSTAR2, 3dsmove.cur - DSMOVE, nie mogłem użyć nazwy 3DSMOVE gdyż żadna nazwa nie może zaczynać się od cyfry.

...powrót do menu. 

Umieszczanie plików AVI w zasobach programu.

Pliki AVI umieszczamy w zasobach programu, tak jak każde inne pliki. Ten typ zasobu ma nazwę AVI. Umieszczamy w katalogu z projektem jakiś plik AVI zakodowany koniecznie kodekiem Microsoft RLE, można oczywiście użyć każdego innego dowolnego kodeka, ale w dalszej części porady pokażę jak wczytywać takie pliki z zasobów do komponentu TAnimate, a ten komponent rozpoznaje tylko kodek Microsoft RLE (MS-RLE). To jest dobry moment, żeby powiedzieć iż do kontrolki TMediaPlayer nie można wczytać plików z zasobów, gdyż ta kontrolka nie posiada żadnych mechanizmów, które by to umożliwiały.
Ci którzy nie mają odpowiedniego pliku AVI mogą go sobie pobrać stąd:
Gdy plik jest już gotowy, uruchamiamy Notatnik i umieszczamy w nim taki wpis:

 

KLEPSYDRA AVI "klepsydra.avi"

 

KLEPSYDRA          -    identyfikator zasobu

AVI                -    typ zasobu

klepsydra.avi      -    nazwa pliku który włączmy do zasobów, można też podać ścieżkę dostępu do pliku, jeżeli znajduje się w innym katalogu niż plik zasobów (*.rc), np.: c:\Windows\clock.avi (pojedyncza kreska ułamkowa odwrócona \)


Tak utworzony plik w Notatniku zapisujemy pod dowolną nazwą, ale zawsze z rozszerzeniem *.RC, np. zasoby.rc, plik oczywiście zapisujemy w tym samym katalogu w którym znajdują się wspomniane plik AVI. Przechodzimy do naszego projektu i wybieramy menu Project | Add to Project..., i włączamy do projektu nasz plik zasoby.rc. Od teraz nie musimy się już martwić kompilacją zasobów, gdyż podczas kompilacji całego projektu zasoby również zostaną automatycznie skompilowane i włączone do projektu.

Wczytanie pliku z zasobów do komponentu Animate jest banalnie proste:

 

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 Animate1->ResName = "KLEPSYDRA";
 Animate1->ResHandle = (int)HInstance;
 Animate1->Active = true; // aktywacja animacji
}
//---------------------------------------------------------------------------

Zachęcam do pobrania przykładowego projektu pokazującego jak można zrobić przeźroczystą animację przemieszczającą się po pulpicie. Działa prawidłowo tylko w Windows XP. Przykłądowy projekt w Borland Developer Studio 2006.

...powrót do menu. 

Wyświetlanie zawartości konsoli CMD.exe w Memo - wg. pomysłu kinio.

    W tej poradzie przedstawię sposób na przechwycenie zawartości treści wyświetlanych w konsoli CMD (Wiersz polecenia)  i wyświetlenie jej w obiekcie Memo. Autorem tego rozwiązania jest kinio, który przedstawił je na forum i zgodził się na umieszczenie porady, ku radości nas wszystkich. Tyle gwoli wstępu.

 

Na początek opis mechanizmu działania, dokładnie tak jak przedstawił to kini:

Program który uruchamia się w konsoli posiada swoje domyślne standardowe wejście jakim jest klawiatura, oraz standardowe wyjście którym jest konsola. Windows umożliwia przekierowanie danych zarówno ze standardowego wejścia i wyjścia na inne urządzenie. Przy pomocy konsoli efekt ten jest bardzo łatwo uzyskać wystarczy użyć specjalnych operatorów.

Jeżeli mamy jakiś program prog.exe który wyświetla nam w konsoli jakieś informacje, to bardzo łatwo możemy to przekierować na plik np. a.txt:

C:\>prog.exe > a.txt

Jeżeli nasz program także umożliwia wprowadzenie jakiś danych za pomocą klawiatury, to możemy zamiast klawiatury użyć np. pliku:

C:\>prog.exe < a.txt

No i w końcu jeżeli chcemy aby na zapisane w pliku dane program wydrukował odpowiedź do pliku to wystarczy napisać:

C:\>prog.exe < a.txt > b.txt

i w pliku b.txt będzie odpowiedź programu na dane z pliku a.txt.

Wstęp ten był potrzebny aby zrozumieć jak wywoływane są pliki wsadowe *.bat.
Tak naprawdę instrukcje zapisane w plikach wsadowych mogą być równie dobrze zapisane w zwykłych plikach tekstowych i uruchomione poleceniem przekierowania strumienia na program - interpreter tych instrukcji (w tym przypadku cmd.exe):

cmd.exe < polecenia_pliku_bat.txt

Czyli tylko rozszerzenie *.bat mówi systemowi że trzeba dokonać takiego przekierowania i dlatego do uruchomienia pliku wsadowego wystarczy na niego kliknąć.

Weźmy na przykład prosty plik wsadowy a.bat o treści:

echo konsola
copy a.txt b.txt
mkdir newdir

uruchamiając taki plik wsadowy w konsoli poleceniem:

a.bat

na ekranie pojawi się nam:

Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.

C:\>echo konsola
konsola

C:\>copy a.txt b.txt
        1 file(s) copied.

C:\>mkdir newdir
C:\>

W tym wypadku system przekierował treść pliku wsadowego na program cmd.exe a program cmd.exe pokazał rezultat na swoim standardowym wyjściu czyli konsoli. Czyli pisząc:

cmd.exe < polecenia_pliku_bat.txt > rezultat.txt

w pliku "rezultat.txt" otrzymamy powyższe linie które wcześniej się ukazały w konsoli, natomiast konsola będzie pusta.
 

Proponowane przeze mnie rozwiązanie to utworzenie właśnie mechanizmu (pipe) umożliwiającego przekierowanie danych na inne wejścia/wyjścia standardowe.
Pipe to jest programowy odpowiednik operatorów przekierowania: '<' '>'.

Więc w moim rozwiązaniu to wszystko działa tak:
Wczytujemy dowolny plik w formacie tekstowym w którym są zapisane instrukcje do wykonania przez jakiś program (tutaj cmd.exe). Następnie tworzymy interfejs przekierowań w obie strony. Tak aby można było pisać do procesu i wyciągać rezultaty (zupełnie jak w przypadku C:\>prog.exe < a.txt > b.txt), gdzie prog.exe to nasz cmd.exe, a.txt to nasz plik wsadowy a b.txt to np. Memo.
Interfejs pipe zawsze posiada dwa końce (jak to rura) jeden do czytania i drugi do pisania. Tak więc definiując pipe dla standardowego wyjścia mamy dwa uchwyty jeden do pisania na to wyjście a drugi do odczytu co tam zapisano. Podobnie jest dla wejścia. W sumie daje nam to 4 uchwyty.

 

Korzystanie z tego mechanizmu wymaga włączenia do projektu biblioteki pipe_ctrl, którą można pobrać stąd. Autorem biblioteki jest w całości również kinio. dlatego nie będę opisywał tutaj mechanizmu działania, a ograniczę się tylko do sposobu stosowania, a i to nie w pełnym wymiarze, czyli mówiąc prościej biblioteka posiada znacznie większe możliwości niż tutaj opisane.

 

W celu włączenia biblioteki do programu trzeba skopiować pliki pipe_ctrl.cpp i plipe_ctrl.h do katalogu z projektem, następnie poprzez menu Project -> Add to project... włączamy do projektu bibliotekę pipe_ctrl.cpp, natomiast w pliku nagłówkowym formularza (np. Unit1.h) w sekcji include dodajemy wpis: #include "pipe_ctrl.h".

W celu przetestowania mechanizmu proponuje utworzyć plik wsadowy BAT, np. copy.bat o treści:

 

@echo off

 

copy c:\temp\plik.txt d:\plik.txt

 

:end

 

Celem tak skonstruowanego pliku wsadowego jest skopiowanie pliku plik.txt z katalogu temp na dysku C: bezpośrednio na dysk D:
To tylko przykład, możecie stworzyć sobie dowolny plik wsadowy, na początek jednak niech nie będzie zbyt złożony. Teraz po uprzednim przygotowaniu projektu tak jak to opisałem wyżej, napiszemy kod, który uruchomi plik copy.bat (ten który utworzyliście), wykona polecenia w nim zawarte, jednak instrukcje nie będą wyświetlane w konsoli CMD lecz w obiekcie Memo1, który trzeba umieścić na formularzu. W pliku nagłówkowym projektu w sekcji private lub public deklarujemy obiekt klasy pipeCtrl. Występuje tutaj również deklaracja funkcji readpipe, którą autor (kinio) umieszcza poza klasą formularza, którą można moim zdaniem pominąć jeżeli zamierzamy korzystać z mechanizmu tylko w obrębie jednego formularza, jeżeli jednak chcemy mieć dostęp do całego mechanizmu nie tylko w jednostce Unit1, lecz również w innych jednostkach projektu to należy zadeklarować tą funkcję, a deklarację obiektu klasy pipeCtrl należy w takim przypadku umieścić w sekcji public. Nie do końca rozumiem celowość definiowania funkcji readpipe poza klasą klasą pipeCtrl, gdyż wydaje mi się iż można by zawrzeć wszystko wewnątrz klasy, nie będę jednak ingerował w bibliotekę, tylko skupię się na sposobie użycia:

 

Wyjaśnienie kinio:

A więc wyjaśniam. Konstruktor klasy pipeCtrl przyjmuje jako argument wskaźnik do funkcji postaci:
 

void (*_read_function)(char*)

 

Jest to wskaźnik do zwykłej funkcji, nie do metody czyli funkcji składowej klasy. Wskaźniki do metod (jak również pól) składowych klas mają nieco inną semantykę. Nie będę tego tutaj tłumaczył jak się je poprawnie definiuje, tylko powiem, że jeżeli ktoś koniecznie chce zdefiniować odpowiednik funkcji readpipe jako metodę jakiejś klasy to trzeba by przedefiniować konstruktor klasy (oraz wszystkie metody wykorzystujące ten wskaźnik) tak aby zamiast aktualnego wskaźnika przyjmował następujący:

 

void (__closure *_read_function)(char*)

 

 

// Plik nagłówkowy np. Unit1.h
//---------------------------------------------------------------------------

#ifndef Unit1H
#define Unit1H

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

//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
        TButton *Button1;
        TMemo *Memo1;
        void __fastcall Button1Click(TObject *Sender);
private: // User declarations

public: // User declarations
        __fastcall TForm1(TComponent* Owner);
        pipeCtrl* pc; // deklaracja obiektu klasy pipeCtrl
};
        void readpipe(char* __buff);
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif

Następnie umieszczamy w pliku źródłowym definicję funkcji rapidpipe, która podłącza obiekt Memo1 do obiektu klasy pipeCtrl. W zdarzeniu OnClick dla przycisku Button1 uruchomimy cały mechanizm ładowania pliku wsadowego i wyświetlania wykonywanych poleceń w Memo1. W pierwszej kolejności klasa pipeCtrl uruchamia konsolę CMD w trybie ukrytym, więc nie jest ona widoczna, następnie uruchamia plik wsadowy, konsola wykonuje zawarte w nim instrukcje, a treść instrukcji przekazuje do obiektu Memo1:

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

#include <vcl.h>
#include <stdio.h>
#pragma hdrstop

#include "Unit1.h"

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"

TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
  pc = NULL; // definicja obiektu klasy pipeCtrl
}
//---------------------------------------------------------------------------
void readpipe(char* __buff)
{
 String text = String(__buff);

 int pos = text.Pos("\n");
 while(pos > 0)
 {
  Form1->Memo1->Lines->Add(text.SubString(1, pos-1));
  text = text.Delete(1, pos);
  pos = text.Pos("\n");
 }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 String fileBAT = "c:\\temp\\copy.bat"; // określenie ścieżki dostępu do pliku wsadowego
 Memo1->Lines->Clear();

 char pathCMD[MAX_PATH];
 GetSystemDirectory(pathCMD, MAX_PATH);
 String console = (String)pathCMD + "\\cmd.exe"; // określenie ścieżki dostępu do konsoli CMD.exe

 
if(!FileExists(fileBAT)) return;

 if(pc != NULL)
 {
   pc = NULL;
 }


 pc = new pipeCtrl(console, readpipe); // podłączenie Memo1 do konsoli
 if(pc->execute() != PIPE_SUCCES)      // uruchomienie konsoli
 {
  MessageDlg(pc->last_error(), mtError, TMsgDlgButtons() << mbOK, 1);
  return;
 }

 if(pc->readPipe() != PIPE_SUCCES)
 {
  MessageDlg(pc->last_error(), mtError, TMsgDlgButtons() << mbOK, 1);
  return;
 }

 if(pc == NULL)
  return;

 if(pc->writeFile(fileBAT) != PIPE_SUCCES) // uruchomienie pliku wsadowego
 {
  MessageDlg(pc->last_error(), mtError, TMsgDlgButtons() << mbOK, 1);
  return;
 }

 if(pc->readPipe() != PIPE_SUCCES)
 {
  MessageDlg(pc->last_error(), mtError, TMsgDlgButtons() << mbOK, 1);
  return;
 }
}
//---------------------------------------------------------------------------

Podczas wykonywania instrukcji zawartych w pliku wsadowym konsola może napotkać problem wymagający interwencji użytkownika, czyli wyśle zapytanie co ma zrobić. Tak się np. stanie jeżeli wywołamy nasz plik wsadowy po raz drugi, próbując skopiować plik w to same miejsce konsola wyśle zapytanie, czy ma zastąpić istniejący plik. Możliwe odpowiedzi T N W (Tak/Nie/Wszystkie) możemy odpowiedzieć odpowiedzieć konsoli wpisując np. odpowiedź do obiektu Edit1 i wysłać ją po naciśnięciu ENTER:

void __fastcall TForm1::Edit1KeyDown(TObject *Sender, WORD &Key,
        TShiftState Shift)
{
 if(Key == 13) // 13 = ENTER
 {
  String text = Edit1->Text;

  if(pc == NULL)
    return;

  if(pc->writePipe(text) != PIPE_SUCCES)
  {
   MessageDlg(pc->last_error(), mtError, TMsgDlgButtons() << mbOK, 1);
   return;
  }
  pc->writePipe(text);
  if(pc->readPipe() != PIPE_SUCCES)
  {
   MessageDlg(pc->last_error(), mtError, TMsgDlgButtons() << mbOK, 1);
   return;
  }
 }
}

Kolejna funkcja oferowana przez klasę pipeCtrl to kill_proces umożliwiająca zamknięcie procesu, a co za tym idzie konsoli. Nie powoduje to zniszczenia obiektu klasy pipeCtrl wiec można ponownie otworzyć nowy lub ten sam plik.

void __fastcall TForm1::Button3Click(TObject *Sender)
{
 if(pc == NULL)
  return;

 if(pc->kill_proces() != PIPE_SUCCES)
 {
  delete pc;
  pc = NULL;
  MessageDlg(pc->last_error(), mtError, TMsgDlgButtons() << mbOK, 1);
  return;
 }
}

To nie wyczerpuje możliwości tej klasy, a jak zapewnia autor i nie ma powodu mu nie wierzyć, możliwe jest wykorzystanie klasy pipeCtrl do uruchamiania nie tylko konsoli CMD, ale do wszystkich programów konsolowych.

kinio:

Chodzi o to że CMD jest także programem konsolowym, czyli takim którego standardowym wyjściem jest ekran konsoli. Biblioteka przechwytuje zawartość standartowego wejścia/wyjścia niezależnie od tego jaki program na ten interfejs wystawia dane. Może to być CMD, jak też również zwykły program drukujący hello world za pomocą printf, czy cout. Pipe (rura) służy do połączenia dwóch procesów i przesyłania pomiędzy nimi danych, niezależnie jakie są to procesy.

...powrót do menu. 

Pobieranie informacji o wersji BIOS', typie procesora itp.

Informacje o wersji BIOS'u typie procesora i innych urządzeniach komputera można pobrać z rejestru, z klucza HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System (WinXP). Niestety nie da się ich odczytać w prosty sposób poprzez klasę TRegistry, gdyż te informacje są zabezpieczone przed edycją,a ich odczyt wymaga nadania specjalnych uprawnień. Próba pobrania tych wartości poprzez klasę TRegistry i funkcję ReadString zakończy się niepowodzeniem, dlatego należy posłużyć się dwiema funkcjami RegOpenKeyEx - otwierającą ten specyficzny klucz i RegQueryValueEx - zwracającą wartości z określonego klucza.

Nie będę szczegółowo opisywał tych funkcji, gdyż taki opis znajduje się w plikach pomocy do BCB, ograniczę się do dwóch przykładów.

Pierwszy przykład pokazuje jak pobrać wersję BIOSU z klucza HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System - SystemBiosVersion. Tutaj małe wyjaśnienie, otóż wartość zwracana z tego klucza zawiera w łańcuchu wartość NULL, a jeżeli zmienna typu AnsiString napotka taki znak, to nie wyświetli tego co się poza nim znajduje, dlatego w poniższym przykładzie te znaki będą zastąpione spacją, lub znakiem łamania linii. Poniższy przykład składa się z dwóch kodów, w pierwszym wersja BIOS'u jest wyświetlana w  liniach obiektu Memo i tutaj znak NULL jest zastępowany znakiem lamania linii, a w drugim w jednej linii w obiekcie Edit i tutaj znak NULL jest zastępowany spacją:

 

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 HKEY hKey;
 LONG Res1,Res2;
 DWORD cData = 255;
 TCHAR SystemBiosVersion[255] = {'\0'};
 Res1 = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System", NULL, KEY_QUERY_VALUE, &hKey);

 if(Res1 == ERROR_SUCCESS)
 {
  Res2 = RegQueryValueEx(hKey, "SystemBiosVersion", NULL, NULL, (LPBYTE)SystemBiosVersion, &cData);
  if(Res2 == ERROR_SUCCESS)
  {
   int i = 0;
   while(i < cData)
   {
    if(SystemBiosVersion[i] == '\0') SystemBiosVersion[i] = '\r';
    i++;
   }
   Memo1->Lines->SetText(SystemBiosVersion);
  }
  else
  {
   Application->MessageBox("RegQueryValueEx: SystemBiosVersion", "ERROR", MB_OK | MB_ICONSTOP);
  }
 }
 else
 {
  Application->MessageBox("RegQueryValueEx: SystemBiosVersion", "ERROR", MB_OK | MB_ICONSTOP);
 }

 RegCloseKey(hKey);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 HKEY hKey;
 LONG Res1,Res2;
 DWORD cData = 255;
 TCHAR SystemBiosVersion[255] = {'\0'};
 Res1 = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System", NULL, KEY_QUERY_VALUE, &hKey);

 if(Res1 == ERROR_SUCCESS)
 {
  Res2 = RegQueryValueEx(hKey, "SystemBiosVersion", NULL, NULL, (LPBYTE)SystemBiosVersion, &cData);
  if(Res2==ERROR_SUCCESS)
  {
   int i = 0;
   while(i < cData)
   {
    if(SystemBiosVersion[i] == '\0') SystemBiosVersion[i] = ' ';
    i++;
   }
   Edit1->SetTextBuf(SystemBiosVersion);
  }
  else
  {
   Application->MessageBox("RegQueryValueEx: SystemBiosVersion", "ERROR", MB_OK | MB_ICONSTOP);
  }
 }
 else
 {
  Application->MessageBox("RegQueryValueEx: SystemBiosVersion", "ERROR", MB_OK | MB_ICONSTOP);
 }

 RegCloseKey(hKey);
}

Z odczytaniem informacji o procesorze będzie bardzo podobnie:

//---------------------------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
 HKEY hKey;
 LONG Res1,Res2;
 DWORD cData = 255;
 char *ProcessorNameString = "";
 Res1 = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", NULL, KEY_QUERY_VALUE, &hKey);

 if(Res1 == ERROR_SUCCESS)
 {
  Res2 = RegQueryValueEx(hKey, "ProcessorNameString", NULL, NULL, ProcessorNameString, &cData);
  if(Res2 == ERROR_SUCCESS)
  {
   Edit1->Text = ((String)ProcessorNameString).Trim();
  }
 }
 RegCloseKey(hKey);
}
//---------------------------------------------------------------------------

...powrót do menu. 

Usuwanie katalogu z całą zawartością.

O usuwaniu katalogów i plików udzieliłem już kilka porad. Rozwiązaniem jest np. usuwanie katalogu za pomocą latających folderów, ale to nie zawsze działa prawidłowo. To co zaprezentuję w tej poradzie nie jest niczym nowym, posłużę się funkcją wyszukiwania plików i podkatalogów (podfolderów) do usunięcia całego katalogu (folderu), gdyż niektórzy mają problemy z przerobieniem tej funkcji tak by nie tylko wyszukiwała pliki i katalogi, ale również je kasowała. Istnieją co prawda funkcje RemoveDir i RemoveDirectory, ale umożliwijają one usuwanie tylko pustych katalogów, niemniej jednak można je wykorzysta. Przedstawiona niżej funkcja usuwania katalogów z całą zawartością wyszukuje pliki i podkatalogi w katalogu który chcemy usunąć i w pierwszej kolejności usuwa pliki w przeszukiwanym katalogu lub podkatalogu, a następnie sam katalog lub podkatalog i tak do czasu aż wszystkie pliki i podkatalogi z zadanego katalogu zostaną usunięte, na samym końcu usuwany jest zadany katalog. Znajduje się tutaj również funkcja zmieniająca atrybuty plików i katalogów, jest ona konieczna, gdyż funkcje DeleteFile i RemoveDir nie usuną plików z atrybutem 'tylko do odczytu', dlatego przed usunięciem funkcja zmienia atrybuty wszystkich plików i folderów na archiwalny, by można było je usunąć.

 

//---------------------------------------------------------------------------
void __fastcall DeleteDir(String Dir)
{
 Dir = (Dir.SubString(Dir.Length(), 1) != "\\") ? Dir + "\\" : Dir;
 TSearchRec sr;

 if(FindFirst(Dir + "*.*", faAnyFile, sr) == 0)
 {
  do
  {
   if(((sr.Attr & faDirectory) > 0) & (sr.Name != ".") & (sr.Name != ".."))
   {
    DeleteDir(Dir + sr.Name + "\\"); // tutaj następuje rekurencyjne wywołanie funkcji
   }
   FileSetAttr(Dir + sr.Name, faArchive); // zmiana atrybutów plików i podkatalogów na archiwalny
   DeleteFile(Dir + sr.Name); // tutaj są kasowane pliki w katalogu (podkatalogu)
   RemoveDir(Dir + sr.Name);  // tutaj jest kasowany podkatalog w zadanym katalogu
  }
  while(FindNext(sr) == 0);
  FindClose(sr);
 }
 RemoveDir(Dir); // na koniec kasowany jest zadany katalog
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Screen->Cursor = crHourGlass; // zmiana kursora na zajęty
 DeleteDir("c:\\temp");
 Screen->Cursor = crDefault; // przywrócenie domyślnego kursora
}
//---------------------------------------------------------------------------

To by było na tyle, jeżeli chodzi o usuwanie katalogu z całą zawartością, chciałbym jednak zwrócić uwagę na pewien fragment kodu:

Dir = (Dir.SubString(Dir.Length(), 1) != "\\") ? Dir + "\\" : Dir;

Co to takiego? Początkujący programiści mogą tego nie wiedzieć, ale jest to wyrażenie warunkowe if(warunek) else polecenie lub nowy warunek, jest to jednak zapis skrócony wykorzystujący operatory warunku ? i :
Te operatory zwracają jedną z dwóch możliwych wartości. Jeżeli warunek zamieszczony przed operatorem
? jest prawdziwy to zwracana jest wartość podana za operatorem ?, jeżeli natomiast jest nieprawdziwy to zwracana jest wartość podana za operatorem :
Warunek użyty w funkcji DeleteDir można by zapisać w sposób standardowy i miałby on taką postać:

  if(Dir.SubString(Dir.Length(), 1) != "\\")
  {
    Dir = Dir + "\\";
  }
  else
  {
    Dir = Dir;
  }

Takiego skróconego zapisu można użyć tylko wtedy gdy nasz warunek zakłada zwrócenie jednej z dwóch możliwych wartości, czyli odpowiada konstrukcji if(warunek) wartość1 else nowy_warunek_lub_wartość_2.
W funkcji DeleteDir ten warunek sprawdza czy na końcu podanej ścieżki dostępu do katalogu, który ma być usunięty, znajduje się kreska ułamkowa odwrócona, jeżeli nie to jest dodawana do końca ścieżki.

...powrót do menu.