Kopiowanie plików

  Żeby skopiować plik należy posłużyć się funkcją Windows API 'CopyFile()'. Pierwszy parametr określa lokalizację pliku do skopiowania, drugi parametr określa miejsce docelowe w które zostanie skopiowany plik, trzeci parametr określa co należy zrobić jeśli w miejscu docelowym już taki plik istnieje. Trzeci parametr decyduje o tym, czy plik ma zastąpić istniejący plik. Jeżeli podamy wartość true, a w podanej lokalizacji znajduje się już plik o takiej nazwie to kopiowanie zostanie przerwane, przy wartości false plik znajdujący się w podanej lokalizacji zostanie zastąpiony nowym plikiem. W podanym przykładzie plik 'c:\windows\config.txt' zostanie skopiowany do katalogu 'c:\temp' i jeżeli będzie tam już znajdował się plik o tej samej nazwie (config.txt) to zostanie zastąpiony (wartość ustawiona na false).

//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 CopyFile("C:\\windows\\config.txt", "C:\\temp\\config.txt", false);
}
//--------------------------------

Poniżej znajduje się przykład praktycznego zastosowania tej funkcji. W podanym przykładzie jeżeli w miejscu docelowym znajduje się już taki sam plik jak ten kopiowany to jego nazwa zostanie zmieniona przez dodanie przedrostka 'kopia_'.

//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 String a = "config.txt";
 if(CopyFile("C:\\windows\\config.txt", ("C:\\temp\\" + a).c_str(), true) == false)
 {
  a = "kopia_" + a;
  CopyFile("C:\\windows\\config.txt", ("C:\\temp\\" + a).c_str(), true);
 }

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

 W powyższym przykładzie można jeszcze wiele poprawić tak, żeby zoptymalizować kod, więc oto przykład takiej modyfikacji:

// W pliku nagłówkowym projektu (np. Unit1.h), w sekcji private: należy umieścić nastęujący wiersz:

private:
void __fastcall Kopiuj(String FileNameA, String Path, String FileNameB);

// Następnie w pliku źródłowym projektu (np. Unit1.cpp), należy umieścić nastęującą procedurę:

//--------------------------------
void __fastcall TForm1::Kopiuj(String FileNameA, String Path, String FileNameB)
{
  if(FileExists(Path + FileNameB))FileNameB = "kopia_" + FileNameB;
  CopyFile(FileNameA.c_str(), (Path + FileNameB).c_str(), false);
}
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  Kopiuj("C:\\Windows\\config.txt", "C:\\Temp\\", "config.txt");
}
//--------------------------------

Oczywiście i ten przykład nie wyczerpuje wszystkich możliwości.
Opis funkcji: CopyFile(const char* lpExistingFileName, const char* lpNewFileName, int bFaillfExists).

...powrót do menu. 

Tworzenie okien komunikatów o dowolnym kształcie.

  W celu utworzenia okna komunikatu (znanego bardziej pod nazwą MessageBox) w dowolnym kształcie posłużymy się dwiema funkcjami API, a mianowicie funkcją - 'CreateMessageDialog(AnsiString Msg, TMsgDlgType DlgType, TMsgDlgButtons Buttons)' oraz funkcją - 'CreateRoundRectRgn(int X1, int Y1, int X2, int Y2, int X3, int Y3)' - szczegółowy opis tej funkcji znajduje się w dziale porad: Formularz. Przechodzimy do pliku źródłowego (np. Unit1.cpp) i umieszczamy na nim komponent 'Button1', następnie w zdarzeniu 'OnClick' umieszczamy całą procedurę. Przykład:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button4Click(TObject *Sender)
{
TForm *frm = new TForm(this);
frm = CreateMessageDialog("To jest przykładowy tekst komunikatu", mtCustom, TMsgDlgButtons() << mbOK);
HRGN rgn;
rgn = CreateRoundRectRgn(0, 0, frm->Width, frm->Height, 20, 20);
SetWindowRgn(frm->Handle, rgn, true);
DeleteObject(rgn);
frm->ShowModal();
}
//--------------------------------

No i tyle wystarczy. W podanym przykładzie zostanie utworzone okno w kształcie prostokąta z zaokrąglonymi rogami. Opis innych kształtów jak podałem wcześniej dostępny jest w dziale porady: Formularz. W powyższym przykładzie okno zostało zdefiniowane jako typ mtCustom, lecz dostępne są również inne typy:

  1. mtWarning - ostrzeżenie,
  2. mtErroe - błąd,
  3. mtInformation - informacja,
  4. mtConfirmation - pytanie,
  5. mtCustom - bez stylu.

Jeśli natomiast chodzi o przyciski, to dostępne są następujące typy:

  1. mbYes - tak,
  2. mbNo - nie,
  3. mbOK - OK,
  4. mbCancel - anuluj,
  5. mbAbort - przerwij.
  6. mbRetry - gotowe,
  7. mbIgnore - ignoruj,
  8. mbAll - wszystkie,
  9. mbNoToAll - nie na wszystkie,
  10. mbYesToAll - tak na wszystkie,
  11. mbHelp - pomoc,

Jest jedno "ale", a mianowicie napisy na przyciskach będą w angielskiej wersji językowej.

...powrót do menu. 


Zmiana rozdzielczości ekranu.

    Do zmiany rozdzielczości i głębi kolorów służy funkcja API ChangeDisplaySettings. Stworzymy funkcję, która będzie pobierała trzy parametry typu DWORD z których pierwszy będzie określał liczbę kolorów do wyświetlenia, a pozostałe dwa będą określały szerokość i wysokość wyświetlania. Funkcję nazwałem ResizeWindow, ale nazwa jest oczywiście dowolna. Tak więc tworzymy nowy projekt i przechodzimy do pliku nagłówkowego (np. Unit1.h), i w sekcji private deklarujemy funkcję ResizeWindow:

// Plik nagłówkowy np. Unit1.h
//--------------------------------
private:
        void __fastcall
ResizeWindow(DWORD bitColor, DWORD width, DWORD height);
//--------------------------------

Następnie przechodzimy do pliku źródłowego i definiujemy zadeklarowaną funkcję, umieszczając od razu kod potrzebny do zmiany rozdzielczości ekranu:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::ResizeWindow(DWORD bitColor, DWORD width, DWORD height)
{
 DEVMODE dm;
 dm.dmSize = sizeof(DEVMODE);

 int index = 0;
 while(EnumDisplaySettings(NULL, index, &dm))
 {
  if(dm.dmBitsPerPel == bitColor && dm.dmPelsWidth == width && dm.dmPelsHeight == height)
  {
   dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
   LONG result = ChangeDisplaySettings(&dm, CDS_TEST);
   if(result == DISP_CHANGE_SUCCESSFUL)
   {
    ChangeDisplaySettings(&dm, 0);
    break;
   }
   else
    if(result == DISP_CHANGE_RESTART)
    {
     ShowMessage("Żeby wprowadzić zmiany trzeba zrestartować komputer!");
     break;
    }
    else
    {
     ShowMessage("Wprowadzono nieprawidłowe parametry!");
     break;
    }
  }
 index++;
 }
}
//--------------------------------

Teraz, żeby zmienić rozdzielczość ekranu wystarczy np. umieścić na formularzu przycisk Button1 i w zdarzeniu OnClick wywołać funkcję ResizeWindow podając jako parametry żądane głębię kolorów, szerokość i wysokość ekranu:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 ResizeWindow(32, 800, 600);
}
//--------------------------------

W przykładzie ustawiłem głębię kolorów na 32 bity oraz rozdzielczość ekranu na 800 x 600.
Opis funkcji: ChangeDisplaySettings(_devicemodeA * IpDevMode, unsigned long dwFlags).

...powrót do menu. 

Odczyt używanego trybu graficznego (rozdzielczość i jakość kolorów).

    Do sprawdzenia ilu kolorów używa w danej chwili system - chodzi o liczbę bitów przypadających na jeden piksel - służy funkcja GetDeviceCaps:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 int bitPerPixel = GetDeviceCaps(Canvas->Handle, BITSPIXEL);
 Label1->Caption = "Bieżący tryb graficzny - " + IntToStr(bitPerPixel) + " bity";
}
//--------------------------------

Odczyt rozdzielczości jest znacznie prostszy i można go zrealizować na dwa sposoby;

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 int width  = Screen->Width; // mierzy szerokość ekranu.
 
int height = Screen->Height; // mierzy wysokość ekranu.
 Label1->Caption = "Rozdzielczość ekranu - " + IntToStr(width) + " x " + IntToStr(height);
}
//--------------------------------

Można się również posłużyć funkcja GetSystemMetrics:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 int width  =  GetSystemMetrics(SM_CXSCREEN);// mierzy szerokość ekranu.
 
int height =  GetSystemMetrics(SM_CYSCREEN);// mierzy wysokość ekranu.
 Label1->Caption = "Rozdzielczość ekranu - " + IntToStr(width) + " x " + IntToStr(height);
}
//--------------------------------

Opis funkcji: GetDeviceCaps : int (void*, int).
Opis funkcji: GetSystemMetrics : int (int).

...powrót do menu. 

Uruchamianie okna dialogowego do przeglądania katalogów.

    W swoim zestawie komponentów BCB zawiera obiekt TOpenDialog jednak ten komponent pozwala tylko na wybieranie plików. W zestawie komponentów nie ma jednak obiektu, który pozwalałby na zaznaczanie katalogów, a często w swoich programach możemy spotkać się z koniecznością określenia katalogu i  wtedy potrzebne jest właśnie takie okno.
    Żeby wywołać okno dialogowe do przeglądania katalogów należy posłużyć się funkcją API - SHBrowseForFolder, jednak żeby skorzystać z tej funkcji trzeba w pliku źródłowym, w sekcji include zaimportować plik:
Shlobj.h.

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

#include "Unit1.h"
#include <Shlobj.h>

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

TForm1 *Form1
//--------------------------------

Następnie umieszczamy na formularzu (np. Form1) dwa obiekty typu TEdit - Edit1 i Edit2 oraz przycisk Button1. Przycisk posłuży do wywołania okienka dialogowego, natomiast do obiektu Edit1 zostanie wpisana ścieżka dostępu do wybranego przez nas katalogu, a w obiekcie Edit2 zostanie umieszczona sama tylko nazwa wybranego katalogu:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 BROWSEINFO info;
 char szDir[MAX_PATH];
 char szDisplayName[MAX_PATH];
 LPITEMIDLIST pidl;
 LPMALLOC pShellMalloc;

 if(SHGetMalloc(&pShellMalloc) == NO_ERROR)
 {
  memset(&info, 0x00,sizeof(info));
  info.hwndOwner = Handle;             //<-- patrz objaśnienie 1.
  info.pidlRoot = NULL;                //<-- patrz objaśnienie 2.
  info.pszDisplayName = szDisplayName; //<-- patrz objaśnienie 3.
  info.lpszTitle = "Tytuł okna";       //<-- patrz objaśnienie 4.
  info.ulFlags = BIF_RETURNONLYFSDIRS; //<-- patrz objaśnienie 5.
  info.lpfn = 0;                       //<-- patrz objaśnienie 6.
  pidl = SHBrowseForFolder(&info);

  if(pidl)
  {
   if(SHGetPathFromIDList(pidl, szDir))
   {
    Edit1->Text = szDir; //<-- tutaj następuje przekazanie ścieżki dostępu do wybranego katalogu.
   }
   Edit2->Text = info.pszDisplayName; //<-- tutaj następuje przekazanie nazwy wybranego katalogu.

   pShellMalloc->Free(pidl);
  }
  pShellMalloc->Release();
 }
}
//--------------------------------

Objaśnienia:

  1. Parametr hwndOwner określa uchwyt okna będącego właścicielem okienka dialogowego. W przykładzie wartość Handle odwołuje się do formularza w którym została umieszczona instrukcja czyli do Form1. Jeżeli jako wartość przekazujemy uchwyt do formularza, to wywołane okno dialogowe będzie oknem modalnym, gdybyśmy jednak przekazali wartość 0 to okienko dialogowe zostało by wywołane niezależnie od formularza.

  2. Parametr pidlRoot określa początkowy katalog, użytkownik nie może cofnąć się powyżej tego katalogu.
  3. Parametr pszDisplayName zapisuje tytuł wybranego katalogu. Powinien wskazywać na bufor, który jest w stanie przechować co najmniej MAX_PATH znaków. Tytuł katalogu nie jest tym samym co ścieżka dostępu.

  4. Parametr lpszTitle pozwala wyświetlić dowolny tekst nad drzewem katalogów.
  5. Parametr ulFlags pozwala wybrać typy przeglądanych folderów. Możliwe wartości to:
    • BIF_BROWSEFORCOMPUTER - przeglądanie otoczenia sieciowego.
    • BIF_BROWSEFORPRINTER - przeglądanie drukarek sieciowych.
    • BIF_DONTGOBELOWDOMAIN - zapobiega wyświetlaniu folderów sieciowych leżących poniżej poziomu domeny.
    • BIF_RETURNONLYFSDIRS - zwraca dyski i katalogi.
    • BIF_STATUSTEXT - wyświetla tekst na okienku dialogowym.
  6. Parametr lpfn określa wskaźnik do funkcji callback. Funkcja callback pozwala modyfikować zachowanie okienka dialogowego. Na przykład, można za jej pomocą włączać i wyłączać przycisk OK, można również przejść do konkretnego folderu, bądź wyświetlić tekst stanu.

Przykład wykorzystania funkcji callback:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
int __stdcall BrowseProc(HWND hwnd,UINT uMsg, LPARAM lParam, LPARAM lpData )
{
 char szDir[MAX_PATH];

 switch(uMsg)
 {
  case BFFM_INITIALIZED: SendMessage(hwnd, BFFM_SETSTATUSTEXT,0, (LPARAM)"Inny tekst");
                         SendMessage(hwnd, BFFM_SETSELECTION, TRUE, (LPARAM)"C:\\Windows");
                         break;
  case BFFM_SELCHANGED: if(SHGetPathFromIDList((LPITEMIDLIST)lParam, szDir))
                           Form1->Edit3->Text = szDir;
                        break;
 }
 return 0;
}
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 BROWSEINFO    info;
 char          szDir[MAX_PATH];
 char          szDisplayName[MAX_PATH];
 LPITEMIDLIST  pidl;
 LPMALLOC      pShellMalloc;

 if(SHGetMalloc(&pShellMalloc) == NO_ERROR)
 {
  memset(&info, 0x00, sizeof(info));
  info.hwndOwner = 0;
  info.pidlRoot  = NULL;
  info.pszDisplayName = szDisplayName;
  info.lpszTitle = "Tytuł okienka";
  info.ulFlags   = BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT;
  info.lpfn      = BrowseProc;            

  pidl = SHBrowseForFolder(&info);

  if(pidl)
  {
   if(SHGetPathFromIDList(pidl, szDir))
   {
    Edit1->Text = szDir;
   }
   Edit2->Text = info.pszDisplayName;

   pShellMalloc->Free(pidl);
   pShellMalloc->Release();
  }
 }
}
//--------------------------------

...powrót do menu. 

Usuwanie ikony programu z paska zadań.

    Zawsze gdy uruchamiamy jakiś  program to na pasku zadań (pasek zadań to taka belka najczęściej na dole pulpitu, no chyba że ktoś lubi inaczej) pojawia się ikona tego programu i to jest regułą. Jednak nie musi tak być zawsze. Istnieje prosta funkcja API - ShowWindow blokująca pojawienie się ikony programu na pasku zadań, można ją wywołać np. w zdarzeniu OnShow dla formularza Form1:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormShow(TObject *Sender)
{
 ShowWindow(Application->Handle, SW_HIDE);
}
//--------------------------------

Kluczem do ukrycia ikony programu jest parametr SW_HIDE, więc żeby ponownie przywołać ikonę na pasek zadań wystarczy wymienić ten parametr na SW_SHOW:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 ShowWindow(Application->Handle, SW_SHOW);
}
//--------------------------------

Niestety funkcja ShowWindow jest daleka od doskonałości i przy pierwszej minimalizacji lub maksymalizacji ikona pojawi się na pasku zadań. Żeby usunąć ten problem utworzymy funkcję, która będzie przechwytywała komunikat minimalizacji i maksymalizacji formularza po czym wywoła funkcję ShowWindow z parametrem SW_HIDE. W tym celu przechodzimy do pliku nagłówkowego i w sekcji private deklarujemy funkcję MiniMaxi, przy czym nazwa funkcji jest dowolna. Natomiast w sekcji public utworzymy mapę komunikatu:

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

public:
        __fastcall TForm1(TComponent* Owner);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_SYSCOMMAND, TMessage, MiniMaxi);
END_MESSAGE_MAP(TForm)
//--------------------------------

Teraz przechodzimy do pliku źródłowego i definiujemy funkcję MiniMaxi umieszczając od razu kod usuwający ikonę z paska zadań:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::MiniMaxi(TMessage& Msg)
{
 TForm::Dispatch(&Msg);
 if(Msg.WParam == SC_MAXIMIZE || Msg.WParam == SC_MINIMIZE)
 {
  ShowWindow(Application->Handle, SW_HIDE);
 }
}
//--------------------------------

I tutaj pojawia się pewne niebezpieczeństwo, jeżeli zminimalizujemy program, który nie posiada ikony na pasku zadań to nie będzie sposobu na to żeby go ponownie przywrócić, więc uważajcie.

...powrót do menu. 

Obsługa komunikatów (MessageBox).

    Pisząc o komunikatach mam na myśli takie okienka dialogowe, które wyskakują gdy program zwraca jakiś wyjątek lub wyświetla komunikaty ostrzegawcze lub informujące o jakimś zdarzeniu. Zapewne wszyscy wiedzą, że do wywoływania komunikatów służy funkcja ShowMessage i jest to najprostsza lecz nie jedyna metoda tworzenia komunikatu, a tak to wygląda:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 ShowMessage("Treść komunikatu");
}
//--------------------------------

...i to w zasadzie nie wymaga już chyba tłumaczenia.
    Jest jeszcze jedna funkcja umożliwiająca tworzenie komunikatu jest to MessageBox. Funkcja ta jest bardziej rozbudowana od ShowMessage ponieważ oprócz podania treści komunikatu umożliwia również podanie nazwy komunikatu wyświetlanej jako właściwość Caption okna komunikatu, możliwe jest również zdefiniowanie ikony która będzie wyświetlana z lewej strony treści komunikatu, no i można też zdefiniować przycisk lub kilka przycisków, które zostaną wyświetlone w oknie komunikatu.
Najprostszy sposób wywołania tej funkcji przedstawia się następująco:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 MessageBox(Handle, "Treść komunikatu", "Etykieta", MB_OK | MB_ICONINFORMATION);
}
//--------------------------------

Ciało funkcji wygląda następująco:

MessageBox(void * hWnd, const char * lpText, const char * lpCaption,  unsigned int uType)

  1. Jako pierwszy parametr - void *nWnd - podajemy uchwyt do okna z którego wywołujemy komunikat najlepiej jest podać parametr: Handle.

  2. Parametr drugi - const char * lpText - to treść komunikatu, przy czym jest to parametr typu char więc jeśli wprowadzimy tam tekst to wszystko jest w porządku jeżeli jednak będzie to treść łączona np. tekst i zawartość zmiennej typu String to należy dokonać konwersji do typu char, np:
    MessageBox(Handle, ("Treść komunikatu" + Edit1->Text).c_str(), "Etykieta", MB_OK | MB_ICONSTOP);

  3. Kolejny parametr - const char * lpCaption - to treść etykiety Caption okna komunikatu, zasada działania taka sama jak w przypadku parametru drugiego.

  4. Ostatni parametr - unsigned int uType - jest najbardziej złożony ponieważ tutaj właśnie definiuje się typ przycisku oraz typ ikony wyświetlanych w oknie komunikatu. Niżej przedstawiam dostępne typy.

Lista stylu przycisków:

Lista typów ikon:

Oprócz podanych stylów przycisków i typu ikon można również stosować modyfikatory:

    Jak widać oprócz standardowego przycisku "OK" możliwe jest również stosowanie przycisków "TAK", "NIE", "ANULUJ", "POWTÓRZ", "PRZERWIJ" i "IGNORUJ", a to z kolei oznacza, że można odpowiadać na komunikaty i w zależności od wybranej odpowiedzi podjąć odpowiednie działanie.
Załóżmy, że tworzymy komunikat który pyta użytkownika programu czy ma zapisać zmiany w jakimś tam pliku i w zależności od odpowiedzi, albo je zapisuje, albo nie:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 int iQuestion = MessageBox(Handle, "Czy chcesz zapisać zmiany do pliku?", "Zapisywanie zmian", MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2);

 if(iQuestion == ID_YES)
 {
  // ...jeżeli TAK następuje wykonanie tej instrukcji...
 }
 else
 {
  // ... jeżeli NIE następuje wykonanie tej instrukcji...
 }
}
//--------------------------------

Jak więc widać to na przykładzie zadeklarowano zmienną typu int i przypisano jej wartość zwracaną przez okno komunikatu, a następnie w zależności od tego, który przycisk został wybrany wykonywane są odpowiednie instrukcje. Oczywiście oprócz wartości ID_YES można stosować w sposób analogiczny pozostałe wartości, czyli: ID_NO, ID_CANCEL, ID_RETRY, ID_ABORT i ID_IGNORE.
Zamiast funkcji MessageBox najczęściej stosuje się funkcję złożoną, tworzoną dla całej aplikacji, a mianowicie:

Application->MessageBox(const char * lpText, const char * lpCaption,  unsigned int uType)

Różnica pomiędzy tymi funkcjami polega tylko na tym, że w przypadku Application->MessageBox nie stosuje się uchwytu okna, ponieważ funkcja jest przyporządkowana całej aplikacji a nie wybranemu formularzowi. Poza tym zasada używania tej funkcji jest identyczna jak w przypadku MessageBox.

...powrót do menu. 

Zmiana tapety pulpitu.

    Tapetę na pulpicie można zmienić, klikając prawym klawiszem myszy w jego dowolnym pustym miejscu i wybierając z menu kontekstowego 'Właściwości', a następnie zakładkę 'Pulpit'. Nie w tym jednak rzecz, można dokonać zmiany tapety na pulpicie za pomocą prostej funkcji API - SystemParametersInfo. Zróbmy to w zdarzeniu OnClick dla przycisku Button1 z wykorzystaniem komponentu OpenDialog1:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 if(OpenDialog1->Execute())
  SystemParametersInfo(SPI_SETDESKWALLPAPER, 0,
                       (void*)OpenDialog1->FileName.c_str(),
                       SPIF_UPDATEINIFILE|SPIF_SENDWININICHANGE);
}
//--------------------------------

...bez komentarza.

...powrót do menu. 

Tworzenie skrótów do plików.

    Jak tworzyć skróty w Windows chyba wszyscy wiedzą, więc daruję sobie opis. Jednak mało kto wie jak napisać program, który będzie tworzył skrót do wskazanego programu i na tym się skupię. Zanim przystąpimy do pisania specjalnej funkcji, która będzie tworzyła skróty należy dołączyć do projektu, w sekcji include plik shlobj.h:

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

#include "Unit1.h"
#include <shlobj.h>

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

TForm1 *Form1;
//--------------------------------

Następnie tworzymy w pliku źródłowym (bez deklarowania w pliku nagłówkowym - tak będzie prościej) funkcję o nazwie GetSpecialFolder, która będzie pobierała ścieżki dostępu do specjalnych katalogów takich jak Pulpit i Menu Start:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
AnsiString __fastcall GetSpecialFolder(int iFolder) // funkcja zwraca nazwę katalogu Pulpit, lub Menu Start
{
 char Path[MAX_PATH];
 ZeroMemory(Path, MAX_PATH);

 LPMALLOC lpMalloc;
 if(SUCCEEDED(SHGetMalloc(&lpMalloc)))
 {
  LPITEMIDLIST lpidl;
  if(SUCCEEDED(SHGetSpecialFolderLocation(Application->Handle,
                     iFolder, &lpidl)))
  {
      SHGetPathFromIDList(lpidl, Path);
      lpMalloc->Free(lpidl);
  }
  lpMalloc->Release();
 }

 return AnsiString(Path);
}
//--------------------------------

Teraz napiszemy funkcję CreateShortcutLink, która będzie się zajmowała tworzeniem skrótów:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
bool __fastcall CreateShortcutLink(AnsiString LinkDesc,
      AnsiString LinkPath, AnsiString TargetPath, AnsiString TargetArgs) //funkcja tworząca skróty.
{
 CoInitialize(NULL);

 bool retval = false;
 HRESULT hres = 0;
 IShellLink* psl = NULL;
 SetLastError(0);

 LPCLASSFACTORY pcf = NULL;
 hres = CoGetClassObject(CLSID_ShellLink,
                         CLSCTX_INPROC_SERVER,
                         NULL,
                         IID_IClassFactory,
                         (LPVOID*)&pcf);

 if(SUCCEEDED(hres))
 {
  hres = pcf->CreateInstance(NULL,
                             IID_IShellLink,
                             (LPVOID*)&psl);
                             pcf->Release();
 }
 if(SUCCEEDED(hres))
 {
  IPersistFile* ppf = NULL;
    // Ustawianie ścieżki, katalogu roboczego i opisu skrótu
  psl->SetPath(TargetPath.c_str());
  psl->SetArguments(TargetArgs.c_str());
  psl->SetDescription(LinkDesc.c_str());
  psl->SetWorkingDirectory(ExtractFilePath(TargetPath).c_str());
  hres = psl->QueryInterface(IID_IPersistFile, &(void*)ppf);

  if(SUCCEEDED(hres))
  {
   wchar_t wsz[MAX_PATH];
   MultiByteToWideChar(CP_ACP, 0, LinkPath.c_str(), -1, wsz, MAX_PATH);
   hres = ppf->Save(wsz, TRUE);
   ppf->Release();
  }
  psl->Release();
  retval = SUCCEEDED(hres);
 }

 switch(hres)
 {
  case S_OK:                  ShowMessage("Utworzono skrót.");                       break;
  case REGDB_E_CLASSNOTREG:   ShowMessage("Niezarejestrowana klasa."); break;
  case E_OUTOFMEMORY:         ShowMessage("Za mało miejsca w pamięci.");             break;
  case E_INVALIDARG:          ShowMessage("Nieprawidłowy parametr.");                break;
  case E_UNEXPECTED:          ShowMessage("Nieoczekiwany błąd.");                    break;
  case CLASS_E_NOAGGREGATION: ShowMessage("Nie można utworzyć klasy.");              break;
  case E_FAIL:                ShowMessage("Nie można utworzyć skrótu.");             break;
  case E_NOTIMPL:             ShowMessage("Nieobsługiwany interfejs.");               break;
  default:                    ShowMessage("Nieznany błąd.");
 }
 CoUninitialize();
 return retval;
}
-------------------------------

A teraz wywołujemy w zdarzeniach OnClick dla przycisków Button1, Button2 i Button3 stworzone funkcję w celu utworzenia skrótów na pulpicie w menu Start i we wskazanym katalogu:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject* Sender)
{
 AnsiString LinkPath = GetSpecialFolder(CSIDL_DESKTOP);
 LinkPath += "\\Nazwa_Programu.lnk";
 if(!CreateShortcutLink("Opis", LinkPath, Application->ExeName, ""))
 {
  // komunikat pokazujący się w przypadku błędu.
 }
}
//--------------------------------
void __fastcall TForm1::Button2Click(TObject* Sender)
{
 AnsiString LinkPath = GetSpecialFolder(CSIDL_STARTMENU);
 LinkPath += "\\Nazwa_Programu.lnk";
 if(!CreateShortcutLink("Opis", LinkPath, Application->ExeName, ""))
 {
  // komunikat pokazujący się w przypadku błędu.
 }
}
//--------------------------------
void __fastcall TForm1::Button3Click(TObject* Sender)
{
 AnsiString LinkPath = "C:\\Nazwa_Katalogu\\Nazwa_Programu.lnk";
 if(!CreateShortcutLink("Opis", LinkPath, Application->ExeName, ""))
 {
  // komunikat pokazujący się w przypadku błędu.
 }
}
//--------------------------------

...powrót do menu. 

Tworzenie aplikacji wielowątkowych. (uzupełnione wg. sugestii Sebastiana Cwynar - dvlart)

    Do tworzenia aplikacji wielowątkowych używa się funkcji BeginThread, a deklaracja tejże funkcji wygląda tak:

int __fastcall BeginThread(void * SecurityAttributes, unsigned StackSize, ThreadFunc ThreadFunc, void * Parameter, unsigned CreationFlags, unsigned &ThreadId);

Przedstawiona funkcja rozpoczyna wątek w programie wielowątkowym poprzez wywołanie funkcji API CreateThread, która rozpoczyna nowy wątek i wywołuje funkcję ThreadFunc w kontekście nowego wątku. Wątek zostaje zakończony w chwili powrotu funkcji. BeginThread zwraca identyfikator nowego wątku lub zero jeżeli system nie może utworzyć nowego wątku. Najłatwiej będzie to wytłumaczyć na przykładzie. W tym celu umieszczamy na formularzu pięć obiektów: Edit1, Edit2, Edit3, Button1 i Button2. Żeby utworzyć nowy wątek trzeba mu przydzielić identyfikator W_ID (nazwa dowolna) oraz pseudoidentyfikator W_PD (nazwa dowolna). Zadeklarujemy je w pliku nagłówkowym w sekcji private:

// Plik nagłówkowy np. Unit1.h
//--------------------------------
private:
        int W_ID;
        unsigned int W_PD;
//--------------------------------

Następnie przechodzimy do pliku źródłowego i definiujemy w nim dwie funkcje Wyliczenia1 i Wyliczenia2 (nazwy dowolna) typu TThreadFunc, deklaracje tych funkcji wyglądają tak:

Integer __fastcall (*THreadFunc)(Pointer Parameter);

więc tworzone przez nas funkcje będą wyglądały następująco:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
int __fastcall Wyliczenia1(Pointer Parameter)
{
 for(int i = 0; i < 1000000; i++)
 {
  Form1->Edit1->Text = IntToStr(i);
  Sleep(100);
 }
 ExitThread(GetExitCodeThread(Wyliczenia1, NULL)); // usunięcie wątku z pamięci, od tego momentu wątku nie można już wstrzymać.
}
//--------------------------------
int __fastcall Wyliczenia2(Pointer Parameter)
{
 for(int i = 1000000; i > 0; i--)
 {
  Form1->Edit2->Text = IntToStr(i);
  Sleep(100);
 }
 ExitThread(GetExitCodeThread(Wyliczenia2, NULL)); // usunięcie wątku z pamięci, od tego momentu wątku nie można już wstrzymać.
}
//--------------------------------

Utworzyliśmy dwie funkcje ponieważ chcę pokazać jak będą działały jednocześnie. Wewnątrz funkcji zostały utworzone pętle zadaniem których jest tylko odliczanie w pierwszym przypadku od zera do miliona a w drugim odwrotnie od miliona do zera. Funkcja Sleep służy do opóźniania wykonywania pętli. Wątki wywołujemy w zdarzeniach OnClick dla przycisków Button1 i Button2:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
int __fastcall Wyliczenia1(Pointer Parameter)
{
 for(int i = 0; i < 1000000; i++)
 {
  Form1->Edit1->Text = IntToStr(i);
  Sleep(100);
 }
}
//--------------------------------
int __fastcall Wyliczenia2(Pointer Parameter)
{
 for(int i = 1000000; i > 0; i--)
 {
  Form1->Edit2->Text = IntToStr(i);
  Sleep(100);
 }
}
//--------------------------------
void __fastcall TForm1::Button1Click(TObject* Sender)
{
 W_ID = BeginThread(NULL, 0, Wyliczenia1, this, 0, W_PD);
}
//--------------------------------
void __fastcall TForm1::Button2Click(TObject* Sender)
{
 W_ID = BeginThread(NULL, 0, Wyliczenia2, this, 0, W_PD);
}
//--------------------------------

Po skompilowaniu programu uruchamiamy wątki klikając na przyciski i obserwujemy co się dzieje. Jak widać obydwie pętle działają niezależnie i nie przeszkadzają sobie wzajemnie, dodatkowo możemy np. swobodnie wpisywać tekst do obiektu Edit3.
Na zakończenie wspomnę tylko o dwóch dodatkowych funkcjach, a mianowicie
SuspendThread służącej do czasowego wstrzymywania wykonywania wątku, oraz ResumeThread wznawiającej jego wykonywanie. Wywołuje się je bardzo łatwo:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button3Click(TObject* Sender)
{
 SuspendThread((Pointer)W_ID);
}
//--------------------------------
void __fastcall TForm1::Button4Click(TObject* Sender)
{
 ResumeThread((Pointer)W_ID);
}
//--------------------------------

...powrót do menu. 

Zamykanie, logowanie i restartowanie systemu.

    W celu wyłączenia, logowania i restartowania systemu Windows, za pomocą własnej aplikacji, należy posłużyć się funkcją:

BOOL ExitWindowsEx(unsigned int uFlags, unsigned int dwReserved);

Jak widać funkcja pobiera dwa parametry, pierwszy parametr uFlags określa rodzaj operacji która będzie wykonywana. Można w to miejsce wstawić następujące wartości:

EWX_FORCE Ten parametr zamyka system, jednak gdy ta flaga jest ustawiona system nie wysyła komunikatów WM_QUERYENDSESSION i WM_ENDSESSION do aktualnie otwartych w systemie programów. To może powodować gubienie danych przez te programy, dlatego tej flagi należy używać tylko w nagłych wypadkach.
EWX_LOGOFF Zamyka bezpiecznie wszystkie procesy i wylogowuje użytkownika z systemu.
EWX_POWEROFF Zamyka system i wyłącza zasilanie. System musi obsługiwać funkcję wyłączania zasilania. Jest to cecha typowa dla systemów Windows NT/2000.
EWX_REBOOT Restartuje system, czyli zamyka go i ponownie uruchamia.
EWX_SHUTDOWN Kończy bezpiecznie wszystkie procesy, zapisuje bufory na dysk twardy i bezpiecznie zamyka system.

Co się zaś tyczy drugiego parametru dwReserved to jest to po prostu zwrot, jako argument podajemy zero. Jeżeli operacja się nie powiedzie to zostanie zwrócony komunikat błędu.

Przykłady wywołania funkcji:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject* Sender)
{
 ExitWindowsEx(EWX_FORCE, 0);
}
//--------------------------------
void __fastcall TForm1::Button2Click(TObject* Sender)
{
 ExitWindowsEx(EWX_LOGOFF, 0);
}
//--------------------------------
void __fastcall TForm1::Button3Click(TObject* Sender)
{
 ExitWindowsEx(EWX_POWEROFF, 0);
}
//--------------------------------
void __fastcall TForm1::Button4Click(TObject* Sender)
{
 ExitWindowsEx(EWX_REBOOT, 0);
}
//--------------------------------
void __fastcall TForm1::Button4Click(TObject* Sender)
{
 ExitWindowsEx(EWX_SHUTDOWN, 0);
}
//--------------------------------
void __fastcall TForm1::Button4Click(TObject* Sender)
{
 ExitWindowsEx(EWX_SHUTDOWN | EWX_FORCE, 0);
}
//--------------------------------

Przedstawiona funkcja nie sprawdza się w przypadku systemu Windows XP. Jednak i z tym problemem można sobie poradzić. Rozwiązanie nadesłał Jakub BACIC, pozwoliłem sobie jednak nieco rozwinąć jego pomysł.
Do zamykania, logowania i restartowania systemu Windows XP wykorzystamy funkcję WinExec przedstawioną w poradzie uruchamianie innego programu - sposób łatwy, lecz zamiast podawania jako parametru ścieżki do programu wstawimy po prostu odpowiednie polecenie. Do wykonania opisywanych operacji należy posłużyć się poleceniem shutdown z odpowiednimi parametrami.

Składnia polecenia wygląda tak i jest dostępna w pliku pomocy Windows pod hasłem shutdown:

shutdown [{-l|-s|-r|-a}] [-f] [-m [\\nazwa_komputera]] [-t xx] [-c "komunikat"] [-d[u][p]:xx:yy]

Parametry:
-l Wylogowuje bieżącego użytkownika (parametr domyślny). Parametr -m nazwa_komputera ma pierwszeństwo.
-s Wyłącza komputer lokalny.
-r Wykonuje ponowny rozruch po wyłączeniu komputera.
-a Przerywa proces wyłączania. Ignoruje inne parametry z wyjątkiem parametrów -l i nazwa_komputera. Parametru -a można używać tylko w okresie nie przekraczającym limitu czasu.
-f Wymusza zamknięcie uruchomionych aplikacji.
-m [\\nazwa_komputera] Określa komputer, który należy wyłączyć.
-t xx Ustawia czasomierz na zamknięcie systemu na xx sekund. Wartość domyślna wynosi 20 sekund.
-c "komunikat" Określa komunikat, który ma być wyświetlony w obszarze Komunikat okna Zamykanie systemu. Komunikat może składać się z maksymalnie 127 znaków. Komunikat należy wpisać w cudzysłowach.
-d [u][p]:xx:yy Wyświetla listę kodów przyczyn zamknięcia. W następującej tabeli podano listę różnych wartości.

 

Wartość Opis
u Wskazuje kod użytkownika.
p Wskazuje kod planowanego wyłączenia.
xx Określa główny kod przyczyny (0-255).
yy Określa podrzędny kod przyczyny (0-65536).

Przykłady:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject* Sender)
{
 WinExec("shutdown -l", SW_SHOW); // wylogowanie użytkownika
}
//--------------------------------
void __fastcall TForm1::Button2Click(TObject* Sender)
{
 WinExec("shutdown -s", SW_SHOW); // wyłączenie systemu po 30 sekundach.
}
//--------------------------------
void __fastcall TForm1::Button3Click(TObject* Sender)
{
 WinExec("shutdown -s -t 20", SW_SHOW); // wyłączenie systemu po 20 sekundach - patrz: -t 20.
}
//--------------------------------
void __fastcall TForm1::Button4Click(TObject* Sender)
{
 WinExec("shutdown -s -t 20 -c \"Treść komunikatu.\"", SW_SHOW); // wyłączenie systemu po 20 sekundach z wyświetleniem komunikatu.
}
//--------------------------------
void __fastcall TForm1::Button4Click(TObject* Sender)
{
 WinExec("shutdown -r", SW_SHOW); //restartuje system.
}
//--------------------------------

 

Cała sztuka opiera się po prostu na wywołaniu programu shutdown.exe, który znajduje się w katalogu systemowym - ...\Windows\System32\. Jako ciekawostkę dodam tylko, że zastosowanie tego sposobu wyłączania systemu WinXP spowoduje wyświetlenie na zakończenie komunikatu "Można teraz bezpiecznie zamknąć system", z którym nie spotkałem się dotychczas w tym systemie ponieważ po wybraniu odpowiedniego polecenia w menu Start system po prostu całkowicie się wyłączał sam.

...powrót do menu. 

Zamykanie windows z wyłączeniem zasilania za pomocą funkcji ExitWindowsEx.

W poradzie "zamykanie, logowanie i restartowanie systemu" napisałem, że w Windows XP nie działa funkcja ExitWindowsEx, otóż niejaki WRonX, użytkownik forum, jeden chyba z nielicznych, którzy się na tym znają, wyprowadził mnie z błędu, więc oto sposób na wyłączenie Windows XP włącznie z zasilaniem - o ile system obsługuje taką funkcję:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button3Click(TObject* Sender)
{
 HANDLE hToken;
 TOKEN_PRIVILEGES tkp;
 OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken); LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid);
 tkp.PrivilegeCount = 1;
 tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
 AdjustTokenPrivileges(hToken, FALSE, &tkp, 0,(PTOKEN_PRIVILEGES)NULL, 0);

 ExitWindowsEx(EWX_FORCE | EWX_POWEROFF, 0);
}
//--------------------------------

opracował: WRonX.

...powrót do menu. 

Powiązanie własnego typu pliku z własnym programem.

Często się zdarza, że pisząc program tworzymy dla niego pliki, które powinny być powiązane z tymże programem i po kliknięciu na taki plik powinny się w nim otwierać. Zrealizowanie tego zadanie jest bardzo proste i składa się z dwóch etapów, pierwszy to przystosowanie naszego programu do takiego otwierania plików i realizujemy to za pomocą funkcji ParamStr, a drugi etap to zarejestrowanie rozszerzenia pliku z poziomu naszego programu, chociaż można to też zrobić z poziomu systemu, jednak bardziej praktycznie jest powiązać plik z programem z poziomu tegoż programu.

Etap pierwszy - przygotowanie programu. Załóżmy, że tworzymy plik z dowolnym rozszerzeniem i chcemy, żeby ten typ pliku był otwierany przez nasz program, a zawartość tego pliku, żeby wyświetlała się w obiekcie Memo1, jedyne co musimy zrobić to umieścić w konstruktorze klasy ładowanie pliku do obiektu Memo1 poprzez punkcję ParamStr przechwytującą drugi parametr, dlaczego drugi ponieważ pierwszy parametr to ścieżka dostępu do naszego programu, a drugi parametr to będzie ścieżka dostępu do uruchamianego pliku, po tym jak już zarejestrujemy rozszerzenie tegoż pliku, kod jest banalnie prosty:

// Plik źródłowy np. Unit1.cpp
//------------------------ konstruktor klasy
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 try
 {
  Memo1->Lines->LoadFromFile(ParamStr(1));
 }
 catch(...)
 {
  MessageBox("Ten format pliku nie jest obsługiwany przez ten program.");
 }
}
//--------------------------------

Etap drugi - rejestrowanie rozszerzenia pliku w następnej poradzie rejestrowanie rozszerzenia pliku.

...powrót do menu. 

Rejestrowanie rozszerzenia pliku.

Gdy tworzymy program, który będzie zapisywał swoje dane do pliku w naszym własnym formacie warto jest zarejestrować w systemie jego rozszerzenie. Stwarza to następujące możliwości:

Rejestracja oczywiście odbywa się w rejestrze więc trzeba skorzystać z klasy TRegistry, opis tej klasy można znaleźć w artykule obsługa rejestru windows, do projektu należy dołączyć bibliotekę registry.hpp, a oto przykład rejestracji:

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

#include <registry.hpp>
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
   //rejestracja rozszerzenia
   TRegistry *Reg = new TRegistry();
   Reg->RootKey = HKEY_CLASSES_ROOT;
   Reg->OpenKey(".roz", true);
   Reg->WriteString("", "roz_auto_file");
   Reg->CloseKey();

   //otwarcie pliku po jego dwukrotnym kliknięciu w Eksploratorze
   Reg->OpenKey("roz_auto_file", true);
   Reg->WriteString("", "opis rozszerzenia");
   Reg->OpenKey("shell", true);
   Reg->WriteString("", "open");
   Reg->OpenKey("open", true);
   Reg->WriteString("", "&Open");
   Reg->OpenKey("command", true);
   Reg->WriteString("", ParamStr(0) + " \"\%1\"");
   Reg->CloseKey();

   //wymyślona przez nas dodatkowa akcja jaką można wykonać na pliku
   Reg->OpenKey("roz_auto_file", true);
   Reg->OpenKey("shell", true);
   Reg->OpenKey("info", true);
   Reg->WriteString("", "&Informacje");
   Reg->OpenKey("command", true);
   Reg->WriteString("", "zadanie do wykonania");
   Reg->CloseKey();

   //kojarzenie ikony z rozszerzeniem (taka sama jak ikona programu)
   Reg->OpenKey("roz_auto_file", true);
   Reg->OpenKey("DefaultIcon", true);
   Reg->WriteString("", ParamStr(0) + ",0");

   delete Reg;
}
//--------------------------------

 

Rozszerzenie pliku można oczywiście rejestrować w tradycyjny sposób, czyli klikamy na wybranym pliku prawym klawiszem myszy i z menu kontekstowego wybieramy polecenie 'Otwórz' lub 'Otwórz za pomocą', lub 'Otwórz z...', w oknie które wyskoczy zaznaczamy opcję 'Zawsze używaj wybranego programu do otwieranie plików tego typu', następnie odszukujemy nasz program za pomocą przycisku 'Przeglądaj'.

...powrót do menu. 

Sprawdzanie ile czasu uruchomiony jest Windows.

W celu sprawdzenia ile czasu uruchomiony jest Windows wystarczy posłużyć się funkcją GetTickCount():

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Label1->Caption = "System działa od: " + IntToStr((GetTickCount()/1000)/60) + " minut";
}
//--------------------------------

...powrót do menu. 

Bezczynność aplikacji - zdarzenie OnIdle.

Aplikacja gdy nic nie robi jest bezczynna, ale pewnie nie wszyscy wiedzą, że bezczynność jest również formą działania i jako takie posiada swoje zdarzenie, tak więc w C++ Builder każda aplikacja (program) posiada zdarzenie OnIdle, zdarzenie to nie jest przypisane do żadnego konkretnego obiektu w aplikacji, lecz do samego programu, czyli co posiada to zdarzenie? Nie formularz, ani żaden obiekt znajdujący się na nim, ale posiada je sam program. Co wywołuje to zdarzenie, skoro żaden obiekt go nie posiada? Tego zdarzenie nie wywołuje żaden obiekt, nie wywołuje go również sama aplikacja, bo skoro jest bezczynna..., więc musi je wywoływać sam system. Szczerze mówiąc nie wiem co wywołuje to zdarzenie, ale wiem jak je stosować i co najważniejsze jest to bardzo użyteczne zdarzenie. W celu przypisania zdarzenia do aplikacji umieszczamy w pliku nagłówkowym w sekcji private lub public deklarację funkcji (zdarzenia) OnIdle, a następnie w pliku źródłowym umieszczamy jej definicję, po czym w konstruktorze klasy przypisujemy to zdarzenie do aplikacji:

// Plik nagłówkowy np. Unit1.h
//--------------------------------
private:
        void __fastcall AppIdle(System::TObject *Sender, bool &Done); // nazwa zdarzenia jest dowolna.
//--------------------------------


// Plik źródłowy np. Unit1.cpp
//--------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 Application->OnIdle = AppIdle; // przypisanie zdarzenia do aplikacji.
}
//--------------------------------
void __fastcall TForm1::AppIdle(System::TObject *Sender, bool &Done)
{
 // jakieś zadanie do wykonania.
}
//--------------------------------


wystarczy, wewnątrz zdarzenia umieszczamy to co ma być wykonywane, gdy aplikacja jest bezczynna, co ciekawe wykonywanie zadania wewnątrz tego zdarzenia nie jest traktowane jak przerwanie bezczynności aplikacji mimo, iż aplikacja coś robi, jednak zlecenie aplikacji innego zadania powoduje przerwanie bezczynności, więc zdarzenie OnIdle się zatrzymuje, a raczej pauzuje. Najlepiej zobrazuje to mały przykład, umieszczamy na formularzu obiekty Button1 i Label1 i tworzymy taki kod:

// Plik nagłówkowy np. Unit1.h
//--------------------------------
private:
        void __fastcall AppIdle(System::TObject *Sender, bool &Done); // nazwa zdarzenia jest dowolna.
//--------------------------------


// Plik źródłowy np. Unit1.cpp
//--------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 Application->OnIdle = AppIdle; // przypisanie zdarzenia do aplikacji.
}
//--------------------------------
void __fastcall TForm1::AppIdle(System::TObject *Sender, bool &Done)
{
 for(int i = 0; i < 90000; i++)
 {
  Caption = (AnsiString)i;
  Application->ProcessMessages();
 }
}
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 for(int i = 0; i < 10000; i++)
 {
  Label1->Caption = (AnsiString)i;
  Application->ProcessMessages();
 }
}
//---------------------------------


W podanym przykładzie zaraz po uruchomieniu aplikacji zostanie uruchomione zdarzenie AppIdle i będzie wykonywane umieszczone w nim zadanie, czyli na pasku tytułowym formularza powinny "śmigać liczby", czyli pętla działa i odlicza od 0 do 90 000, to powinno jej zająć trochę czasu. Jeśli w trakcie tego odliczania klikniemy na przycisku Button1, to bezczynność aplikacji zostanie przerwana i zdarzenie AppIdle zostanie zatrzymane, a zacznie się wykonywać zdarzenie OnClick dla przycisku Button1 i na etykiecie Label1 zaczną "śmigać liczby", a napis na Caption formularza stanie w miejscu, gdy pętla umieszczona w zdarzeniu OnClick dobiegnie do końca wtedy aplikacja znów będzie bezczynna i pętla umieszczona w zdarzeniu AppIdle znów rozpocznie działanie, przy czym odliczanie rozpocznie się od miejsca w którym się zatrzymało, a nie od początku i gdy pętla dobiegnie do końca, wtedy odliczanie rozpocznie się od początku i tak w kółko, bez przerwy.
Teraz pokażę inny ciekawy sposób zastosowania tego zdarzenia, tworzymy prosty kod pobierający aktualną datę i czas w zdarzeniu OnIdle i wyświetlający ją na obiekcie Label1:


// Plik źródłowy np. Unit1.cpp
//--------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 Application->OnIdle = AppIdle; // przypisanie zdarzenia do aplikacji.
}
//--------------------------------
void __fastcall TForm1::AppIdle(System::TObject *Sender, bool &Done)
{
 Label1->Caption = Now();
}
//--------------------------------


Po uruchomieniu aplikacji i pozostawieniu jej bezczynnej zauważymy, że na etykiecie Label1 pokazała się aktualna data i czas, ale co ciekawe czas zmienia się w odstępach jednosekundowych, czyli zdarzenie OnIdle działa jak Timer, ponieważ cały czas się aktualizuje. Niestety z tą bezczynnością bywa tak, że jak system jest zajęty to zdarzenie się zatrzymuje, nie zawsze przy starcie programu zdarzenie ruszy od razu, ponieważ program może coś robić. Tego zdarzenia nie można traktować jako Timera ponieważ jest bardzo zawodne i wystarczy uruchomić w systemie jakiś proces by je zatrzymać, ale jeśli potrzebujemy, żeby aplikacja wykonała jakieś drobne zadanie gdy zasoby systemu są wolne. Zdarzenie nie będzie się wykonywać w tle, działa tylko wtedy gdy kursor myszy znajduje się nad oknem programu i aplikacja jest bezczynna.

...powrót do menu. 

Obsługa schowka.

Zawartość schowka można kopiować z i do różnych obiektów, które to obsługują np. do obiektu Edit1:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Edit1->CopyToClipboard();
}
//--------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 Edit1->PasteFromClipboard();
}
//--------------------------------

Można również kopiować grafikę, np. bitmapy. W skład pakietu DataBase wchodzi komponent TDBImage znajdujący się na zakładce Data Controls, pozwala on na kopiowanie z i do schowka:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 DBImage1->Picture->LoadFromFile("C:\\EROTYKA\\Tapety\\ładne.jpg");
 DBImage1->CopyToClipboard();
}
//--------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 DBImage1->PasteFromClipboard();
}
//--------------------------------

Możliwe jest również obsługiwać schowek za , ale w tym przypadku należy włączyć do pliku źródłowego bibliotekę #include <clipbrd.hpp>:

// Plik źródłowy np. Unit1.cpp
#include <clipbrd.hpp>

//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 TClipboard *pCB = new TClipboard();
 Image1->Picture->LoadFromClipboardFormat(CF_BITMAP, pCB->GetAsHandle(CF_BITMAP), 0);
 delete pCB;
}
//--------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 int DataHandle;
 HPALETTE APalette;
 unsigned short MyFormat;
 Image1->Picture->SaveToClipboardFormat(MyFormat,DataHandle,APalette);
 Clipboard()->SetAsHandle(MyFormat, DataHandle);
}
//--------------------------------

W pomocy BCB można znaleźć informację, że funkcję SaveToClipboardFormat tak się właśnie stosuje, ale u mnie to nie zadziałało. Przedstawione metody działają w odniesieniu do bitmap, klasa TJPEGImage również posiada te funkcje, jednak nie zadziałały i występuje zupełny brak pomocy w tej kwestii. W obiektach takich jak np. Memo1 działają funkcje CopyToClipboard i PasteFromClipboard.
Jednym z problemów jest kopiowanie zawartości schowka do zmiennych, oczywiście w grę wchodzą zmienne AnsiString, ponieważ jeśli nawet skopiujemy liczbę, to i tak będzie ona traktowana jako tekst są dwa sposoby na kopiowanie zawartości schowka do zmiennych, prosty i skomplikowany:

// Plik źródłowy np. Unit1.cpp
#include <clipbrd.hpp>

//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 TClipboard *pCB= new TClipboard();
 String a = pCB->AsText;
 Edit1->Text = a;
 delete pCB;
}
//--------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 TClipboard *pCB = new TClipboard();
 AnsiString MyString;
 int TextHandle;
 char *pText;

 pCB->Open();
 try
 {
  TextHandle = pCB->GetAsHandle(CF_TEXT);
  pText = (char *)GlobalLock((HGLOBAL)TextHandle);
  MyString = pText;
  GlobalUnlock((HGLOBAL)TextHandle);
 }
 catch(...)
 {
  pCB->Close();
  throw;
 }
 pCB->Close();
 RichEdit1->Text = MyString;
 delete pCB;
}
//--------------------------------

Jednym ze sposobów przeniesienia dowolnej zawartości do schowka jest wykorzystanie funkcji Assign, można w ten sposób umieścić w schowku np. grafikę JPEG z obiektu Image, możliwe jest umieszczenie grafiki w dowolnym formacie:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 TClipboard *pCB = new TClipboard();
 pCB->Assign(Image1->Picture);
 delete pCB;
}
//--------------------------------

Jak to pokazałem wyżej kopiowanie ze schowka grafiki w formacie JPEG jest wielce problematyczne, jednak znalazłem sposób:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 TClipboard *pCB = new TClipboard();
 Image1->Picture->Assign(pCB);
 delete pCB;
}
//--------------------------------

W ten sposób można umieścić w Image grafikę w formacie BMP lub JPEG i  w każdym innym formacie jaki uda nam się skopiować do schowka, a to oznacza, że niezależnie od tego w jakim formacie umieszczamy grafikę w schowku, schowek i tak to konwertuje na bitmapę, najlepiej to widać na takim przykładzie, umieśćmy w schowku grafikę w formacie GIF za pomocą dowolnego programu, który potrafi ten format obsługiwać, a następnie spróbujmy ją przenieść do Image i żeby było trudniej przenieśmy ją poprzez obiekt klasy TBitmap:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 TClipboard *pCB = new TClipboard();
 Graphics::TBitmap *bmp = new Graphics::TBitmap;
 bmp->Assign(pCB);
 Image1->Picture->Bitmap = bmp;
 delete pCB, bmp;
}
//--------------------------------

To daje znacznie szersze możliwości, jeśli przechwycimy do schowka ekran za pomocą klawisza PrtScr to potem można go umieścić w Image lub obiekcie typu TBitmap.

...powrót do menu. 

Zablokowanie uruchamiania wygaszacza ekranu.

W celu zablokowania uruchamiania wygaszacza należy posłużyć się funkcją SystemParametrsInfo, zablokowanie opiera się na wysyłaniu do systemu fałszywego komunikatu o tym, że wygaszacz jest już uruchomiony:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 //Wyłączenie wygaszacza:
 SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, FALSE, NULL, 0);

 //Włączenie:
 SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, TRUE, NULL, 0);
}
//--------------------------------

...powrót do menu. 

Mruganie ikoną programu na pasku zadań.  

W tej poradzie pokaże jak za pomocą prostej funkcji mrugać ikoną programu na pasku zadań. Służy do tego funkcja FlashWindows pobierającą dwa parametry, pierwszy to uchwyt do okna programu, którego ikoną chcemy mrugać, drugi parametr to zmienna typu bool, wartość true włącza mruganie, wartość false - wyłącza, przy czym po zakończeniu mrugania, ikona na pasku zadań nie zmieni koloru dopóki jej nie zaznaczymy. Funkcję FlashWindows można wywołać w dowolnym zdarzeniu, ja jednak umieszczę ją w nowym wątku tak, żeby pętla określająca częstotliwość mrugania nie wpływała na całą aplikację powodując opóźnienia, zatrzymanie mrugania będzie się odbywać poprzez wstrzymanie wykonywania wątku:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
unsigned short int W_ID;
int __fastcall Blink(Pointer P)
{
 for(;;)
 {
  FlashWindow(Application->Handle, true);
  Sleep(300);
  FlashWindow(Application->Handle, false);
 }
}
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 unsigned int x;
 W_ID = BeginThread(NULL, 0, Blink, this, 0, x);
}
//--------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 SuspendThread((Pointer)W_ID);
}
//--------------------------------

...powrót do menu. 

    Uruchomienie aplikacji i oczekiwanie na jej zamknięcie.

    O uruchamianiu plików i programów pisałem już wielokrotnie, tym razem chcę jednak pokazać bardzo użyteczny sposób na uruchamianie programów. Nowością jest to, że nasz program uruchomi jakiś inny program, np. Notatnik i poczeka na jego zamknięcie, po zamknięciu Notatnika można wykonać jakieś zaplanowane działanie. Praktyczne zastosowanie tej metody może mieć miejsce w sytuacji gdy chcemy uruchomić jakiś program w rozdzielczości innej niż aktualnie używana, a po zamknięciu programu chcemy żeby wcześniejsza rozdzielczość została automatycznie przywrócona przez nasz program. Całe zadanie jest trochę złożone, ponieważ oprócz funkcji uruchamiającej program i oczekującej na jego zamknięcie, trzeba również stworzyć funkcję zmieniającą rozdzielczość  ekranu, ale przed dokonaniem zmian trzeba najpierw sprawdzić aktualne ustawienia ekranu by później można je było przywrócić.
W przykładzie program pobierze informacje o rozdzielczości ekranu, głębi kolorów i częstotliwości odświeżania, następnie zmieni rozdzielczość na 800x600 i częstotliwość odświeżania ustawi na 70 Hz, po czym uruchomi Notatnik i wczyta do niego plik Win.ini z katalogu Windows. Następnie po zamknięciu Notatnika (przy czym Notatnik zamykamy w sposób standardowy, czyli sami) program przywróci wcześniejsze ustawienia ekranu.
Najpierw przedstawię funkcję do zmiany rozdzielczości ekranu i chociaż już o niej pisałem w tym dziale, będzie w niej coś nowego, a mianowicie funkcja będzie dodatkowo zmieniała częstotliwość odświeżania:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall ChangeResolution(DWORD bitColor, DWORD width, DWORD height, int freq)
{
 DEVMODE dm;
 dm.dmSize = sizeof(DEVMODE);

 int index = 0;
 while(EnumDisplaySettings(NULL, index, &dm))
 {
  if(dm.dmBitsPerPel == bitColor && dm.dmDisplayFrequency == freq && dm.dmPelsWidth == width && dm.dmPelsHeight == height)
  {
   dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
   LONG result = ChangeDisplaySettings(&dm, CDS_TEST);
   if(result == DISP_CHANGE_SUCCESSFUL)
   {
    ChangeDisplaySettings(&dm, 0);
    break;
   }
   else
    if(result == DISP_CHANGE_RESTART)
    {
     ShowMessage("Żeby wprowadzić zmiany trzeba zrestartować komputer!");
     break;
    }
    else
    {
     ShowMessage("Wprowadzono nieprawidłowe parametry!");
     break;
    }
   }
   index++;
  }
}
//--------------------------------

Teraz kolej na funkcję uruchamiającą program i oczekującą na jej zamknięcie. O tej funkcji chyba jeszcze nie pisałem, jest to funkcja stworzona przeze mnie wykorzystująca strukturę SHELLEXECUTEINFO. Funkcja będzie pobierała dwa argumenty, czyli ścieżkę dostępu do programu (pliku) oraz parametr z jakim ma być uruchomiony program, oczywiście parametr można by sobie darować i uruchamiać tylko program, ale ja chcę tutaj pokazać jak uruchomić program z wybranym do edycji plikiem, jest to inny sposób od tego przedstawionego w poradzie Uruchamianie programu z wybranym do edycji plikiem. Przedstawię funkcję i sposób jej wywołania razem z funkcją zmiany rozdzielczości ekranu:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall ExecuteApplication(AnsiString FileName, char param[], HWND h)
{
 SHELLEXECUTEINFO sei;
 memset(&sei, 0, sizeof (sei));
 sei.cbSize = sizeof(sei);
 sei.fMask = SEE_MASK_NOCLOSEPROCESS;
 sei.hwnd = h;
 sei.lpVerb = "open";
 sei.lpFile = FileName.c_str();
 sei.lpParameters = param;
 sei.nShow = SW_SHOWDEFAULT;

 if(ShellExecuteEx(&sei))
  try
  {
   WaitForSingleObject(sei.hProcess, INFINITE);
  }
  __finally
 {
  CloseHandle(sei.hProcess);
 }
}
//--------------------------------
void __fastcall ChangeResolution(DWORD bitColor, DWORD width, DWORD height, int freq)
{
 // tutaj funkcja zmiany rozdzielczości stworzona wcześniej
}
//--------------------------------
#include <stdio.h>
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 char sysdir[_MAX_PATH], windir[_MAX_PATH], winini[_MAX_PATH];
 GetSystemDirectory(sysdir, _MAX_PATH); //pobieranie ścieżki dostępu do katalogu System32
 GetWindowsDirectoryA(windir, _MAX_PATH); //pobieranie ścieżki dostępu do katalogu Windows
 sprintf(winini, "%s", (AnsiString)windir + "\\win.ini");

 int bitPerPixel = GetDeviceCaps(Canvas->Handle, BITSPIXEL); //pobieranie informacji o aktualnej głębi bitowej ekranu
 int width = GetSystemMetrics(SM_CXSCREEN); //pobieranie informacji o szerokości ekranu
 int height = GetSystemMetrics(SM_CYSCREEN); //pobieranie informacji o wysokości ekranu
 int freq = GetDeviceCaps(Canvas->Handle, VREFRESH); //pobieranie informacji o częstotliwości odświeżania

 Application->Minimize();
 ChangeResolution(bitPerPixel, 800, 600, 70); // zmiana rozdzielczości ekranu
 ExecuteApplication((AnsiString)sysdir + "\\notepad.exe", winini, this); //uruchomienie programu i oczekiwanie na jego zamknięcie
 ChangeResolution(bitPerPixel, width, height, freq); //przywrócenie wcześniejszej rozdzielczości
 Application->Restore();
}
//--------------------------------

W przykładzie pojawia się funkcja minimalizująca nasz program przed uruchomieniem innego programu, należy zminimalizować program ponieważ zostaje on wstrzymany do czasu zamknięcia programu, który uruchamia, a co za tym idzie wstrzymane zostaje jego odświeżanie, więc pulpit w miejscu którego znajduje się nasz program również nie zostanie odświeżony i powstaną plamy. Najlepiej zobaczyć to na przykładzie usuwając z kodu funkcję minimalizującą. Na koniec program wywołuje funkcję przywracającą (Restore).

...powrót do menu. 

Sprawdzanie i zmiana częstotliwości odświeżania ekranu.

W tej poradzie pokażę sposób na zmiana częstotliwości odświeżania ekranu. Wykorzystam tutaj poradę na zmianę rozdzielczości ekranu dodając tylko kilka nowych elementów. W zasadzie z wykorzystaniem tej porady można dokonać zmiany głębi bitowej, rozdzielczości i częstotliwości odświeżania ekranu, do tamtego przykładu dodamy w ciele funkcji jeszcze jeden argument określający właśnie częstotliwość odświeżania. Zanim zaczniecie się z tym bawić należy najpierw sprawdzić czy dla wybranej rozdzielczości można ustawić wybraną częstotliwość, ponieważ ustawienie zbyt dużej może spowodować fizyczne uszkodzenie monitora (ja raz tak miałem), dlatego przy włączaniu dużej częstotliwości odświeżania należy ustawić małą rozdzielczość.

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall ChangeFrequency(DWORD width, DWORD height, int freq)
{
 DEVMODE dm;
 dm.dmSize = sizeof(DEVMODE);

 int index = 0;
 while(EnumDisplaySettings(NULL, index, &dm))
 {
  if(dm.dmDisplayFrequency == freq && dm.dmPelsWidth == width && dm.dmPelsHeight == height)
  {
   dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
   LONG result = ChangeDisplaySettings(&dm, CDS_TEST);
   if(result == DISP_CHANGE_SUCCESSFUL)
   {
    ChangeDisplaySettings(&dm, 0);
    break;
   }
   else
    if(result == DISP_CHANGE_RESTART)
    {
     ShowMessage("Żeby wprowadzić zmiany trzeba zrestartować komputer!");
     break;
    }
    else
    {
     ShowMessage("Wprowadzono nieprawidłowe parametry!");
     break;
    }
   }
   index++;
  }
}
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 ChangeFrequency(800, 600, 70);
}
//--------------------------------

Przedstawiony sposób działa prawdopodobnie tylko w systemach Windows opartych na jądrze NT, czyli Windows NT/2000/XP, w Win 95/98/ME prawdopodobnie nie zadziała.

...powrót do menu. 

Metryka systemu.

Metryka systemu jest dostępna w Windows po wybraniu właściwości pulpitu, po przejściu na zakładkę 'Wygląd' i wybraniu przycisku 'Zaawansowane':

Do pobierania metryki systemu służy funkcja GetSystemMetrics, która pobiera tylko jeden argument określający typ zwracanej właściwości ustawienia systemu:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Memo1->Lines->Clear();
 Memo1->Lines->Add("Rozdzielczość ekranu: " + (String)GetSystemMetrics(SM_CXSCREEN) + "x" + (String)GetSystemMetrics(SM_CYSCREEN));
 Memo1->Lines->Add("Obszar roboczy ekranu: " + (String)GetSystemMetrics(SM_CXFULLSCREEN) + "x" + (String)GetSystemMetrics(SM_CYFULLSCREEN));
 Memo1->Lines->Add("Liczba klawiszy myszy: " + (String)GetSystemMetrics(SM_CMOUSEBUTTONS));
 Memo1->Lines->Add("Szerokość paska przewijania: " + (String)GetSystemMetrics(SM_CXHSCROLL));
 Memo1->Lines->Add("Rozmiar ikon: " + (String)GetSystemMetrics(SM_CXICON));
 Memo1->Lines->Add("Rozmieszczenie poziome ikon: " + (String)(GetSystemMetrics(SM_CXICONSPACING) - GetSystemMetrics(SM_CXICON)));
 Memo1->Lines->Add("Rozmieszczenie pionowe ikon: " + (String)(GetSystemMetrics(SM_CYICONSPACING) - GetSystemMetrics(SM_CYICON)));
 Memo1->Lines->Add("Wymiary okna po maksymalizacji: " + (String)GetSystemMetrics(SM_CXMAXIMIZED) + "x" + (String)GetSystemMetrics(SM_CYMAXIMIZED));
 Memo1->Lines->Add("Rozmiar MainMenu: " + (String)GetSystemMetrics(SM_CXMENUSIZE));
 Memo1->Lines->Add("Rozmiar przycisku paska tytułowego: " + (String)GetSystemMetrics(SM_CXSIZE));
}
//--------------------------------

Przedstawiłem tutaj tylko niektóre z ustawień systemu. Spis parametrów które można przekazać jako argumenty dla funkcji GetSystemMetrics można znaleźć w pomocy BCB, w tym celu należy zaznaczyć w oknie edytora zapis GetSystemMetrics  i wcisnąć klawisz F1.

...powrót do menu. 

Zablokowanie uruchomienia kopii programu - sposób drugi.

    O blokowaniu kopii programu pisałem już w dziale teoria, tutaj jednak chcę przedstawić znacznie prostszy i krótszy sposób opierający się na funkcji CreateSemaphote, którą można wywołać we zdarzeniu OnCreate dla formularza głównego. Funkcja pobiera cztery argumenty a nas interesuje ostatni, będący nazwą 'semafora'. Nazwa musi być unikalna i niepowtarzalna, jest to o tyle istotne, że jeżeli w systemie będzie już uruchomiony jakiś program zawierający taką samą nazwę 'semafora' to nasz program uzna, że jest to jego własna kopia i się nie uruchomi, a teraz do rzeczy, oto funkcja, która nie wymaga dalszych wyjaśnień:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
 THandle h;
 h = (int)CreateSemaphore(NULL, 0, 1, "UNIKALNA_NAZWA");
 if((h != 0) && (GetLastError() == ERROR_ALREADY_EXISTS))
 {
  CloseHandle((void *)h);
  ShowMessage("Ten program jest już uruchomiony!");
  Application->Terminate();
 }
}
//--------------------------------

...powrót do menu. 

Wyliczanie czcionek zainstalowanych w systemie.

W tej poradzie chcę pokazać jak można wyświetlić listę wszystkich czcionek zainstalowanych w systemie Windows. Bez zagłębiania się w szczegóły podam prosty sposób:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
 ListBox1->Items = Screen->Fonts;
}
//--------------------------------

W ten oto sposób wszystkie czcionki zostaną wyświetlone w obiekcie ListBox, ponieważ funkcja Fonts zwraca wartość typu TStrings możliwe jest bezpośrednie jej przypisanie do właściwości Items obiektu ListBox, oczywiście można to zrobić z każdym obiektem pobierającym na wejściu wartości typu TStrings, np. Memo1->Lines, ComboBox1->Items, itp...
Z zaprezentowanym przykładem można zrobić znacznie więcej, można np. dodać kod, który oprócz wyświetlanej nazwy czcionki wyświetli ją w jej własnym kroju a dodatkowo można również zmienić wysokość każdego wiersza na liście, w tym celu należy ustawić właściwość Style obiektu ListBox1 na lbOwnerDrawVariable, a następnie umieszczamy odpowiedni kod w zdarzeniach OnDrawItem i OnMeasureItem, a oto kompletny kod:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
 ListBox1->Items = Screen->Fonts;
}
//--------------------------------
void __fastcall TForm1::ListBox1DrawItem(TWinControl *Control, int Index,
        TRect &Rect, TOwnerDrawState State)
{
 ListBox1->Canvas->FillRect(Rect);
 ListBox1->Canvas->Font->Name = ListBox1->Items->Strings[Index].c_str();
 ListBox1->Canvas->Font->Size = 0;
 ListBox1->Canvas->TextOut(Rect.Left + 1, Rect.Top + 1, ListBox1->Items->Strings[Index].c_str());
}
//--------------------------------
void __fastcall TForm1::ListBox1MeasureItem(TWinControl *Control,
        int Index, int &Height)
{
 ListBox1->Canvas->Font->Name = ListBox1->Items->Strings[Index].c_str();
 ListBox1->Canvas->Font->Size = 0;
 Height = ListBox1->Canvas->TextHeight("FONTS") + 2; //można tutaj wstawić dowolny tekst, liczy się tekst a nie treść, funkcja ma na celu sprawdzenie wysokości czcionki
}
//--------------------------------

W zaprezentowanym przykładzie nie ma nic odkrywczego, ponieważ to co tutaj przedstawiłem można znaleźć w przykładowym projekcie znajdującym się w katalogu [..]\Program Files\Borland\CBuilder6\Examples\Apps\OwnerList\ . Żeby czcionki mogły być wyświetlane prawidłowo, należy dla obiektu ListBox1 ustawić, dla czcionki skrypt Zachodni (właściwość: Font->Charset = ANSI_CHARSET). Standardowo pisząc teksty w języku polskim używamy skryptu Europa Środkowa (własciwość: Font->Charset = EASTEUROPE_CHARSET) ponieważ pozwala to na prawidłowe wyświetlanie polskich znaków, jednak nie wszystkie czcionki obsługują skrypt Europa Środkowa i jeżeli ustawimy taki właśnie dla obiektu ListBox1, to te czcionki, które nie obsługują tego skryptu nie będą wyświetlane w swoim kroju, dlatego wybranie skryptu Zachodni jest tutaj najbardziej odpowiednie, gdyż niemal wszystkie czcionki są go w stanie obsłużyć.
    W dalszej części tej porady chcę pokazać sposób na wyliczanie czcionek za pomocą funkcji EnumFonts, ponieważ daje ona znacznie większe możliwości, można pobierać nie tylko nazwę czcionki, ale również np. typ obsługiwanego skryptu. Funkcja EnumFonts nie podaje nazwy czcionki, lecz nazwę kroju czcionki z którego się ona wywodzi, w praktyce oznacza to, że na liście pojawi się kilka takich samych nazw kroju dla różnych nazw czcionki, dla przykładu:

Nazwa kroju czcionki
Arial
Arial Baltick
Arial Baltick
Arial Baltick
Arial Black
itp...
Nazwa czcionki
Arial
Arial Black
Arial Narrow
Arial Unicode MS
Asimov
itp...

Dla jasności funkcja EnumFonts wyświetli nazwę kroju czcionki, a nie nazwę czcionki. Użycie tej funkcji wymaga stworzenia funkcji dodatkowej, którą ja nazwałem Fonty. W przykładzie wynik działania funkcji zostanie wyświetlony w obiekcie ListView pozwoli to na wyświetlenie skryptu obsługiwanego przez każdą czcionkę. Umieszczamy na formularzu obiekt ListView1 i ustawiam jego właściwość ViewStyle na vsRaport, a następnie we właściwości Columns tworzymy dwie kolumny pierwszej nadajemy nazwę Nazwa kroju czcionki, a drugiej Skrypt właściwość AutoSize dla obydwu kolumn ustawiamy na true, natomiast właściwość SortType obiektu ListView1 ustawiamy na stNone. Teraz możemy przystąpić do kodowania:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
bool _stdcall Fonty(LPLOGFONT LogFont, LPNEWTEXTMETRIC TextMetric, DWORD FontType, TListView *ListView)
{
 ListView->Items->Add();
 int i = ListView->Items->Count - 1;
 ListView->Items->Item[i]->Caption = (AnsiString)LogFont->lfFaceName;
 String Fcharset;
 CharsetToIdent(LogFont->lfCharSet, Fcharset); // tutaj następuje dekodowanie wartości liczbowej określającej typ skryptu na nazwę skryptu
 ListView->Items->Item[i]->SubItems->Add(Fcharset);
 ListView->Items->Item[i]->Update();
 return true;
}
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 HDC DC;
 DC = GetDC(0);
 ListView1->SortType = (TSortType)Listactns::stNone; //wyłączenie sortowania w obiekcie ListView1
 ListView1->Column[1]->AutoSize = true;
 EnumFonts(DC, NULL, (FONTENUMPROC)Fonty, (LPARAM)ListView1);

 ListView1->SortType = (TSortType)Listactns::stBoth; // włączenie sortowania w obiekcie ListView1
 ListView1->Column[1]->AutoSize = false;
 ListView1->Column[1]->Width = ListView1->Width - ListView1->Column[0]->Width - GetSystemMetrics(SM_CXVSCROLL) - 4;
}
//--------------------------------

Na żółto zaznaczyłem fragmenty kodu, które są zbędne z punktu prawidłowego wyliczania czcionek, służą one tylko do ustawiania takiego rozmiaru kolumn w obiekcie ListView1, żeby nie pojawiał się w nim poziomy pasek przewijania. Wyłączenie sortowania w trakcie wyliczania czcionek ma na celu wyeliminowanie migotania zawartości obiektu ListView1 w trakcie wykonywania zadania, oraz służy prawidłowemu powiązaniu zawartości kolumny drugiej z zawartością kolumny pierwszej, jeśli ustawimy sortowanie bezpośrednio we właściwości SortType obiektu ListView1, w Object Inspector, to druga kolumna nie zostanie prawidłowo powiązana z kolumną pierwszą.
Istnieje jeszcze jedna podobna funkcja EnumFontFamilies działająca na tych samych zasadach co funkcja EnumFonts, jednak podaje ona nie nazwę kroju czcionki, lecz nazwę czcionki, czyli działa podobnie jak funkcja
Screen->Fonts z tą różnicą, że możliwe jest określanie dodatkowych właściwości czcionki tak jak ma to miejsce w funkcji EnumFonts:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
bool _stdcall Fonty(LPLOGFONT LogFont, LPNEWTEXTMETRIC TextMetric, DWORD FontType, TListView *ListView)
{
 ListView->Items->Add();
 int i = ListView->Items->Count - 1;
 ListView->Items->Item[i]->Caption = (AnsiString)LogFont->lfFaceName;
 String Fcharset;
 CharsetToIdent(LogFont->lfCharSet, Fcharset); // tutaj następuje dekodowanie wartości liczbowej określającej typ skryptu na nazwę skryptu
 ListView->Items->Item[i]->SubItems->Add(Fcharset);
 ListView->Items->Item[i]->Update();
 return true;
}
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 HDC DC;
 DC = GetDC(0);
 ListView1->SortType = (TSortType)Listactns::stNone; //wyłączenie sortowania w obiekcie ListView1
 ListView1->Column[1]->AutoSize = true;
 EnumFontFamilies(DC, NULL, (FONTENUMPROC)Fonty, (LPARAM)ListView1);

 ListView1->SortType = (TSortType)Listactns::stBoth; // włączenie sortowania w obiekcie ListView1
 ListView1->Column[1]->AutoSize = false;
 ListView1->Column[1]->Width = ListView1->Width - ListView1->Column[0]->Width - GetSystemMetrics(SM_CXVSCROLL) - 4;
}
//--------------------------------

Jak widać w przykładzie zmieniła się tylko nazwa funkcji cała reszta pozostaje bez zmian.
Niżej przedstawiam spis wartości zwracanych przez struktury LOGFONT i NEWTEXTMETRIC:

typedef struct tagLOGFONT {
     LONG lfHeight;
     LONG lfWidth;
     LONG lfEscapement;
     LONG lfOrientation;
     LONG lfWeight;
     BYTE lfItalic;
     BYTE lfUnderline;
     BYTE lfStrikeOut;
     BYTE lfCharSet;
     BYTE lfOutPrecision;
     BYTE lfClipPrecision;
     BYTE lfQuality;
     BYTE lfPitchAndFamily;
     TCHAR lfFaceName[LF_FACESIZE];
} LOGFONT;

typedef struct tagNEWTEXTMETRIC {
     LONG tmHeight;
     LONG tmAscent;
     LONG tmDescent;
     LONG tmInternalLeading;
     LONG tmExternalLeading;
     LONG tmAveCharWidth;
     LONG tmMaxCharWidth;
     LONG tmWeight;
     LONG tmOverhang;
     LONG tmDigitizedAspectX;
     LONG tmDigitizedAspectY;
     BCHAR tmFirstChar;
     BCHAR tmLastChar;
     BCHAR tmDefaultChar;
     BCHAR tmBreakChar;
     BYTE tmItalic;
     BYTE tmUnderlined;
     BYTE tmStruckOut;
     BYTE tmPitchAndFamily;
     BYTE tmCharSet;
     DWORD ntmFlags;
     UINT ntmSizeEM;
     UINT ntmCellHeight;
     UINT ntmAvgWidth;
}
NEWTEXTMETRIC;

...powrót do menu. 

Ładowanie czcionki z pliku.

    Zdarza się, że pisząc programy chcemy zastosować w nich jakąś szczególną czcionkę np. taką, która nie jest instalowana standardowo z systemem Windows i tutaj pojawia się dylemat, ponieważ nasz program będzie w stanie korzystać z takiej czcionki tylko w sytuacji gdy jest ona zainstalowana w systemie, ale przecież użytkownik programu wcale nie musi posiadać wybranej przez nas czcionki. Można oczywiście dołączać plik czcionki z programem i informować użytkownika o konieczności zainstalowania jej w systemie, ale jest to mało profesjonalne i niewygodne szczególnie, że istnieje funkcja AddFontResource pozwalająca na załadowanie do programu czcionki bezpośrednio z pliku, bez konieczności instalowania jej w systemie. Funkcja pobiera tylko jeden argument i jest to ścieżka do pliku czcionki, funkcja zwraca wartość typu bool informującą o powodzeniu w ładowaniu czcionki. Przedstawię kod na takie załadowanie czcionki, ale żebyście mogli sami to przetestować proponuję ściągnąć sobie czcionkę TexasFuneral.TTF i nie instalować jej w systemie prawdopodobnie nie posiadacie jej w swoich zasobach, więc może ona posłużyć do testów, jeżeli jednak ktoś przypadkiem posiada taką czcionkę to proponuję ściągnąć jakąś inną z Internetu. W przykładzie czcionka zostanie załadowana z podanego wcześniej pliku a tekst w obiekcie Edit1 zostanie wyświetlony właśnie z wykorzystaniem tej czcionki. Czcionka Texas Funeral nie obsługuje skryptu Europa Środkowa, więc żeby możliwe było wyświetlenie tekstu w tym kroju czcionki należy ustawić dla obiektu Edit1 właściwość Font->Charset na skrypt Zachodni, czyli na ANSI_CHARSET:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 if(AddFontResource((ExtractFilePath(Application->ExeName) + "TexasFuneral.TTF").c_str()))
 {
  Edit1->Font->Charset = ANSI_CHARSET;
  Edit1->Font->Name = "Texas Funeral";
 }
}
//--------------------------------

Ustawienie prawidłowego skryptu jest bardzo ważne. Zwracam uwagę na to, że funkcji AddFontResource przekazujemy ścieżkę dostępu do pliku czcionki, ale przypisując tą czcionkę wybranemu obiektowi podajemy nazwę kroju, Nazwę kroju można zobaczyć klikając dwukrotnie na pliku czcionki, spowoduje to otwarcie pliku w programie fontview.exe instalowanym standardowo w każdym systemie Windows.

Wybrana czcionka zostanie w ten sposób załadowana nie tylko do naszego programu, lecz do systemu Windows, oznacza to, że będzie ona dostępna dla wszystkich uruchamianych programów. Funkcja nie instaluje jednak czcionki w systemie, tylko ją ładuje na czas bieżącej sesji. W praktyce oznacza to, że czcionka będzie dostępna do restartu systemu, po ponownym uruchomieniu komputera, czcionka nie będzie już dostępna ponieważ nie została zainstalowana, jednak użycie funkcji AddFontResource spowoduje zawsze załadowanie jej do systemu.

...powrót do menu. 

Uruchamianie Apletów Panelu Sterowania.

Aplety można znaleźć w Panelu Sterowania są to swego rodzaju "podprogramy" uruchamiane za pomocą Panelu Sterowania, czyli programu Control.exe znajdującego się w katalogu [..]\Windows\System32. Aplety można uruchamiać za pomocą funkcji WinExec podając ścieżkę dostępu do programu Control.exe i nazwę apletu, np: C:\\Windows\\System32\\Control.exe TIMEDATE.CPL
Przykład uruchomienia Apletu Właściwości: Data i Godzina:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 char SysDir[_MAX_PATH], *CtrlDir;
 GetSystemDirectory(SysDir, _MAX_PATH);
 CtrlDir = ((AnsiString)SysDir + "\\Control.exe Timedate.cpl").c_str();

 WinExec(CtrlDir, SW_SHOWNORMAL);
}
//--------------------------------

Jak widać Aplety Panelu Sterowania to nic innego jak pliki z rozszerzeniem *.CPL i można je znaleźć w katalogu systemowym (System32), ale nie zawsze w niektórych przypadkach należy do programu Control.exe przekazać jako parametr nie odwołanie do pliku *.CPL lecz konkretną komendę i tak np. jest z Apletem Drukarki i Faksy:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 char SysDir[_MAX_PATH], *CtrlDir;
 GetSystemDirectory(SysDir, _MAX_PATH);
 CtrlDir = ((AnsiString)SysDir + "\\Control.exe PRINTERS").c_str();

 WinExec(CtrlDir, SW_SHOWNORMAL);
}
//--------------------------------

Tym razem zostało przekazane polecenie PRINTERS (wielkość liter bez znaczenia). W celu przywołania aplety Myszy:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 char SysDir[_MAX_PATH], *CtrlDir;
 GetSystemDirectory(SysDir, _MAX_PATH);
 CtrlDir = ((AnsiString)SysDir + "\\Control.exe main.cpl").c_str();

 WinExec(CtrlDir, SW_SHOWNORMAL);
}
//--------------------------------

Niestety nie znam wszystkich komend i np. nie wiem jak przywołać za pomocą Panelu Sterowania okno czcionki, być może nie odpowiada za to żaden Aplet lecz uruchamia się po prostu podkatalog Fonts z katalogu Windows:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 char WinDir[_MAX_PATH], *CtrlDir;
 GetWindowsDirectory(WinDir, _MAX_PATH);
 CtrlDir = ((AnsiString)WinDir + "\\Fonts\\").c_str();

 ShellExecute(Form1->Handle, NULL , CtrlDir, "", "", SW_SHOWMAXIMIZED);
}
//--------------------------------

Uruchamianie Apletów za poprzez program Control.exe nie zawsze może się powieść, dlatego znacznie lepiej jest wykorzystać program Rundll32.exe i sterownik Shell32.dll:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 char SysDir[_MAX_PATH], *CtrlDir;
 GetSystemDirectory(SysDir, _MAX_PATH);
 CtrlDir = ((AnsiString)SysDir + "\\rundll32.exe shell32.dll,Control_RunDLL sysdm.cpl").c_str();

 WinExec(CtrlDir, SW_SHOWNORMAL);
}
//--------------------------------

Jak jednak uruchomić np. Aplet Klawiatury, nie istnieje dla tego żaden odrębny plik apletu, otóż należy to zrobić za pomocą pliku main.cpl, tego samego, którego użyliśmy dla myszki różnica jedna polega na tym, że tym razem należy samemu apletowi przekazać argument @1:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 char SysDir[_MAX_PATH], *CtrlDir;
 GetSystemDirectory(SysDir, _MAX_PATH);
 CtrlDir = ((AnsiString)SysDir + "\\rundll32.exe shell32.dll,Control_RunDLL main.cpl @1").c_str();

 WinExec(CtrlDir, SW_SHOWNORMAL);
}
//--------------------------------

Występuje tutaj mnóstwo kombinacji dlatego przedstawię listę komend:

Gdy wywołujemy aplet main.cpl bez parametru to domyślnie jest tam wstawiany parametr @0.
To nie wyczerpuje wszystkich możliwości ponieważ można przywołać np. Aplet Właściwości Ekranu z zakładką Wygląd wywołując aplet desk.cpl z dwoma parametrami (
shell32,Control_RunDLL desk.cpl Display,@Apperance):

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 ShellExecute(GetDesktopWindow(), "open", "control.exe", "desk.cpl Display,@Appearance", "",SW_SHOW );
}
//--------------------------------

Za pomocą środowiska BCB można tworzyć własne aplety, ale o tym w przyszłości. W następnej poradzie przedstawię sposób na wyliczanie Apletów Panelu Sterowania.

...powrót do menu. 

Wyliczanie Apletów Panelu Sterowania.

Tym razem pokażę jak stworzyć listę Apletów Panelu Sterowania, nie ma na to gotowej funkcji, ale zawsze można zaindeksować aplety z plików. Można by tutaj oczywiście zastosować wyszukiwanie plików, ale nie ma takiej potrzeby ponieważ są one zaindeksowane w rejestrze. Stworzę funkcję ogólną która będzie wyświetlała wynik indeksowania we wszelkiego typu obiektach zawierających klasę TStrings:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
#include <Registry.hpp>
template <class A> void EnumApplets(A *Lista)
{
 TRegistry *Rejestr = new TRegistry();
 Rejestr->RootKey = HKEY_LOCAL_MACHINE;
 Rejestr->OpenKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ControlPanel\\Flags", false);
 Rejestr->GetValueNames(Lista);
 delete Rejestr;
}
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 EnumApplets(ListBox1->Items);
}
//--------------------------------
void __fastcall TForm1::ListBox1Click(TObject *Sender)
{
 char WinDir[_MAX_PATH], *CtrlDir;
 GetSystemDirectory(WinDir, _MAX_PATH);
 CtrlDir = ((AnsiString)WinDir + "\\Control.exe " + ListBox1->Items->Strings[ListBox1->ItemIndex]).c_str();

 WinExec(CtrlDir, SW_SHOWNORMAL);
}
//--------------------------------

Jest to najprostszy sposób w jaki można zaindeksować może nie tyle same Aplety Panelu Sterowania ile pliki apletów, niestety ten tema jest mi w zasadzie obcy, więc to na razie wszystko co mam w tej kwestii do powiedzenia.

...powrót do menu. 

Zablokowanie komputera.

W bibliotekach c++ builder znajduje się funkcja LockWorkStation() umożliwiająca zablokowanie komputera, czyli zawartość pulpitu zostanie  ukryta i zostanie przywołane systemowe okno "Odblokowanie komputera", a jego odblokowanie będzie możliwe dopiero po podaniu hasła. Może być to bardzo użyteczne w sytuacji gdy pracujemy z naszym programem i robimy sobie przerwę, a nie chcemy, żeby ktoś zaglądał co robimy. Umożliwia to po prostu szybkie ukrycie zawartości pulpitu.
Takie samo okno jest wywoływane gdy wyprowadzamy komputer ze stanu wstrzymania, a w Opcjach zasilania na zakładce Zaawansowane mamy zaznaczoną opcję: Monituj o padanie hasła, wznawiając pracę komputera po stanie wstrzymania. Funkcja LockWorkStation() działa niezależnie od tego czy ta opcja jest zaznaczona czy nie, nie powoduje ona jednak wprowadzenia komputera w stan wstrzymania.

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

...powrót do menu. 

Wypełnianie grafiką PopupMenu.

Obiekt PopupMenu posiada możliwość wypełniania go grafiką i nie chodzi mi o przypisywanie ikon poszczególnym elementom menu, lecz o wstawienie tła. Na początek należy przygotować sobie trzy pliki graficzne w formacie *.bmp, ja już takie przygotowałem:


popup.bmp - tło podstawowe                popu1.bmp - tło zaznaczenia                popup2.bmp - checkbox dla pozycji menu.

Trzeba również zmienić właściwości obiektu PopupMenu:

W tak przygotowanym obiekcie tworzymy przykładowe menu. Zawartość jest w tej chwili bez znaczenia, nadajmy im nazwy Pop0, Pop1. W pop0 tworzymy SubMenu o nazwach Pop2, Pop3, Pop4. Grafikę jako tło umieszczamy w zdarzeniu OnDrawItem dla jednego elementu menu  Pop0:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
__fastcall TForm1::TInterface1(TComponent* Owner)
        : TForm(Owner)
{
 pop1 = new Graphics::TBitmap;
 pop1->LoadFromResourceName((int)HInstance, "POP1");
 pop2 = new Graphics::TBitmap;
 pop2->LoadFromResourceName((int)HInstance, "POP2");
 pop3 = new Graphics::TBitmap;
 pop3->LoadFromResourceName((int)HInstance, "POP3");
}
//--------------------------------
void __fastcall TInterface1::Pop0DrawItem(TObject *Sender,
        TCanvas *ACanvas, TRect &ARect, bool Selected)
{
 if(Selected)
 {
  ACanvas->Font->Name = "Verdana";
  ACanvas->Font->Size = 8;
  ACanvas->Draw(ARect.Left, ARect.Top, pop2); // grafika popup1.bmp
  ACanvas->Brush->Style = bsClear;
  ACanvas->TextOut(ARect.Left + 30, ARect.Top + 4,
  dynamic_cast<TMenuItem *>(Sender)->Caption);
 }
 else
 {
  ACanvas->Font->Name = "Verdana";
  ACanvas->Font->Size = 8;
  ACanvas->Draw(ARect.Left, ARect.Top, pop1); // grafika popup.bmp
  ACanvas->Brush->Style = bsClear;
  ACanvas->TextOut(ARect.Left + 30, ARect.Top + 4,
  dynamic_cast<TMenuItem *>(Sender)->Caption);
 }
 if(dynamic_cast<TMenuItem *>(Sender)->Checked == true)
  ACanvas->Draw(ARect.Left + 1, ARect.Top, pop3); // grafika popup2.bmp
}
//--------------------------------

Kod zawiera dwie sekcje podzielone w zależności od wartości zmiennej Selected. Zmienna ta określa to, czy dany element menu został wybrany, jeśli tak to wykonuje jeden kod jeśli nie to drugi. Zanim przejdę dalej, grafiki stanowiące tło menu umieściłem w zasobach programu, a w konstruktorze formularz są one wczytywane to trzech obiektów typu TBitmap. Nie przedstawiłem tutaj deklaracji tych obiektów, ale należy pamiętać o ich umieszczeniu w pliku nagłówkowym w sekcji private lub public. Oczywiście wspomniane grafiki można równie dobrze wczytywać z dysku, ale nie jest to dobre rozwiązanie. Opis umieszczania bitmapy w zasobach programu znajduje się w dziale porady | grafika.
    Jak widać po kodzie umieszczonym w zdarzeniu OnDrawItem całe zadanie rysowania tła opiera się na klasie TCanvas, która występuje jako argument tego zdarzenia. W tym zdarzeniu tej klasy używa się dokładnie tak samo jak w każdej innej sytuacji. Na szczególną uwagę zasługuje tutaj przypisanie polimorficzne dynamic_cast, można by z tego nie korzystać, ja jednak chcę aby kod umieszczony tylko w jednym zdarzeniu OnDrawItem dla jednej pozycji PopupMenu obsługiwał również zdarzenia OnDrawItem w pozostałych pozycjach tego menu, ale to nie wystarczy, należy dla każdej pozycji menu w 'Object Inspector' na zakładce 'Events' w pozycji OnDrawItem wybrać Pop0DrawItem podłączając w ten sposób wszystkie zdarzenia OnDrawItem wszystkich pozycji w menu pod zdarzenie OnDrawItem pozycji Pop0.
To by w zasadzie wystarczyło, ale można zrobić jeszcze jedną rzecz, otóż w zdarzeniu OnMeasureItem dla elementu Pop0 umieszczamy instrukcję  ustalającą rozmiar (długość i wysokość) elementu menu:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TInterface1::Pop0MeasureItem(TObject *Sender,
        TCanvas *ACanvas, int &Width, int &Height)
{
 Width = 200;
 Height = 22;
}
//--------------------------------

Przykład: popupmenu.zip - rozmiar: 202 kB (projekt Brorland C++ Builder 6 Enterprise)

...powrót do menu. 

Wyrównanie polecenia MainMenu do prawej strony.

Czym jest MainMenu nie muszę chyba nikomu wyjaśniać, a jeśli ktoś nie wie, to nie jest to porada dla niego. Standardowo gdy tworzymy MainMenu to wszystkie jego polecenia są wyrównywane do lewego górnego rogu formularza, ale w niektórych aplikacjach można się natknąć na pozycję MainMenu wyrównaną do prawego górnego rogu formularza, z reguły jest to polecenie Pomoc. Stworzenie takiego menu wymaga umieszczenia w zdarzeniu OnCreate formularza na którym umieszczamy to menu kodu, który zmieni wyrównanie wybranego polecenia do prawej strony.
Umieszczamy na formularzu komponent MainMenu1 i tworzymy w nim polecenia według własnego uznania, dla porządku stwórzmy równię pozycję MenuItem o nazwie Pomoc1 i to ją właśnie wyrównamy do prawej strony. Po umieszczeniu komponentu na formularzu ustawiamy właściwość Menu formularza na MainMenu1, a teraz wystarczy taki oto prosty kod:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
 TMenuItemInfo *MI;
 char Buffer[80];

 MI = new TMenuItemInfo();
 try
 {
  ZeroMemory((MI), sizeof(MI));
  MI->cbSize = 44;
  MI->fMask = MIIM_TYPE;
  MI->dwTypeData = Buffer;
  MI->cch = sizeof(Buffer);
  if(GetMenuItemInfo(MainMenu1->Handle, Pomoc1->MenuIndex, true, MI))
  {
   MI->fType = (MI->fType | MFT_RIGHTJUSTIFY);
   if(SetMenuItemInfo(MainMenu1->Handle, Pomoc1->MenuIndex, true, MI))
    DrawMenuBar(MainMenu1->WindowHandle);
  }
 }
 __finally // lub catch(...)
 {
  delete MI;
 }
}
//--------------------------------

Przy takim ustawieniu kodu wyrównaniu kodu
Jeżeli funkcjom GetMenuItemInfo i SetMenuItemInfo jako trzeci parametr przekażemy wartość false, to wyrównaniu do prawej strony ulegną wszystkie pozycje MainMenu za wyjątkiem pierwszej. Jeżeli zamiast zmiennej MFT_RIGHTJUSTIFY wstawimy MFT_RIGHTORDER to wszystkie polecenia MainMenu będą wyrównane do prawej strony razem z SubMenu, ale obowiązują pewne zasady, jeżeli jako drugi parametr do funkcji GetMenuItemInfo i SetMenuItemInfo  przekazujemy odwołanie do pierwszej pozycji MainMenu to trzeci parametr musi być ustawiony na true, w przeciwnym razie nie ma znaczenia jaką wartość przyjmie trzeci parametr.

...powrót do menu. 

Tworzenie listy ikon z plików ikon (*.ico, *.exe, *.dll) w ListBox.

W tej poradzie pokaże jak do obiektu ListBox1 załadować listę, a właściwie wyświetlić wszystkie ikony z wybranego pliku, może to być dowolny plik zawierający ikony, a więc ICO EXE i DLL. Na początek umieszczamy na formularzu komponenty Button1, OpenDialog1, ListBox1 i ImageList1. Następnie zmieniamy kilka właściwośćc ListBox1, a więc ustawiamy jego właściwość Columns na 7, ItemHeight na 36, Style na lbOwnerDrawFixed. W komponencie ImageList1 zmieniamy właściwości: BkColor na clWhite, Width i Height na 32. W komponencie OpenDialog1 ustawiamy właściwość Filter na: Pliki ikon|*.ico;*.dll;*.exe
Do ładowania i wyświetlania ikon z plików tworzymy specjalną funkcję, której deklarację umieszczamy w pliku nagłówkowym w sekcji private, a definicja funkcji ma następującą postać:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::GetIconsToList(AnsiString FileIcon)
{
 ImageList1->Clear();
 ListBox1->Items->Clear();

 HICON ico;
 TIcon *MyIcon = new TIcon;
 int i = 0;
 for(;;)
 {
  ico = ExtractIcon(HInstance, FileIcon.c_str(), i);

  if(ico == NULL)
  {
   if(IconIndex >= 0)
   ListBox1->ItemIndex = IconIndex;
   delete MyIcon;
   return;
  }
  MyIcon->Handle = ico;
  ImageList1->AddIcon(MyIcon);
  ListBox1->Items->Add("");
  i++;
 }
}
//--------------------------------

To nie wszystko, teraz tworzymy zdarzenie OnDrawItem dla ListBox1 i umieszczamy w nim taki oto kod:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::ListBox1DrawItem(TWinControl *Control, int Index,
        TRect &Rect, TOwnerDrawState State)
{
 if(State.Contains(odSelected))
 {
  ListBox1->Canvas->Brush->Color = clSilver;
 }
 ListBox1->Canvas->FillRect(Rect);
 ImageList1->Draw(ListBox1->Canvas, Rect.Left + 3, Rect.Top + 2, Index, true);
}
//--------------------------------

Teraz można już wczytać w zdarzeniu OnClick dla przycisku Button1 ikony z jakiegoś pliku do ListBox1. Ikony zostaną załadowane po wybraniu w OpenDialog1 dowolnego pliku, na pierwszy test proponuję plik: c:\Windows\System32\Shell32.dll jako zwierający najwięcej ikon.

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 if(OpenDialog1->Execute())
 {
  GetIconsToList(OpenDialog1->FileName);
 }
}
//--------------------------------

Można posunąć się dalej i np. po wybraniu ikony na liście w zdarzeniu OnClick dla ListBox1 załadować wybraną ikonę do obiektu Image1, tylko najpierw proponuję ustawić wymiary Image1 - Width i Height na 32, a właściwość Center na true:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::ListBox1Click(TObject *Sender)
{
 if(ListBox1->ItemIndex >= 0)
 {
  Image1->Canvas->Brush->Color = clWhite;
  Image1->Canvas->FillRect(Rect(0, 0, 32, 32));
  ImageList1->Draw(Image1->Canvas, 0, 0, ListBox1->ItemIndex, true);
 }
}
//--------------------------------

To był przykład załadowania ikony do Image1 z obiektu ImageList1, jeżeli jednak znamy numer ikony, którą chcemy wyświetlić to można ją pobrać do Image1 bezpośrednio z pliku:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::ListBox1Click(TObject *Sender)
{
 if(ListBox1->ItemIndex >= 0)
 {
  HICON ico;
  ico = ExtractIcon(HInstance, OpenDialog1->FileName.c_str(), ListBox1->ItemIndex);
  Image1->Picture->Icon->Handle = ico;
 }
}
//--------------------------------

Na tym można by właściwie temat zakończyć, ale chcę pokazać jeszcze jeden sposób na wczytanie ikon do obiektu ListBox1, bez udziału komponentu ImageList1. Jest to sposób o tyle lepszy, że pozwala załadować do ListBox1 ikony bez tła i towarzyszących mu artefaktów, poza tym eliminuje się w ten sposób jeden zbędny tutaj komponent, a mianowicie ImageList1:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::GetIconsToList(AnsiString FileIcon)
{
 ListBox1->Items->Clear();

 HICON ico;
 int i = 0;
 do{
    ico = ExtractIcon(HInstance, FileIcon.c_str(), i);
    ListBox1->Items->Add("");
    i++;
   }while(ico != NULL);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 if(OpenDialog1->Execute())
  GetIconsToList(OpenDialog1->FileName);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ListBox1DrawItem(TWinControl *Control, int Index,
        TRect &Rect, TOwnerDrawState State)
{
 if(State.Contains(odSelected))
  ListBox1->Canvas->Brush->Color = clSilver;

 ListBox1->Canvas->FillRect(Rect);

 HICON ico;
 ico = ExtractIcon(HInstance, OpenDialog1->FileName.c_str(), Index);
 TIcon *MyIcon = new TIcon;
 MyIcon->Handle = ico;
 ListBox1->Canvas->Draw(Rect.Left + 3, Rect.Top + 2, MyIcon);
 delete MyIcon;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ListBox1Click(TObject *Sender)
{
 if(ListBox1->ItemIndex >= 0)
 {
  HICON ico;
  ico = ExtractIcon(HInstance, OpenDialog1->FileName.c_str(), ListBox1->ItemIndex);
  Image1->Picture->Icon->Handle = ico;
 }
}
//--------------------------------

...powrót do menu. 

Zmiana ustawień regionalnych. Aplet Panelu Sterowania: Opcje regionalne i językowe.

    Do zmiany ustawień 'Opcji regionalnych i językowych' służ funkcja SetLocaleInfo. Pobiera ona trzy argumenty: pierwszy argument precyzuje z której lokalizacji będą pobierane lub zmieniane opcje regionalne lub językowe. Dostępne są tutaj dwie wartości: LOCALE_SYSTEM_DEFAULT - dla opcji systemowych; LOCALE_USER_DEFAULT - dla opcji użytkownika. Drugi argument precyzuje jakie opcje chcemy pobrać lub zmienić w wybranej lokalizacji, trzeci argument jest wartością typu AnsiString i służy do zmiany wybranej opcji.
Przykład zmiany daty długiej w opcjach regionalnych i językowych na zakładce 'Dostosuj opcje regionalne' | 'Data':


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 SetLocaleInfo(LOCALE_SYSTEM_DEFAULT, LOCALE_SLONGDATE, "dddd dd MMMM yyyy");
}
//--------------------------------

Jeśli natomiast chcemy zmienić separator dziesiętny wystarczy tylko zmienić drugi i trzeci argument w funkcji SetLocaleInfo:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 SetLocaleInfo(LOCALE_SYSTEM_DEFAULT, LOCALE_SDECIMAL, ".");
}
//--------------------------------

Lista parametrów opcji:

LOCALE_ICALENDARTYPE
LOCALE_ICURRDIGITS
LOCALE_ICURRENCY
LOCALE_IDIGITS
LOCALE_IFIRSTDAYOFWEEK
LOCALE_IFIRSTWEEKOFYEAR
LOCALE_ILZERO
LOCALE_IMEASURE
LOCALE_INEGCURR
LOCALE_INEGNUMBER
LOCALE_ITIME
LOCALE_S1159
LOCALE_S2359
LOCALE_SCURRENCY
LOCALE_SDATE
LOCALE_SDECIMAL
LOCALE_SGROUPING
LOCALE_SLIST
LOCALE_SLONGDATE
LOCALE_SMONDECIMALSEP
LOCALE_SMONGROUPING
LOCALE_SMONTHOUSANDSEP
LOCALE_SNEGATIVESIGN
LOCALE_SPOSITIVESIGN
LOCALE_SSHORTDATE
LOCALE_STHOUSAND
LOCALE_STIME
LOCALE_STIMEFORMAT

Na zakończenie przedstawię jeszcze funkcję GetLocaleInfo odczytującą ustawienia opcji regionalnych i językowych. Ta funkcja pobiera cztery argumenty z tym, że w przypadku dwóch pierwszych argumentów postępujemy dokładnie tak samo jak z funkcją SetLocaleInfo, natomiast trzeci argument to wskaźnik do bufora przechowującego informacje, a czwarty argument to rozmiar tego bufora.

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 char Info[255];
 GetLocaleInfo(LOCALE_SYSTEM_DEFAULT, LOCALE_SLONGDATE, Info, sizeof(Info));
 Label1->Caption = (String)Info;
}
//--------------------------------

...powrót do menu. 

Przywołanie okna programu na pierwszy plan.

O tym, że ustawienie właściwości formularza FormStyle na fsStayOnTop, utrzymuje go zawsze na wierzchu wiedzą już chyba wszyscy. Takie rozwiązanie jednak nie zawsze może wystarczać, ponieważ zawsze na wierzchu pozostaje tylko okno główne programu, jeśli ustawimy tak tą właściwość dla jakiegoś okna wtórnego, to będzie ono zawsze na wierzchu tylko w odniesieniu do okna głównego. Jeśli zachodzi potrzeba przywołania okna programu na pierwszy plan, można by się posłużyć funkcją SetForegroundWindow gdyby nie to, że ta funkcja działa prawidłowo tylko w Win95, w pozostałych systemach na wierzch zostanie przywołane tylko aktywne okno programu, a w innych przypadkach wywołanie tej funkcji spowoduje tylko miganie ikony na pasku zadań.
Rozwiązaniem problemu jest podczepienie procesu, który chcemy przywołać na pierwszy plan, do aktywnego obecnie procesu i dopiero wtedy wywołanie funkcji SetForegroundWindow.
Stwórzmy funkcję która zrealizuje to zadanie:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void BringWindowToFront(HWND hWnd)
{
 bool Result;
 DWORD ForegroundWindowThreadID;
 DWORD WindowThreadID;
 if(hWnd != GetForegroundWindow())
 {
  ForegroundWindowThreadID = GetWindowThreadProcessId(GetForegroundWindow(), NULL);
  WindowThreadID = GetWindowThreadProcessId(hWnd, NULL);
  if(ForegroundWindowThreadID != WindowThreadID)
  {
   AttachThreadInput(ForegroundWindowThreadID, WindowThreadID, true);
   SetForegroundWindow(hWnd);
   AttachThreadInput(ForegroundWindowThreadID, WindowThreadID, false);
  }
  else
   SetForegroundWindow(hWnd);
  ShowWindow(hWnd, SW_RESTORE);
 }
}
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 BringWindowToFront(this->Handle);
}
//--------------------------------

Jak widać tak skonstruowaną funkcję wystarczy wywołać w dowolnym zdarzeniu. Jeżeli potrzebujemy, żeby nasze okno zawsze pozostawało na wierzchu to można tą funkcję umieścić w zdarzeniu OnTimer obiektu Timer1, w ten sposób "zegar" w każdym obiegu będzie sprawdzał, czy okno znajduje się na pierwszym planie i w razie potrzeby je przywoła.
Funkcję można wykorzystać do przywołania na pierwszy plan dowolnego okna każdego innego programu, czyli nie koniecznie naszego, jest to możliwe tylko wtedy gdy znamy nazwę klasy tego programu lub nazwę okna:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 BringWindowToFront(FindWindow(NULL, "C++Builder 6"));
}
//--------------------------------

...powrót do menu. 

Ukrywanie i wyświetlanie ikon na pulpicie.

Nie wiem do czego może się to przydać, ale skoro to takie proste, to oto kod na ukrywanie ikon na pulpicie:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button5Click(TObject *Sender)
{
 HWND hWnd = FindWindowEx(FindWindow("Progman", NULL), NULL, "SHELLDLL_DefView", NULL);
 if(hWnd) ShowWindow(hWnd, SW_HIDE);
}
//--------------------------------

i ponowne wyświetlenie ikon na pulpicie

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button5Click(TObject *Sender)
{
 HWND hWnd = FindWindowEx(FindWindow("Progman", NULL), NULL, "SHELLDLL_DefView", NULL);
 if(hWnd) ShowWindow(hWnd, SW_SHOW);
}
//--------------------------------

...powrót do menu. 

Wyświetlanie predefiniowanych ikon systemu Windows.

Ikony predefiniowane to np. takie, które można sobie zobaczyć gdy wywoła się okno komunikatu za pomocą funkcji Application->MessageBox, czyli to takie jakie widać na obrazku powyżej. W przypadku komunikatów MessageBox rodzaj wykreślanej ikony określa się poprzez wskaźnik np. MB_ICONSTOP, w przypadku wyświetlania ikon predefiniowanych jest podobnie, tylko nazwy wskaźników są nieco inne, poniższa lista zawiera wykaz wskaźników predefiniowanych dla funkcji LoadIcon, którą wykorzystamy do wyświetlania tych ikon i ich odpowiedniki w MessageBox:

IDI_APPLICATION  - brak
IDI_ASTERISK       - MB_ICONINFORMATION
IDI_EXCLAMATION - MB_ICONEXCLAMATION
IDI_HAND             - MB_ICONSTOP
IDI_QUESTION      - MB_ICONQUESTION

Jak już wspomniałem do wyświetlania ikon posłużymy się funkcją LoadIcon, zwracającą wartość typy HICON i pobierającą dwa argumenty, pierwszy to wskaźnik do pliku zawierającego ikonę, jeżeli chcemy wyświetlić ikonę predefiniowaną to wstawiamy tutaj NULL, drugi argument to wskaźnik do typu wyświetlanej ikony. Pokaże na przykładzie obiektu Image1 jak w prosty sposób wyświetlić taką ikonę:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Image1->Picture->Icon->Handle = LoadIcon(NULL, IDI_QUESTION);
}
//--------------------------------

Wczytanie ikony do obiektu typu TIcon jest równie proste, ale zapisanie w obiekcie typu TBitmap wymaga już zastosowania funkcji Draw.

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 TIcon *ico = new TIcon;
 ico = LoadIcon(NULL, IDI_QUESTION);
 Image1->Picture->Icon = ico;
 delete ico;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 using Graphics::TBitmap;
 TBitmap *Bitmapa = new TBitmap;
 TIcon *ico = new TIcon;
 ico->Handle = LoadIcon(NULL, IDI_QUESTION);
 Bitmapa->Width = ico->Width;
 Bitmapa->Height = ico->Height;
 Bitmapa->Canvas->Draw(0, 0, ico);
 Image1->Picture->Bitmap->Assign(Bitmapa);
 delete ico, Bitmapa;
}
//--------------------------------

Jeżeli natomiast zapragnęlibyśmy umieścić taką ikoną np. w obiekcie ListBox1, to wymaga to stworzenia specjalnej funkcji, która będzie wczytywała ikonę, a następnie dokona jej konwersji na bitmapę i przeskaluje do szerokości wiersza (ItemHeight) na liście. Można by zadanie uprościć umieszczając na liście od razu ikonę, bez konwersji na bitmapę, jednak ikonie nie można zmieniać rozmiarów, czyli jeżeli wczytujemy ikonę o wymiarach 32x32 to nie można jej przeskalować i zmniejszyć np. do wymiarów 16x16, dlatego niezbędna jest konwersja na bitmapę. Właściwość Style obiektu Image1 ustawiamy na lbOwnerDrawFixed, możemy również zwiększyć nieco szerokość wiersza ustawiając właściwość ItemHeight na 20. Funkcja jest napisana w taki sposób, że automatycznie będzie skalować ikonę i umieszczać ją z lewej strony, wycentruje też tekst w poziomie wyrównując go również do lewej strony z odstępem od ikony ustawionym na 5:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall DrawPredefinedIcon(LPCTSTR lpIconName, TListBox *Lc,
        TRect lRect, int Index)
{
 unsigned short h = Lc->ItemHeight - 2;
 TRect myRect;
 myRect.Left = lRect.Left + 2;
 myRect.Top = lRect.Top + 2;
 myRect.Bottom = lRect.Top + h;
 myRect.Right = lRect.Left + h;
 TIcon *ico = new TIcon;
 ico->Handle = LoadIcon(NULL, lpIconName);
 using Graphics::TBitmap;
 TBitmap *Bitmap = new TBitmap;
 Bitmap->Width = ico->Width;
 Bitmap->Height = ico->Height;
 Bitmap->Canvas->Brush->Color = Lc->Canvas->Brush->Color;
 Bitmap->Canvas->FillRect(Rect(0, 0, Bitmap->Width, Bitmap->Height));
 Bitmap->Canvas->Draw(0, 0, ico);
 Lc->Canvas->FillRect(lRect);
 Lc->Canvas->StretchDraw(myRect, Bitmap);
 unsigned short th = (lRect.Height() - Lc->Canvas->TextHeight(Lc->Items->Strings[Index]))/2;
 Lc->Canvas->TextOut(myRect.Right + 5, lRect.Top + th, Lc->Items->Strings[Index]);
 delete ico, Bitmap;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ListBox1DrawItem(TWinControl *Control, int Index,
        TRect &Rect, TOwnerDrawState State)
{
 if(Index == 1) // ikona zostanie umieszczona tylko w pierwszym wierszu
  DrawPredefinedIcon(IDI_HAND, dynamic_cast<TListBox*>(Control), Rect, Index);
 else
  DrawPredefinedIcon(NULL, dynamic_cast<TListBox*>(Control), Rect, Index);
}
//--------------------------------

Jeżeli chcielibyśmy zrobić to samo z obiektem ComboBox to należy zamienić w nagłówku funkcji DrawPredefinedIcon argument TListBox *Lc na TComboBox *Lc.

...powrót do menu.