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).
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:
Jeśli natomiast chodzi o przyciski, to dostępne są następujące typy:
Jest jedno "ale", a mianowicie napisy na przyciskach będą w angielskiej
wersji językowej.
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).
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).
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:
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.
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.
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(); } } } //-------------------------------- |
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.
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)
Jako pierwszy parametr - void *nWnd - podajemy uchwyt do okna z którego wywołujemy komunikat najlepiej jest podać parametr: Handle.
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);
Kolejny parametr - const char * lpCaption - to treść etykiety Caption okna komunikatu, zasada działania taka sama jak w przypadku parametru drugiego.
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:
MB_SYSTEMMODAL - Działanie aplikacji zostaje zawieszone, dopóki użytkownik nie odpowie na żądanie okna komunikatu.
MB_APPLMODAL - Użytkownik musi odpowiedzieć na zapytanie okna komunikatu, zanim będzie mógł kontynuować pracę w bieżącym oknie. Może się jednak przenieść do innych okien aplikacji i w nich pracować.
MB_DEFBUTTON1 - Pierwszy przycisk w oknie komunikatu jest przyciskiem domyślnym. Zwróć uwagę na to, że zawsze jest on domyślny jeśli nie zaznaczono inaczej.
MB_DEFBUTTON2 - Drugi przycisk jest przyciskiem domyślnym.
MB_DEFBUTTON3 - Trzeci przycisk jest przyciskiem domyślnym.
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) |
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.
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.
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. } } //-------------------------------- |
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
// 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); } //-------------------------------- |
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:
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.
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 |
opracował: WRonX.
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 |
Etap drugi - rejestrowanie rozszerzenia pliku w następnej poradzie rejestrowanie rozszerzenia pliku.
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:
można powiązać z tym rozszerzeniem jakąś ikonę, która będzie się pojawiała np. w Eksploratorze Windows
można otwierać plik w naszym programie po jego dwukrotnym kliknięciu
można też zdefiniować własne akcje wykonywane na pliku (nie tylko jego otwieranie)
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 //otwarcie pliku po jego
dwukrotnym kliknięciu w Eksploratorze //wymyślona przez nas dodatkowa akcja jaką można
wykonać na pliku //kojarzenie ikony z rozszerzeniem (taka sama jak
ikona programu) 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'.
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 |
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. } //-------------------------------- |
// 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(); } } //--------------------------------- |
// 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(); } //-------------------------------- |
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(); } //-------------------------------- |
// 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.
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); } //-------------------------------- |
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); } //-------------------------------- |
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).
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.
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.
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(); } } //-------------------------------- |
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 { |
typedef
struct tagNEWTEXTMETRIC { |
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.
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.
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.
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(); } //-------------------------------- |
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)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.
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; } } //-------------------------------- |
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; } //-------------------------------- |
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")); } //-------------------------------- |
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); } //-------------------------------- |
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 |
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.