Wyliczanie zasobów.

Czym są zasoby nie muszę chyba nikomu tłumaczyć, w tej poradzie pokażę jak wyliczyć zasoby. Skupię się na wyliczaniu zasobów według typu i nazwy zasobu, bo to jest tutaj w zasadzie najważniejsze, szczególnie jeżeli chce się poddać jakieś zasoby edycji, ale o edycji zasobów w innej poradzie.
Do wyliczania zasobów według typu służy funkcja:
BOOL EnumResourceTypes (HMODULE hModule, ENUMRESTYPEPROC lpEnumFunc, LONG_PTR lParam); a do wyliczania zasobów na podstawie nazwy służy funkcja: BOOL EnumResourceNames(HMODULE hModule, LPCTSTR lpszType, ENUMRESNAMEPROC lpEnumFunc, LONG_PTR lParam); łącząc ze sobą te dwie funkcje otrzymamy jako wynik spis zasobów według typu z przypisanymi do każdego typu nazwami zasobów. Jeżeli ktoś korzystał z programu Resource Hacker to zauważył tam na pewno po lewej stronie drzewo zasobów, to drzewo to obiekt typu TTreeView.
Pokażę tutaj jak zrobić dokładnie takie samo drzewo zasobów, czyli należy umieścić na formularzu obiekt TreeView1. Zanim zaprezentuję kod, trochę wyjaśnień.
W plikach zasobów np.*.EXE i w bibliotekach np.DLL występuje wiele rodzajów zasobów, a funkcje wyliczą tutaj wszystkie zasoby, więc w zasadzie wszystko co znajduje się w takim pliku jest zasobem, a więc wszelkie kursory i ikony, nie tylko te dodane np. przez Ciebie do programu w postaci zasobów, ale również te występujące w komponentach dodanych przez program.
Funkcje wyliczą wszystkie zasoby, a niektóre typy i nazwy zasobów będą posiadały tylko numer i tak np. typ zasobu o numerze 1 oznacza Cursor i jest to typ zasobu RT_CURSOR itp. Jak więc widać funkcje wyliczające nie zwracają nazwy typu zasobu, lecz jego numer w tablicy zasobów, za wyjątkiem pewnych szczególnych typów jak np. "WAVE". Jeżeli chcemy, żeby funkcje zwracały nazwy zamiast numerów, no to trzeba je wyposażyć w odpowiedni kod tłumaczący. Ja zawrę tutaj tylko te najbardziej oczywiste nazwy typów zasobów, a pozostałe będę reprezentowane przez numer.
Przedstawiam kompletny kod wyliczający zasoby:

//--------------------------------
#include <stdio.h>
__int8 node; // ośmiobitowa zakree od -127 do 127, można też użyć po prostu __int16 lub int itp...
BOOL WINAPI EnumNamesFunc(HMODULE hModule, LPCTSTR lpType, LPTSTR lpName, TTreeView *TreeView)
{
 char buffer[100];
 if((ULONG)lpName & 0xFFFF0000)
 {
  sprintf(buffer, "%s", lpName);
 }
 else
 {
  sprintf(buffer, "%u", (USHORT)lpName);
 }
 TreeView->Items->AddChild(TreeView->Items->Item[node], (String)buffer);
 return true;
}
//---------------------------------------------------------------------------
BOOL WINAPI EnumTypesFunc(HMODULE hModule, LPTSTR lpType, TTreeView *TreeView)
{
 char buffer[100];
 if((ULONG)lpType & 0xFFFF0000)
 {
  sprintf(buffer, "%s", lpType);
 }
 else
 {
  sprintf(buffer, "%u", (USHORT)lpType);
 }

 int nr;
 String name;

 try
 {
  nr = StrToInt((String)buffer);
  name = ((String)buffer);
 }
 catch(...)
 {
  nr = -1;
  name = (String)buffer;
 }

 if(nr >= 0)
 {
  switch(nr)
  {
   case 1: name = "Kursor";       break;
   case 2: name = "Bitmapa";      break;
   case 3: name = "Ikona";        break;
   case 5: name = "Dialog";       break;
   case 6: name = "String Table"; break;
   case 10: name = "RCDATA";      break;
   case 12: name = "Zbiór Kursorów"; break;
   case 14: name = "Zbiór Ikon"; break;
   case 24: name = "MANIFEST";   break;
  }
 }
 TreeView->Items->Add(0, name);
 node = TreeView->Items->Count - 1;

 EnumResourceNames(hModule, lpType, (ENUMRESNAMEPROC)EnumNamesFunc, (LONG)TreeView);
 return true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
 String FileName = "c:\\Projekty\\Project1\\Project1.exe"; // plik z zasobami
 if(!FileExists(FileName)) ShowMessage("BŁĄD! Nie znaleziono pliku");

 HANDLE hfile = LoadLibrary(FileName.c_str());
 if(hfile != NULL)
 {
  EnumResourceTypes(hfile, (ENUMRESTYPEPROC)EnumTypesFunc, (LONG)TreeView1);
  FreeLibrary(hfile);
 }
}
//--------------------------------

Na zakończenie jeszcze przykład wyliczania zasobów określonego typu, do tego celu trzeba trochę zmodyfikować funkcję EnumTypesFunc:

BOOL WINAPI EnumTypesFunc(HMODULE hModule, LPTSTR lpType, TTreeView *TreeView)
{
 char buffer[100];
 if((ULONG)lpType & 0xFFFF0000)
 {
  sprintf(buffer, "%s", lpType);
 }
 else
 {
  sprintf(buffer, "%u", (USHORT)lpType);
 }

 int nr;
 String name;

 try
 {
  nr = StrToInt((String)buffer);
  name = ((String)buffer);
 }
 catch(...)
 {
  nr = -1;
  name = (String)buffer;
 }

 if(nr >= 0)
 {
  switch(nr)
  {
   case 1: name = "Kursor";       break;
   case 2: name = "Bitmapa";      break;
   case 3: name = "Ikona";        break;
   case 5: name = "Dialog";       break;
   case 6: name = "String Table"; break;
   case 10: name = "RCDATA";      break;
   case 12: name = "Zbiór Kursorów"; break;
   case 14: name = "Zbiór Ikon"; break;
   case 24: name = "MANIFEST";   break;
  }
 }
 if((String)buffer == "WAVE" || (String)buffer == (USHORT)RT_CURSOR)
 {
  TreeView->Items->Add(0, name);
  node = TreeView->Items->Count - 1;
  EnumResourceNames(hModule, lpType, (ENUMRESNAMEPROC)EnumNamesFunc, (LONG)TreeView);
 }
 return true;
}

...powrót do menu. 

Edycja zasobów (modyfikacja, usuwanie, wstawianie).

Edycja zasobów jeszcze na etapie programowania jest zadaniem banalnie prostym, dlatego ta porada jest o tym jak edytować zasoby w plikach zasobów np. *.EXE, *.DLL, bez udziału kompilatora, czyli w plikach już skompilowanych z poziomu własnego programu. Zaznaczę tutaj od razu, że program edytujący zasoby, nie może edytować sam siebie, czyli mówiąc jeszcze prościej działający program, uruchomiony, czyli aktywny proces nie może zmienić nic w swoim własnym pliku, dzieje się tak dlatego, że nie można nic zmieniać w aktywnym procesie, a każdy uruchomiony plik *.exe staje się aktywnym procesem, więc program edytujący zasoby nie zmieni nic nie tylko we własnym pliku (*.exe), ale również nie będzie w stanie tego zrobić w żadnym innym uruchomionym pliku *.EXE.
Do edycji zasobów służy funkcja UpdateResource pobierająca sześć argumentów, i tak pierwszy w kolejności to uchwyt do pliku zawierającego zasoby, które chcemy zmienić, drugi to typ zmienianego zasobu, trzeci to nazwa zasobu, czwarty to identyfikator języka zasobu, który chcemy zmienić, piąty to wskaźnik do zasobu który zamieni zamieni oryginalny zasób w pliku, a szósty to rozmiar tego zasobu. Funkcja UpdateResource działa w połączeniu z dwiema innymi funkcjami BeginUpdateResource, która zwraca uchwyt do pliku zawierającego zasoby, które będziemy edytować i EndUpdateResurce kończąca dokonywanie zmian w pliku z zasobami i zapisująca te zmiany. Zapisująca, gdyż funkcja UpdateResource dokonuje zmian tylko w pamięci, a dopiero wywołanie funkcji EndUpdateResource dokonuje tych zmian w pliku.
Zasób który chcemy podmienić w pliku zasobów trzeba wczytać do pamięci, gdyż nie można go od razu "władować" do pliku, dlatego należy stworzyć obiekt klasy TMemoryStream wczytując do niego podmieniany zasób. Co do identyfikatora języka zasobu (dotyczy tylko niektórych typów zasobu), to trzeba podawać prawidłowy identyfikator języka, natomiast jako sublanguage można podać SUBLANG_DEFAULT o ile dany język nie ma jakiejś swojej odmiany, w przykładzie podałem dane neutralne

//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 TMemoryStream *memory = NULL;
 String FileName = ExtractFilePath(ParamStr(0)) + "test.exe"; // plik z zasobami
 if(!FileExists(FileName))
 {
  ShowMessage("BŁĄD! NIe odnaleziono pliku");
  return;
 }

 HANDLE hfile = BeginUpdateResource(FileName.c_str(), false);
 if(hfile == NULL ) return;

 memory = new TMemoryStream();
 memory->LoadFromFile(ExtractFilePath(ParamStr(0)) + "music1.wav");

 if(UpdateResource(hfile, "WAVE", "ID_SONG1",
                   MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
                   memory->Memory, memory->Size))
 {
  EndUpdateResource(hfile, false);
 }
 if(memory != NULL) delete memory;
}
//---------------------------------------------------------------------------

Tablice Language i SubLanguage:

Identifier Predefined symbol Language
0x00 LANG_NEUTRAL Neutral
0x01 LANG_ARABIC Arabic
0x02 LANG_BULGARIAN Bulgarian
0x03 LANG_CATALAN Catalan
0x04 LANG_CHINESE Chinese
0x05 LANG_CZECH Czech
0x06 LANG_DANISH Danish
0x07 LANG_GERMAN German
0x08 LANG_GREEK Greek
0x09 LANG_ENGLISH English
0x0a LANG_SPANISH Spanish
0x0b LANG_FINNISH Finnish
0x0c LANG_FRENCH French
0x0d LANG_HEBREW Hebrew
0x0e LANG_HUNGARIAN Hungarian
0x0f LANG_ICELANDIC Icelandic
0x10 LANG_ITALIAN Italian
0x11 LANG_JAPANESE Japanese
0x12 LANG_KOREAN Korean
0x13 LANG_DUTCH Dutch
0x14 LANG_NORWEGIAN Norwegian
0x15 LANG_POLISH Polish
0x16 LANG_PORTUGUESE Portuguese
0x18 LANG_ROMANIAN Romanian
0x19 LANG_RUSSIAN Russian
0x1a LANG_CROATIAN Croatian
0x1a LANG_SERBIAN Serbian
0x1b LANG_SLOVAK Slovak
0x1c LANG_ALBANIAN Albanian
0x1d LANG_SWEDISH Swedish
0x1e LANG_THAI Thai
0x1f LANG_TURKISH Turkish
0x20 LANG_URDU Urdu
0x21 LANG_INDONESIAN Indonesian
0x22 LANG_UKRAINIAN Ukrainian
0x23 LANG_BELARUSIAN Belarusian
0x24 LANG_SLOVENIAN Slovenian
0x25 LANG_ESTONIAN Estonian
0x26 LANG_LATVIAN Latvian
0x27 LANG_LITHUANIAN Lithuanian
0x29 LANG_FARSI Farsi
0x2a LANG_VIETNAMESE Vietnamese
0x2b LANG_ARMENIAN Armenian
0x2c LANG_AZERI Azeri
0x2d LANG_BASQUE Basque
0x2f LANG_MACEDONIAN FYRO Macedonian
0x36 LANG_AFRIKAANS Afrikaans
0x37 LANG_GEORGIAN Georgian
0x38 LANG_FAEROESE Faeroese
0x39 LANG_HINDI Hindi
0x3e LANG_MALAY Malay
0x3f LANG_KAZAK Kazak
0x40 LANG_KYRGYZ Kyrgyz
0x41 LANG_SWAHILI Swahili
0x43 LANG_UZBEK Uzbek
0x44 LANG_TATAR Tatar
0x45 LANG_BENGALI Not supported.
0x46 LANG_PUNJABI Punjabi
0x47 LANG_GUJARATI Gujarati
0x48 LANG_ORIYA Not supported.
0x49 LANG_TAMIL Tamil
0x4a LANG_TELUGU Telugu
0x4b LANG_KANNADA Kannada
0x4c LANG_MALAYALAM Not supported.
0x4d LANG_ASSAMESE Not supported.
0x4e LANG_MARATHI Marathi
0x4f LANG_SANSKRIT Sanskrit
0x50 LANG_MONGOLIAN Mongolian
0x56 LANG_GALICIAN Galician
0x57 LANG_KONKANI Konkani
0x58 LANG_MANIPURI Not supported.
0x59 LANG_SINDHI Not supported.
0x5a LANG_SYRIAC Syriac
0x60 LANG_KASHMIRI Not supported.
0x61 LANG_NEPALI Not supported.
0x65 LANG_DIVEHI Divehi
0x7f LANG_INVARIANT  
 
Identifier Predefined symbol Language
0x00 SUBLANG_NEUTRAL Language neutral
0x01 SUBLANG_DEFAULT User Default
0x02 SUBLANG_SYS_DEFAULT System Default
0x01 SUBLANG_ARABIC_SAUDI_ARABIA Arabic (Saudi Arabia)
0x02 SUBLANG_ARABIC_IRAQ Arabic (Iraq)
0x03 SUBLANG_ARABIC_EGYPT Arabic (Egypt)
0x04 SUBLANG_ARABIC_LIBYA Arabic (Libya)
0x05 SUBLANG_ARABIC_ALGERIA Arabic (Algeria)
0x06 SUBLANG_ARABIC_MOROCCO Arabic (Morocco)
0x07 SUBLANG_ARABIC_TUNISIA Arabic (Tunisia)
0x08 SUBLANG_ARABIC_OMAN Arabic (Oman)
0x09 SUBLANG_ARABIC_YEMEN Arabic (Yemen)
0x0a SUBLANG_ARABIC_SYRIA Arabic (Syria)
0x0b SUBLANG_ARABIC_JORDAN Arabic (Jordan)
0x0c SUBLANG_ARABIC_LEBANON Arabic (Lebanon)
0x0d SUBLANG_ARABIC_KUWAIT Arabic (Kuwait)
0x0e SUBLANG_ARABIC_UAE Arabic (U.A.E.)
0x0f SUBLANG_ARABIC_BAHRAIN Arabic (Bahrain)
0x10 SUBLANG_ARABIC_QATAR Arabic (Qatar)
0x01 SUBLANG_AZERI_LATIN Azeri (Latin)
0x02 SUBLANG_AZERI_CYRILLIC Azeri (Cyrillic)
0x01 SUBLANG_CHINESE_TRADITIONAL Chinese (Traditional)
0x02 SUBLANG_CHINESE_SIMPLIFIED Chinese (Simplified)
0x03 SUBLANG_CHINESE_HONGKONG Chinese (Hong Kong SAR, PRC)
0x04 SUBLANG_CHINESE_SINGAPORE Chinese (Singapore)
0x05 SUBLANG_CHINESE_MACAU Chinese (Macao SAR)
0x01 SUBLANG_DUTCH Dutch
0x02 SUBLANG_DUTCH_BELGIAN Dutch (Belgian)
0x01 SUBLANG_ENGLISH_US English (US)
0x02 SUBLANG_ENGLISH_UK English (UK)
0x03 SUBLANG_ENGLISH_AUS English (Australian)
0x04 SUBLANG_ENGLISH_CAN English (Canadian)
0x05 SUBLANG_ENGLISH_NZ English (New Zealand)
0x06 SUBLANG_ENGLISH_EIRE English (Ireland)
0x07 SUBLANG_ENGLISH_SOUTH_AFRICA English (South Africa)
0x08 SUBLANG_ENGLISH_JAMAICA English (Jamaica)
0x09 SUBLANG_ENGLISH_CARIBBEAN English (Caribbean)
0x0a SUBLANG_ENGLISH_BELIZE English (Belize)
0x0b SUBLANG_ENGLISH_TRINIDAD English (Trinidad)
0x0c SUBLANG_ENGLISH_ZIMBABWE English (Zimbabwe)
0x0d SUBLANG_ENGLISH_PHILIPPINES English (Philippines)
0x01 SUBLANG_FRENCH French
0x02 SUBLANG_FRENCH_BELGIAN French (Belgian)
0x03 SUBLANG_FRENCH_CANADIAN French (Canadian)
0x04 SUBLANG_FRENCH_SWISS French (Swiss)
0x05 SUBLANG_FRENCH_LUXEMBOURG French (Luxembourg)
0x06 SUBLANG_FRENCH_MONACO French (Monaco)
0x01 SUBLANG_GERMAN German
0x02 SUBLANG_GERMAN_SWISS German (Swiss)
0x03 SUBLANG_GERMAN_AUSTRIAN German (Austrian)
0x04 SUBLANG_GERMAN_LUXEMBOURG German (Luxembourg)
0x05 SUBLANG_GERMAN_LIECHTENSTEIN German (Liechtenstein)
0x01 SUBLANG_ITALIAN Italian
0x02 SUBLANG_ITALIAN_SWISS Italian (Swiss)
0x01 SUBLANG_KOREAN Korean
0x01 SUBLANG_LITHUANIAN Lithuanian
0x01 SUBLANG_MALAY_MALAYSIA Malay (Malaysia)
0x02 SUBLANG_MALAY_BRUNEI_DARUSSALAM Malay (Brunei Darassalam)
0x01 SUBLANG_NORWEGIAN_BOKMAL Norwegian (Bokmal)
0x02 SUBLANG_NORWEGIAN_NYNORSK Norwegian (Nynorsk)
0x01 SUBLANG_PORTUGUESE_BRAZILIAN Portuguese (Brazil)
0x02 SUBLANG_PORTUGUESE Portuguese (Portugal)
0x02 SUBLANG_SERBIAN_LATIN Serbian (Latin)
0x03 SUBLANG_SERBIAN_CYRILLIC Serbian (Cyrillic)
0x01 SUBLANG_SPANISH Spanish (Castilian)
0x02 SUBLANG_SPANISH_MEXICAN Spanish (Mexican)
0x03 SUBLANG_SPANISH_MODERN Spanish (Spain)
0x04 SUBLANG_SPANISH_GUATEMALA Spanish (Guatemala)
0x05 SUBLANG_SPANISH_COSTA_RICA Spanish (Costa Rica)
0x06 SUBLANG_SPANISH_PANAMA Spanish (Panama)
0x07 SUBLANG_SPANISH_DOMINICAN_REPUBLIC Spanish (Dominican Republic)
0x08 SUBLANG_SPANISH_VENEZUELA Spanish (Venezuela)
0x09 SUBLANG_SPANISH_COLOMBIA Spanish (Colombia)
0x0a SUBLANG_SPANISH_PERU Spanish (Peru)
0x0b SUBLANG_SPANISH_ARGENTINA Spanish (Argentina)
0x0c SUBLANG_SPANISH_ECUADOR Spanish (Ecuador)
0x0d SUBLANG_SPANISH_CHILE Spanish (Chile)
0x0e SUBLANG_SPANISH_URUGUAY Spanish (Uruguay)
0x0f SUBLANG_SPANISH_PARAGUAY Spanish (Paraguay)
0x10 SUBLANG_SPANISH_BOLIVIA Spanish (Bolivia)
0x11 SUBLANG_SPANISH_EL_SALVADOR Spanish (El Salvador)
0x12 SUBLANG_SPANISH_HONDURAS Spanish (Honduras)
0x13 SUBLANG_SPANISH_NICARAGUA Spanish (Nicaragua)
0x14 SUBLANG_SPANISH_PUERTO_RICO Spanish (Puerto Rico)
0x01 SUBLANG_SWEDISH Swedish
0x02 SUBLANG_SWEDISH_FINLAND Swedish (Finland)
0x01 SUBLANG_URDU_PAKISTAN Urdu (Pakistan)
0x02 SUBLANG_URDU_INDIA Urdu (India)
0x01 SUBLANG_UZBEK_LATIN Uzbek (Latin)
0x02 SUBLANG_UZBEK_CYRILLIC Uzbek (Cyrillic)
 

Nie wszystkie zasoby można jednak w tak prosty sposób modyfikować, działa to bez problemu na bitmapach i zasobach typu WAVE, ale np. z ikonami i kursorami są już problemy, tutaj wymagana jest specjalna technika o której nie będzie w tej poradzie.
Funkcja UpdateResource pozwala również na dodawania nowych zasobów, wystarczy podać typ zasobu i nazwę zasobu której nie ma w pliku zasobów, a taki zasób zostanie dodany. Jeżeli chcemy usuwać zasób to jako piąty parametr funkcji UpdateResource podajemy wartość NULL, a jako szósty 0, oczywiście trzeba podać istniejący typ zasobu i nazwę,

...powrót do menu. 

Komedy: Pokaż pulpit, Właściwosci Paska Zadań itp...

Tym razem pokażę kod na wywoływanie komendy Pokaż pulpit, która jest chyba wszystkim użytkownikom Windows dobrze znana. W celu wywołania tej komendy trzeba się podłączyć do biblioteki Shell32.dll, a można to zrobić poprzez utworzenie obiektu klasy IShellDispatch4, a żeby to było możliwe trzeba włączyć do pliku źródłowego do sekcji include bibliotekę shlobj.h. Biblioteka ta ma jednak to do siebie, że wywołuje liczne błędy, dlatego na samym początku pliku źródłowego trzeba dodać definicję NO_WIN32_LEAN_AND_MEAN.
Niżej przedstawiam kompletny kod na wywołanie polecenia Pokaż pulpit w zdarzeniu OnClick przycisku Button1:

//---------------------------------------------------------------------------
#define NO_WIN32_LEAN_AND_MEAN
#include <vcl.h>
#pragma hdrstop

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

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


TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
 CoInitialize(NULL);

 IShellDispatch4* pShell4;
 CoCreateInstance(__uuidof(Shell), NULL, CLSCTX_INPROC_SERVER, __uuidof(IShellDispatch4), (void**)&pShell4);
 pShell4->ToggleDesktop();
 pShell4->Release();

 CoUninitialize();
}

Po naciśnięciu przycisku Button1 wszystkie okna zostaną zminimalizowane, problem jednak w tym, że okno naszego programu również, a żeby je ponownie przywrócić na pulpit hurtem, czyli wszystkie na raz, trzeba ponownie wywołać ten kod, ale skoro nasze okno jest zminimalizowane, to żeby nacisnąć na Button1 musimy je przywrócić i tutaj jest problem, ponieważ po ponownym kliknięciu na Button1 nasze okno znów zostanie zminimalizowane, a pozostałe okna nie zostaną przywrócone. Można obejść ten problem poprzez minimalizację naszego programu do paska zadań, za pomocą komponentu TTrayIcon i przypisaniu do niego PopupMenu z poleceniem Pokaż pulpit, wtedy gdy klikniemy prawym klawiszem myszy na ikonie naszego programu to pokaże się menu z którego będzie można wybrać polecenie pokaż pulpit.
Trochę to skomplikowane, ale jest prostszy sposób, zamiast minimalizacji wszystkich okien za pomocą funkcji ToggleDesktop(), można je zminimalizować używając funkcji MinimizeAll(), a przywrócić za pomocą funkcji UndoMinimizeAll():

//---------------------------------------------------------------------------
#define NO_WIN32_LEAN_AND_MEAN
#include <vcl.h>
#pragma hdrstop

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

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


TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
 CoInitialize(NULL);

 IShellDispatch4* pShell4;
 CoCreateInstance(__uuidof(Shell), NULL, CLSCTX_INPROC_SERVER, __uuidof(IShellDispatch4), (void**)&pShell4);
 pShell4->MinimizeAll();
 pShell4->Release();

 CoUninitialize();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 CoInitialize(NULL);

 IShellDispatch4* pShell4;
 CoCreateInstance(__uuidof(Shell), NULL, CLSCTX_INPROC_SERVER, __uuidof(IShellDispatch4), (void**)&pShell4);
 pShell4->UndoMinimizeAll();
 pShell4->Release();

 CoUninitialize();
}

Klasa IShellDispatch4 pozwala na wiele więcej można np. przywołać okno Właściwości Paska zadań i Menu Start: pShell4->TrayProperties();
Kilka innych komend:
    CascadeWindows()        -    rozmieszcza kaskadowo okna na pulpicie
    FileRun()                         -    wyświetla okno Uruchom (menu Start - Uruchom)
    FindComputer()              -    uruchamia okno wyszukiwania komputera
    FindFiles()                      -    uruchamia okno wyszukiwania plików
    SetTime()                       -    uruchamia okno Właściwości data i godzina
    ShutdownWindows()     -    przywołuje okno Zamykanie systemu Windows

...powrót do menu. 

Zmiana wyglądu dymka podpowiedzi (HINT)

Do zmiany wyglądu, czyli np. koloru, obramowania, stylu i rodzaju czcionki dymka podpowiedzi tzw. Hint służy klasa specjalna HintWindowClass, jednak skorzystanie z tej klasy wymaga stworzenia innej klasy dziedziczącej po klasie THintWindow, dlatego też musimy najpierw stworzyć taką klasę.
Tworzymy nowy projekt, a następnie z menu File | New | Unite, zapisujemy nowo utworzone pliki *.cpp i *.h pod dowolną nazwą w katalogu z projektem, np. pod nazwą Hintclass.*. Jeżeli pliki nie zostały automatycznie dodane do projektu to wybieramy z menu Project | Add to project i włączmy do projektu plik Hintclass.cpp, a w pliku nagłówkowym głównego formularza programu umieszczamy wpis
#include "Hintclass.h".
Teraz przechodzimy do pliku Hintclass.h i tworzymy nową klasę, ja nadałem jej nazwę TBlackHint, lecz nazwa jest dowolna:

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

#ifndef HintclassH
#define HintclassH

#include <Classes.hpp>
#include <Controls.hpp>
#include <Forms.hpp>

//---------------------------------------------------------------------------
class TBlackHint : public THintWindow
{
public:
        __fastcall TBlackHint(TComponent* Owner);

private:
        bool FActivating;
        void __fastcall Paint(void);
        virtual void __fastcall NCPaint(HDC hdc);
        virtual void __fastcall CreateParams(TCreateParams &Params);
        virtual void __fastcall ActivateHint(const TRect &Rect, const String AHint);
};

#endif

Następnie przechodzimy do pliku Hintclass.cpp i umieszczamy w nim taki kod:

//---------------------------------------------------------------------------
#pragma hdrstop

#include "Hintclass.h"

//---------------------------------------------------------------------------
#pragma package(smart_init)
__fastcall TBlackHint::TBlackHint(TComponent* Owner) :
        THintWindow(Owner)
{
 Canvas->Font->Name = "Verdana";
 Canvas->Font->Color = clWhite;
 Canvas->Font->Size = 8;
}
//---------------------------------------------------------------------------
void __fastcall TBlackHint::Paint(void)
{
 Width = Canvas->TextWidth(Caption) + 16;
 Height = Canvas->TextHeight(Caption) + 7;
 Color = clBlack;
 TRect R = ClientRect;
 R.Left = R.Left + 1;
 R.Top = R.Top + 2;
 R.Right = R.Right - 1;
 R.Bottom = R.Bottom;
 Canvas->Pen->Color = (TColor)0x002868D8;
 Canvas->Rectangle(R.Left, R.Top - 2, R.Right + 1, R.Bottom);
 DrawText(Canvas->Handle, Caption.c_str(), -1, &R, DT_CENTER | DT_VCENTER |
          DT_NOPREFIX | DT_WORDBREAK | DT_NOCLIP | DrawTextBiDiModeFlagsReadingOnly());
}
//---------------------------------------------------------------------------
void __fastcall TBlackHint::NCPaint(HDC hdc)
{
 Invalidate();
}
//---------------------------------------------------------------------------
void __fastcall TBlackHint::CreateParams(TCreateParams &Params)
{
 Params.Style = Params.Style & ~WS_BORDER;
 THintWindow::CreateParams(Params);
}
//---------------------------------------------------------------------------
void __fastcall TBlackHint::ActivateHint(const TRect &Rect, const String AHint)
{
 FActivating = true;
 try
 {
  Caption = AHint;
  TRect r = Rect;
  r.Left -= 10;
  r.Right += 10;
  r.Top -= 5;
  r.Bottom += 5;
  UpdateBoundsRect(r);
  if(r.Top + Height > Screen->DesktopHeight) r.Top = Screen->DesktopHeight - Height;
  if(r.Left + Width > Screen->DesktopWidth) r.Left = Screen->DesktopWidth - Width;
  if(r.Left < Screen->DesktopLeft) r.Left = Screen->DesktopLeft;
  if(r.Bottom < Screen->DesktopTop) r.Bottom = Screen->DesktopTop;
  HRGN hrgn = CreateRectRgn(0, 0, r.Width(), r.Height());
  SetWindowRgn(Handle, hrgn, true);
  SetWindowPos(Handle, HWND_TOPMOST, r.Left, r.Top, r.Width(), r.Height(), SWP_SHOWWINDOW | SWP_NOACTIVATE);
  Invalidate();
 }
 __finally
 {
  FActivating = false;
 }
}
//---------------------------------------------------------------------------

W konstruktorze klasy definiujemy takie parametry jak np. krój czcionki, kolor rozmiar, styl itp. W zdarzeniu Paint określamy wygląd dymka, jak np. kolor, rozmiar i położenie tekstu, możemy sobie tutaj dowoli kombinować, ja w przykładzie zdefiniowałem czarne tło z pomarańczową ramką i białą czcionką. W funkcji ActiveHint określamy położenie dymka podpowiedzi na ekranie w odniesieniu do kursora i na początek proponuję nie zmieniać nic w tym kodzie, bo dymek może się wcale nie wyświetlić, lub pokaże się w zgoła nieoczekiwanym miejscu.
Gdy pliki z klasą są już gotowe i włączone do projektu, w pliku źródłowym głównego formularza  w konstruktorze klasy, aktywujemy nowy Hint:

TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 HintWindowClass = __classid(TBlackHint);
}
//---------------------------------------------------------------------------

Na zakończenie dodam, że tak zmodyfikowany Hint działa tylko w naszym programie, a nie w całym systemie, a dokładniej tylko w oknach naszego programu, przy czym klasę podłączamy tylko w głównym oknie aplikacji, w pozostałych powinno działać automatycznie.

...powrót do menu. 

Otwieranie plików powiązanych z programem zawsze tylko w otwartym programie.

Niniejsza porada stanowi uzupełnieni porady: powiązanie własnego typu pliku z własny programem. W tamtej poradzie zostało pokazane jak można otworzyć plik we własnym programie korzystając z funkcji ParamStr. Przedstawiony tam sposób ma jednak dość poważny mankament, jeżeli już otworzymy w naszym programie jakiś plik i klikniemy na innym pliku powiązanym z naszym programem, to oczywiście taki plik otworzy się w naszym programie, a dokładnie rzecz biorąc w kolejnej kopii naszego programu, a nie w już uruchomionym programie, czyli za każdym razem gdy będziemy otwierać nowy plik to będzie się jednocześnie uruchamiała nowa kopia naszego programu.
W tej poradzie pokażę jak to zmienić i otwierać nowe pliki w już otwartym programie, bez uruchamiania kolejnych kopii, czyli zawsze w jednej kopii. Mechanizm opiera się na utworzeniu mutexu uniemożliwiającemu utworzenie kopii programu, oraz na wysłaniu do już otwartego programu komunikatu zawierającego polecenie uruchomienia pliku.
Początek jest trochę nietypowy, gdy już utworzymy nowy projekt wybieramy z menu Project | View source, wyskoczy na strona z mniej więcej taką zawartością:

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

#include <vcl.h>
#pragma hdrstop

//---------------------------------------------------------------------------
USEFORM("Unit1.cpp", Dupa1);
//---------------------------------------------------------------------------
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
 try
 {
  Application->Initialize();
  Application->CreateForm(__classid(TForm1), &Form1);
  Application->Run();
 }
 catch (Exception &exception)
 {
  Application->ShowException(&exception);
 }
  catch (...)
  {
   try
   {
    throw Exception("");
   }
  catch (Exception &exception)
  {
   Application->ShowException(&exception);
  }
 }
 return 0;
}
//---------------------------------------------------------------------------

I to właśnie tutaj dokonujemy drobnych modyfikacji, wstawiając mutex i funkcje odpowiedzialne za wyszukanie otwartego już okna naszego programu w oparciu o nazwę klasy naszego programu. Tutaj kilka wyjaśnień, mutex to dowolna jednoczłonowa nazwa zawierająca tylko liczby i litery języka angielskiego, nazwa ta musi być niepowtarzalną, czyli niespotykana w innych programach. Druga rzecz to nazwa klasy naszego programu, również musi być niepowtarzalna jeżeli nasz program ma być prawidłowo odnaleziony. Nazwa klasy programu, to nazwa okna głównego programu, czyli załóżmy że nasze okno główne nazywa się Form1, nazwa klasy będzie wtedy TForm1. Żeby uniknąć błędu polegającego na powtarzaniu się klas programów w systemie, o co nie trudno jeżeli wszystkim formularzom głównym w naszych programach nadajemy nazwę Form1. Dlatego w przykładzie posłużę się mutexem o nazwie MyAssMutex i formularzem o nazwie MyAss, czyli klasa będzie nosiła nazwę TMyAss.
Po modyfikacji źródło projektu powinno wyglądać mniej więcej tak:

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

#include <vcl.h>
#pragma hdrstop

//---------------------------------------------------------------------------
USEFORM("Unit1.cpp", MyAss);
//---------------------------------------------------------------------------
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR cmdLine, int) // w nagłówku tej funkcji również występuje drobna zmiana
{
 try
 {
  HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS, 0, "MyAssMutex");

 if(!hMutex) hMutex = CreateMutex(0, 0, "MyAssMutex");
 else
 {
  HWND hWnd = FindWindow("TMyAss", 0);
  SetForegroundWindow(hWnd);

  if(strlen(cmdLine) != 0)
  {
   COPYDATASTRUCT cds;
   cds.cbData = strlen(cmdLine) + 1;
   cds.lpData = StringReplace(cmdLine, "\"", "", TReplaceFlags() << rfReplaceAll).c_str();
   SendMessage(hWnd, WM_COPYDATA, 0, (LPARAM)&cds);
  }
  return 0;
 }
 Application->Initialize();
 Application->CreateForm(__classid(TMyAss), &MyAss);
 Application->Run();

 ReleaseMutex(hMutex);
 }
 catch (Exception &exception)
 {
  Application->ShowException(&exception);
 }
 return 0;
}
//---------------------------------------------------------------------------

Jak widać po odnalezieniu okna program wysyła do niego komunikat (a dokładniej do "uruchomionego siebie") WM_COPYDATA wraz z parametrem zawierającym ścieżkę dostępu do otwieranego pliku, żeby program mógł odebrać ten komunikat trzeba w nim zaimplementować funkcję przechwytującą komunikaty. Ja posłużę się tutaj mapą komunikatów i funkcją nastawionymi na przechwytywanie właśnie komunikatu WM_COPYDATA. W tym celu w pliku nagłówkowym deklarujemy funkcję WmCopyData (nazwa dowolna), dodatkowo zadeklarujemy tutaj zmienną typu AnsiString OlPathFile zapamiętującą ścieżkę dostępu do już otwartego pliku. To się przydaje, jeżeli otwieramy np. w Memo jakiś plik tekstowy i chcemy, żeby przed otwarciem i wczytaniem do Memo nowego pliku program sprawdzał czy w starym zaszły jakieś zmiany i żeby nas pytał czy chcemy te zmiany zapisać, można też będzie anulować otwarcie nowego pliku. W sekcji public umieszczamy mapę komunikatu:

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

private:
        void __fastcall WmCopyData(TWMCopyData& Message);
        String OldPathFile;

public:
        __fastcall TMyAss(TComponent* Owner);

BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_COPYDATA, TWMCopyData, WmCopyData)
END_MESSAGE_MAP(TForm)
};

W pliku nagłówkowym umieszczamy teraz definicję tej funkcji, a wewnątrz ciała funkcji umieszczamy również kod sprawdzający czy w poprzednio otwartym pliku zaszły zmiany, no i oczywiście kod odpowiedzialny za wczytanie pliku do Memo. Dodatkowo tworzymy również zdarzenie OnCreate w którym umieszczamy kod otwierający plik w Memo gdy uruchamiamy taki plik w naszym programie, a ten program nie został jeszcze uruchomiony. Działa to tak, że jeżeli klikamy na jakimś pliku powiązanym z naszym programem, a ten program nie został jeszcze uruchomiony, to wykonuje się kod zawarty w zdarzeniu OnCreate, ale jeżeli otwieramy taki plik, a nasz program jest już uruchomiony to wykonuje się kod zawarty w funkcji WmCopyData:

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

//---------------------------------------------------------------------------
void __fastcall TMyAss::WmCopyData(TWMCopyData& Message)
{
 AnsiString slCmdLine = (char*)Message.CopyDataStruct->lpData;

 if(Memo1->Modified)
 {
  __int16 idx = Application->MessageBox("Czy chcesz zapisać zmiany w pliku", "Zapisywanie pliku", MB_YESNOCANCEL | MB_ICONQUESTION);
  switch(idx)
  {
   case ID_YES: Memo1->Lines->SaveToFile(OldPathFile); break;
   case ID_NO: break;
   case ID_CANCEL: return;
  }
 }

 Memo1->Lines->LoadFromFile(slCmdLine);

 OldPathFile = slCmdLine;
}
//------------------------------------------------------------------------------
void __fastcall TMyAss::FormCreate(TObject *Sender)
{
 if(!ParamStr(1).IsEmpty())
 Memo1->Lines->LoadFromFile(ParamStr(1));

 OldPathFile = ParamStr(1);
}
//---------------------------------------------------------------------------

Na koniec małe wyjaśnienie odnośnie Memo1 i plików tekstowych. Notatnik w Windows XP ma możliwość zapisywania plików tekstowych w kodowaniu Unicode, ale Memo1 nie potrafi prawidłowo interpretować takich plików. Memo rozpoznaje tylko kodowanie ANSII.
Jeszcze jedna informacja dla użytkowników środowiska Borland Developer Studio, jeżeli w tym środowisku nie działa wam komponent TRichEdit, to znaczy, że w systemie brakuje biblioteki Winspool.dll, trzeba ją więc skądś ściągnąć i skopiować do katalogu [...]\Windows\System32.

...powrót do menu. 

Wywoływanie nie modalnego okna komunikatu.

Chyba wszyscy wiedzą jak wywołać okno komunikatu i zapewne wszystkim jest wiadome, że takie okno zawsze jest wywoływane jako modalne, czyli dopóki nie zostanie zamknięte, to nie można się przemieszczać między innymi oknami w programie. Mamy na przykład program składający się z okna głównego i okna wtórnego (drugiego), jeżeli obydwa okna będą uruchomione, jako nie modalne, no to oczywiście można oczywiście się między nimi przemieszczać, jeżeli jednak w oknie głównym lub wtórnym wywołamy komunikat, czy to za pomocą funkcji ShowMessage, czy też Application->MessageBox, to wyskoczy nam okno komunikatu i nie będziemy mogli nic zrobić w żadnym innym oknie programu dopóki okno komunikatu nie zostanie zamknięte.
Istnieje jednak sposób na ominięcie tego, możemy wywołać okno komunikatu w taki sposób, że możliwe będzie przemieszczanie się pomiędzy innymi oknami w programie, nawet jeżeli okno komunikatu będzie otwarte. Sposób ten polega na wywołaniu funkcji MessageBox i przekazaniu jej jako pierwszego argumentu wartości NULL, gwoli wyjaśnienia, ten argument to uchwyt do okna pod które chcemy podpiąć komunikat, przekazując wartość NULL nie podpinamy tego komunikatu pod żadne okno, w ten sposób okno komunikatu nie blokuje innych okien programu.

//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 MessageBox(NULL, "Treść komunikatu", "Tytuł komunikatu", MB_OK | MB_ICONHAND);
}
//--------------------------------

Lub z zapytaniem:

//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 __int8 idx = MessageBox(NULL, "Czy chcesz zrobić coś tam?", "Tytuł komunikatu", MB_YESNOCANCEL | MB_ICONQUESTION | MB_DEFBUTTON2);
 
 switch(idx)
 {
  case ID_YES: // jakieś zadanie jeżeli wybrano przycisk TAK; break;
  case ID_NO: // jakieś zadanie jeżeli wybrano przycisk NIE; break;
  case ID_CANCEL: // przerwanie operacji jeżeli wybrano ANULUJ; return;
 }
}
//--------------------------------

Nie będę wyjaśniał co to znaczy MB_OK, MB_YESNOCANCEL, MB_ICONHAND itp... bo można o tym przeczytać w poradzie obsługa komunikatów.
Wywołanie okna komunikatu jako okno nie modalne, ma poważną wadę, jeżeli przejdziemy do innego okna w programie, to okno komunikatu zajedzie na dalszy plan, co może zaowocować tym, że zapomnimy je zamknąć, ale i temu można zaradzić wywołując to okno z parametrm MB_TOPMOST, można też wyrównać tekst w oknie komunikatu do prawej strony wywołując je z parametrem MB_RIGHT, domyślnie tekst jest wyrównywany do strony lewej, nie ma argumentu umożliwiającego wyśrodkowanie tekstu. Ciekawy efekt uzyskamy wywołując okno z parametrem MB_RTLREADING, który powoduje odwrócenie okna, czyli lewa strona staje się prawą, takie lustrzane odbicie okna komunikatu, jednak tekst nie ulega odwróceniu.

//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 MessageBox(NULL, "Treść komunikatu", "Tytuł komunikatu", MB_YESNO | MB_ICONHAND | MB_TOPMOST | MB_RTLREADING | MB_RIGHT);
}
//--------------------------------

...powrót do menu. 

Blokada klawiatury i myszki.

W celu zablokowania klawiatury i myszki można posłużyć się funkcją BlockInput, która pobiera argument typu BOOL. Przekazując wartość TRUE blokujemy jednocześnie klawiaturę i myszkę, przekazując wartość FALSE wyłączamy blokadę. W celu użycia tej funkcji należy włączyć do projektu bibliotekę winable.h w sekcji include pliku nagłówkowego. Jeżeli okaże się, że funkcja nie działa prawidłowo lub wcale, to trzeba jeszcze włączyć do projektu poprzez menu Project | Add to project bibliotekę user32.lib, która powinna znajdować się w katalogu lib\psdk, np: C:\Program Files\Borland\CBuilder6\Lib\Psdk
 
#include "winable.h"
//--------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
 BlockInput(true); // włączenie blokady
 Sleep(10000);     // wstrzymanie wykonywania kodu na 10 sekund
 BlockInput(false); // wyłączenie blokady
}
//--------------------------------

W przedstawionym przykładzie umieściłem w zdarzeniu OnClick przycisku Button1 jednocześnie kod blokujący i odblokowujący po 10 sekundach, a to dlatego, że po włączeniu blokady komputer nie reaguje ani na klawiaturę, ani na myszkę.
Jeżeli zablokujemy myszkę i klawiaturę to nie będziemy mieli możliwości wywołania w programie funkcji odblokowującej, dlatego trzeba postępować z tym ostrożnie, gdyż w przypadku zablokowania jedyne co pozostaje to restart systemu, dlatego używasz tego kodu na własną odpowiedzialność.

...powrót do menu. 

Zmiana kursorów myszy dla całego systemu.

Zmiany kursorów myszki można dokonać w Aplecie Mysz Panelu sterowania, ale to nie sztuka. Możliwa jest zmiana  zmiana kursorów z poziomu własnego programu poprzez modyfikację rejestru i wysłanie do systemu polecenia zaktualizowania kursorów myszy za pomocą funkcji SystemParametersInfo.
Zadanie jest banalnie proste w realizacji, na przykładzie pokażę jak zmienić kursor wskazujący (strzałka - Arrow) na własny kursor animowany, zaznaczam od razu, że przy zmianie kursorów dla całego systemu, trzeba użyć kursora znajdującego się gdzieś na dysku twardym, a nie w zasobach programu. Zanim przystąpimy do programowania trzeba przygotować sobie jakiś kursor zwykły lub animowany, ja posłużę się kursorem rainbow.ani, który u mnie znajduje się na dysku C: w katalogu Windows\Cursors. Użycie klasy TRegistry wymaga włączenia do projektu w sekcji include biblioteki Registry.hpp.

 
#include <Registry.hpp>
//--------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
 String cursor = "C:\\WINDOWS\\Cursors\\rainbow.ani";
 TRegistry *Rejestr = new TRegistry();
 Rejestr->RootKey = HKEY_CURRENT_USER;
 Rejestr->OpenKey("Control Panel\\Cursors", true);
 Rejestr->WriteString("Arrow", cursor); // tutaj następuje zmiana wybranego kursora
 Rejestr->Free();
 
 SystemParametersInfo(SPI_SETCURSORS, 0, NULL, NULL); // aktualizacja kursorów
}
//--------------------------------

Jak widać na przykładzie kursor strzałki w rejestrze to wartość ciągu o nazwie Arrow, chcąc zmienić inny kursor wystarczy tylko zmienić wartość ciągu. Niżej przedstawiam listę możliwych wartości:
Wybór normalny                       -    Arrow               

Wybór pomocy                         -    Help             
Praca w tle                               -    AppStarting  

Zajęty                                       -    Wait             

Wybór precyzyjny                     -    Crosshair     

Wybór tekstowy                       -    IBeam (iBeam)

Pismo ręczne                            -    NWPen         

Niedostępny                             -    No                

Zmiana wymiaru pionowego    -    SizeNS          

Zmiana wymiaru poziomego    -    SizeWE         

Zmiana wym. po przekątnej 1  -   SizeNWSE     

Zmiana wym. po przekątnej 2  -   SizeNESW     

Przenieś                                   -    SizeAll          

Wybór alternatywny                 -    UppArrow     

Wybór łącza                              -    Hand           
 

...powrót do menu. 

Uruchamianie okna Mój komputer.

Z uruchomieniem okna Mój komputer jest ten problem, że nie istnieje do niego żadna ścieżka dostępu. W przypadku np. Panelu sterowania wystarczy wywołać program Control.exe, ale Mój komputer to nie jest program, lecz miejsce w rejestrze systemowym.
Uruchomienie okna Mo komputer opiera się na pobraniu ścieżki dostępu do katalogu specjalnego CSIDL_DRIVES i przepisania jej do listy PIDL (item identifier list), a potem to już wystarczy wywołać funkcję SHELLEXECUTEINFO:
 
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 LPITEMIDLIST ppidl;
 LPMALLOC pShellMalloc;
 if(SUCCEEDED(SHGetMalloc(&pShellMalloc)))
 {
  SHGetSpecialFolderLocation(Handle, CSIDL_DRIVES, &ppidl);

  SHELLEXECUTEINFO ShExecInfo;
  ShExecInfo.cbSize = sizeof(ShExecInfo);
  ShExecInfo.fMask = SEE_MASK_IDLIST;
  ShExecInfo.hwnd = Handle;
  ShExecInfo.lpVerb = NULL;
  ShExecInfo.lpFile = NULL;
  ShExecInfo.lpParameters = NULL;
  ShExecInfo.lpDirectory = NULL;
  ShExecInfo.nShow = SW_SHOWNORMAL;
  ShExecInfo.hInstApp = NULL;
  ShExecInfo.lpIDList = ppidl;
  ShellExecuteEx(&ShExecInfo);
 }
 pShellMalloc->Free(ppidl);
 pShellMalloc->Release();
}
//--------------------------------

...powrót do menu. 

Sprawdzanie i opróżnianie kosza.

W celu sprawdzenia aktualnego rozmiaru kosza, czyli sprawdzenia jakie rozmiary mają w sumie wszystkie pliki w koszu oraz sprawdzenia ile jest tych plików można posłużyć się funkcją SHQueryRecycleBin, która poprzez strukturę SHQUERYRBINFO zwraca właśnie aktualny rozmiar kosza (i64Size) i liczbę plików w koszu. Funkcja SHQueryRecycleBin pobiera dwa argumenty pierwszy to ścieżka dostępu do kosza, który chcemy sprawdzić np. c:\recycler, co ciekawe, jeżeli zamiast pełnej ścieżki dostępu podamy tylko literę dysku (np. c:) na którym ten kosz się znajduje, to funkcja sama ten kosz odnajdzie. Jeżeli mamy kilka koszy i chcemy sprawdzić jaki jest rozmiar wszystkich koszy i ile jest plików we wszystkich koszach to zamiast podawać ścieżkę dostępu do wybranego kosza, podajemy wartość NULL. Drugi argument to adres do struktury SHQUERYRBINFO, która zwraca nam te dane:
 
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 SHQUERYRBINFO shqi;
 shqi.cbSize = sizeof(SHQUERYRBINFO);
 SHQueryRecycleBin("c:\\recycler", &shqi); // dla wszystkich koszy: SHQueryRecycleBin(NULL, &shqi);

 Label1->Caption = "Rozmiar: " + (String)(shqi.i64Size/1024) + " KB; Liczba plików: " + (String)shqi.i64NumItems;
}
//--------------------------------

W celu opróżnienia kosza można posłużyć się funkcją SHEmptyRecycleBin, która pobiera trzy argumenty. Pierwszy to uchwyt do okna, do którego chcemy przypisać okno dialogowe opróżniania kosza, czyli można tutaj przekazać uchwyt do okna naszego programu (this->Handle), ale równie dobrze można przekazać wartość NULL, czyli bez uchwytu. Drugi argument to ścieżka dostępu do wybranego kosza, zasada jest tutaj dokładnie taka sama jak w przypadku powyższej funkcji SHQueryRecycleBin, podanie wartości NULL sprawi, że opróżnione zostaną wszystkie kosze. Trzeci argument to flaga określająca jak wizualowany będzie proces opróżniania i dostępne są trzy parametry:

SHERB_NOCONFIRMATION    -    Okno dialogowe opróżniania kosza, nie będzie wyświetlane
SHERB_NOPROGRESSUI        -    Pasek postępu opróżniania kosza (ProgressBar) w oknie dialogowym opróżniania kosza nie będzie wyświetlany
SHERB_NOSOUND                 -    Nie będzie odtwarzany dźwięk opróżnienia kosza (ma znaczenie tylko jeżeli w aplecie Dźwięki został przypisany dźwięk dla kosza).

 
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 SHEmptyRecycleBin(NULL, "c:\\recycler", SHERB_NOCONFIRMATION); // dla wszystkich koszy: SHEmptyRecycleBin(NULL, NULL, SHERB_NOCONFIRMATION);
}
//--------------------------------

Jeżeli chcemy wyświetlić listę plików w koszu, to postępujemy z katalogiem RECYCLER dokładnie tak samo jak z każdym innym, czyli trzeba się tutaj posłużyć funkcją wyszukiwania folderów, podfolderów i plików, gdyż nie ma na to odrębnej funkcji.

...powrót do menu.