Odczyt własnego adresu IP.
Zagadnienie związane z siecią nie są moją specjalnością, mimo to postanowiłem jednak zamieścić poradę pokazującą w jaki sposób odczytać własny adres IP. Żeby jednak skorzystać z opisanej niżej funkcji, trzeba włączyć do projektu, np w pliku źródłowym, w sekcji include plik winsock.h:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit1.h" #include <winsock.h> //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- |
Następnie trzeba zadeklarować funkcję odczytującą adres IP - GetLocalIP (nazwa dowolna), w pliku nagłówkowym:
| // Plik nagłówkowy, np. Unit1.h //--------------------------------------------------------------------------- private: AnsiString GetLocalIP(void); //--------------------------------------------------------------------------- |
Teraz pozostało już tylko umieszczenie w pliku źródłowym definicji stworzonej funkcji i wywołanie jej np w zdarzeniu OnClick dla przycisku Button1:
| // Plik źródłowy np. Unit1.cpp //--------------------------------------------------------------------------- AnsiString TForm1::GetLocalIP(void) { AnsiString retVal; WSAData wsaData; if(!WSAStartup(MAKEWORD(1, 1),&wsaData)) { char BufHost[80]; if(gethostname(BufHost, sizeof(BufHost)) != SOCKET_ERROR) { hostent *phe = gethostbyname(BufHost); if(phe) { in_addr addr; for(int i = 0; phe->h_addr_list[i] != 0; i++) { CopyMemory(&addr, phe->h_addr_list[i], sizeof(in_addr)); if(i > 0) AppendStr(retVal, "."); AppendStr(retVal, inet_ntoa(addr)); } } } } WSACleanup(); return retVal; } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { Label1->Caption = GetLocalIP(); } //--------------------------------------------------------------------------- |
Przedstawiona funkcja najpierw inicjuje Windows Sockets DLL i zwraca wartość 0 jeżeli inicjacja zakończyła się sukcesem, następnie wpisuje do zmiennej BufHost nazwę hosta, po czym funkcja gethostbyname pobiera informacje o hoście. Potem adres zostaje skopiowany do zmiennej addr poczym adres zostaje przekonwertowany do typu AnsiString i na koniec zostaje zwrócony.
Żądanie hasła podczas logowania do programu.
Zdarza się, że pisząc jakiś program potrzebujemy utworzyć okno dialogowe, które będzie żądało podania hasła przed uruchomieniem programu. Można to zrobić na wiele sposobów, ale ja pokaże sposób dosyć nietypowy, lecz wyjątkowo skuteczny. Więc na początek tworzymy w naszym programie nowe okno dialogowe, które będzie żądało podania hasła i zapisujemy je pod nazwą PWDialogUnit.cpp. Nazwy które będę podawał są dowolne jednak mniej doświadczonym programistom, proponuję ich nie zmieniać i stosować się na razie do moich zaleceń. Gdy już zapiszemy nowe okno dialogowe zmieniamy jego nazwę na PWDialog (nazwa dowolna) i umieszczamy na nim komponent Edit1 - zmieniamy nazwę na PasswordEdit, a właściwość PasswordChar ustawiamy na * (gwiazdka). Umieszczamy również obiekt Button1 i ustawiamy jego właściwość
ModalResult na mrOK. Tyle jeśli chodzi o okienko dialogowe do sprawdzania hasła, nie wprowadzamy w nim żadnego kodu, ale otwieramy za to plik projektu wybierając z menu Project | View Source. Powinien ukazać się plik z następującą zawartością:
| //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop USERES("Project1.res"); USEFORM("Unit1.cpp", Form1); USEFORM("PWDialogUnit.cpp", PWDialog); //--------------------------------------------------------------------------- WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { try { Application->Initialize(); Application->CreateForm(__classid(TForm1), &Form1); Application->CreateForm(__classid(TPWDialog), &PWDialog); Application->Run(); } catch (Exception &exception) { Application->ShowException(&exception); } return 0; } //--------------------------------------------------------------------------- |
To właśnie ten plik trzeba nieco zmodyfikować, tak jak to pokazuje poniższy przykład. Modyfikacje zostały zaznaczone na żółto, a hasło umożliwiające uruchomienie programu jest wyróżnione czerwonym kolorem:
| //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include <memory> {
|
Tyle wystarczy. To rozwiązanie ma pewną wadę, otóż hasło musi być zdefiniowane wewnątrz programu.
Pobieranie adresu z aktywnego okna przeglądarki.
W celu pobrania adresu z aktywnego okna przeglądarki, należy posłużyć się klasą TDdeClientConv, niestety nie wiem czy ta klasa jest dostępna w wersjach BCB 1 i 3, ale w wersji 4 i następnych jest na pewno. Adres przechwycimy w zdarzeniu OnClick dla przycisku Button1 i wpiszemy go do obiektu Edit1:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit1.h" #include <winsock.h> #include <DdeMan.hpp> //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { TDdeClientConv *Adres = new TDdeClientConv(this); Adres->SetLink("iexplore", "WWW_GetWindowInfo"); Edit1->Text = Adres->RequestData("0xFFFFFFFF, sURL, sTitle"); delete Adres; } //--------------------------------------------------------------------------- |
Na żółto wyróżniony został parametr określający z jakiej przeglądarki przechwytujemy adres i jeżeli jest to Netscape, to trzeba zmienić ten parametr na "netscape", dla Firefox będzie to firefox itp. Zamiast tworzyć własną klasę, można umieścić na formularzu komponent DdeClientConv, który znajduje się na zakładce System. Jeżeli jako parametr funkcji RequestData podamy tylko "sURL" to i tak funkcja zwróci adres i tytuł.
Zapisywanie struktur do plików.
Przed przystąpieniem do zapisywania struktur do pliku proponuję najpierw zapoznać się z artykułem: struktury oraz z poradą: zapisywanie danych do plików (klasa ofstream) ponieważ w tej poradzie zostaną wykorzystane wiadomości z tam zawarte. Przydatna może się okazać również wiedza zawarta w poradzie: typy znakowe - char.
W celu zapisania całej struktury do pliku posłużymy się klasą fstream, więc trzeba importować do pliku nagłówkowego (np Unit1.h) w sekcji include plik fstream.h:
#include "fstream.h"
Przed zapisaniem struktury do pliku trzeba ją wypełnić jakimi wartościami, w przykładzie posłużę się nieco zmodyfikowaną strukturą opisaną w artykule: struktury. Struktura zawiera dwie zmienne typu char zadeklarowane jako tablice jednowymiarowe mogące pomieścić do pięciu elementów, znajduje się tam również jedna zmienna typu int, będąca również tablicą jednowymiarową o takiej samej liczbie elementów jak pozostałe zmienne. Następnie struktura zostanie wypełniona przykładowymi wartościami.
Kolejnym krokiem będzie zadeklarowanie zmiennej ofstream (nazwa dowolna) typu strumień, która zostanie wykorzystana do zapisania danych do pliku. Jednak przed zapisaniem pliku trzeba go otworzyć, chodzi oczywiście o otwarcie strumienia. Strumień zostanie otwarty w trybie do
zapisu (ios:out), oraz w trybie binarnym (ios::binary), a następnie zostanie wprowadzona do niego struktura za pomocą metody write. Do funkcji write zostaną wprowadzone jako argumenty: adres struktury oraz jej wielkość. Funkcja write zapisze dane do wskazanego pliku. Po wszystkim strumień zostanie zamknięty:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit1.h" #include <stdio.h> //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- struct Baza { char Name[5][250]; char Mark[5][50]; float Cost[5]; }; //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { Baza Hard; sprintf(Hard.Name[0], "%s", "Bear Paw 1200 TA"); sprintf(Hard.Mark[0], "%s", "Skaner"); Hard.Cost[0] = 350; sprintf(Hard.Name[1], "%s", "ImageQuest Q770"); sprintf(Hard.Mark[1], "%s", "Monitor"); Hard.Cost[1] = 800; sprintf(Hard.Name[2], "%s", "Tracer TRK 21MTT"); sprintf(Hard.Mark[2], "%s", "Klawiatura"); Hard.Cost[2] = 187; sprintf(Hard.Name[3], "%s", "FlashDisk"); sprintf(Hard.Mark[3], "%s", "Napęd Dyskowy USB Flash"); Hard.Cost[3] = 120; sprintf(Hard.Name[4], "%s", "Epson Stylus C20SX"); sprintf(Hard.Mark[4], "%s", "Drukarka"); Hard.Cost[4] = 320; fstream outfile; outfile.open("Test.dat", ios::out | ios::binary); outfile.write((char *)&Hard, sizeof(Hard)); outfile.close(); } //--------------------------------------------------------------------------- |
Proszę zwrócić uwagę, że nowy zbiór - Hard jest tworzona poprzez wywołanie nazwy struktury - Baza, a odwołanie do zmiennych wchodzących w skład struktury następuje po kropce, a nie jak to bywa zazwyczaj w BCB poprzez strzałkę. Ponieważ zmienne wewnątrz struktury są zadeklarowane jako tablice jednowymiarowe więc przy odwoływaniu się do nich trzeba podawać również numer elementu tablicy:
Tak jest poprawnie: Hard.Name[x]
ale tak już by było źle: Hard.Name
W artykule: struktury zmienne znajdujące się wewnątrz struktury nie były tablicami, lecz sam zbiór Hard był tablicą jednowymiarową, jednak takie rozwiązanie nastręczało by pewnych problemów przy zapisywaniu tylu tablic do zbiorów, dlatego lepszym rozwiązaniem jest umieszczanie tablic wewnątrz struktury, a nie struktury wewnątrz wielu tablic.
W następnej poradzie zostanie przedstawiona metoda odczytywania struktur z plików i zostanie wykorzystana do tego celu struktura zaprezentowana w tej poradzie.
Odczytywanie struktur z plików.
Zanim zaczniecie czytać jak się odczytuje struktury z plików proponuję najpierw zapoznać się z poprzednią poradą: zapisywanie struktur do plików. Odczytywanie struktur jest w istocie tylko odwrotnością zapisywania. Trzeba otworzyć strumień w trybie do odczytu a następnie posłużyć się metodą read przekazując jej jako argumenty adres do struktury oraz rozmiar struktury. W przykładzie wykorzystam strukturę przedstawioną w poprzedniej poradzie, a dane odczytane z pliku, zostaną wczytane do obiektu StringGrid1, dlatego teraz proponuję umieścić tenże obiekt na formularzu a następnie ustawić jego właściwości w następujący sposób:
Przedstawiony niżej przykład będzie zawierał (umieszczone na szarym tle) elementy przedstawione już w poprzedniej poradzie, a to po to, żeby całość była lepiej czytelna:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit1.h" #include <stdio.h> //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- struct Baza { char Name[5][250]; char Mark[5][50]; float Cost[5]; }; //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { Baza Hard; sprintf(Hard.Name[0], "%s", "Bear Paw 1200 TA"); sprintf(Hard.Mark[0], "%s", "Skaner"); Hard.Cost[0] = 350; sprintf(Hard.Name[1], "%s", "ImageQuest Q770"); sprintf(Hard.Mark[1], "%s", "Monitor"); Hard.Cost[1] = 800; sprintf(Hard.Name[2], "%s", "Tracer TRK 21MTT"); sprintf(Hard.Mark[2], "%s", "Klawiatura"); Hard.Cost[2] = 187; sprintf(Hard.Name[3], "%s", "FlashDisk"); sprintf(Hard.Mark[3], "%s", "Napęd Dyskowy USB Flash"); Hard.Cost[3] = 120; sprintf(Hard.Name[4], "%s", "Epson Stylus C20SX"); sprintf(Hard.Mark[4], "%s", "Drukarka"); Hard.Cost[4] = 320; fstream outfile; outfile.open("Test.dat", ios::out | ios::binary); outfile.write((char *)&Hard, sizeof(Hard)); outfile.close(); } //--------------------------------------------------------------------------- |
| void __fastcall TForm1::Button2Click(TObject *Sender) { StringGrid1->Cells[0][0] = "Nazwa"; StringGrid1->Cells[1][0] = "Marka"; StringGrid1->Cells[2][0] = "Cena w PLN"; Baza Hardware; fstream infile; infile.open("Test.dat", ios::in | ios::binary); infile.read((char *)&Hardware, sizeof(Hardware)); for(int i = 0; i < 5; i++) { StringGrid1->Cells[0][i + 1] = Hardware.Name[i]; StringGrid1->Cells[1][i + 1] = Hardware.Mark[i]; StringGrid1->Cells[2][i + 1] = Hardware.Cost[i]; StringGrid1->RowCount++; } StringGrid1->RowCount--; infile.close(); } //--------------------------------------------------------------------------- |
Właściwie to chyba nie wymaga już dalszych wyjaśnień. Jak to widać funkcja read odczytała dane z pliku i wrzuciła je do obiektu Hardware bdącego zbiorem struktury Baza, a następnie poszczególne wartości zostały wyciągnięte ze zbioru i przepisane do komórek obiektu StringGrid1. Jeżeli ktoś zastanawia się dlaczego przy wprowadzaniu wartości do zmiennych Name i Mark posłużyłem się metodą sprintf, to odsyłam go do porady typy znakowe - char, tam zostało to dokładnie opisane. No i jeszcze jedno, w sekcji include pliku źródłowego został włączony do projektu plik stdio.h, a to właśnie po to by można było skorzystać z metody sprintf, która jest w nim zdefiniowana.
Wczytanie i używanie animowanego kursora.
Żeby umieścić w programie animowany kursor myszy należy go wczytać używając metody LoadCursorFromFile i najlepiej jest to zrobić wewnątrz konstruktora klasy, następnie należy go dołączyć do pozostałych kursorów wchodzących w skład aplikacji, po czym można go już przypisać do całego formularza lub do wybranego obiektu na formularzu. W przykładzie przypiszę kursor do obiektu Memo1:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { HCURSOR Kursor_1 = LoadCursorFromFile("Kursor.ani"); Screen->Cursors[1] = Kursor_1; Memo1->Cursor = (Controls::TCursor)1; } //--------------------------------------------------------------------------- |
Oczywiście w ten sposób można wczytać więcej niż jeden kursor:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { HCURSOR Kursor_1 = LoadCursorFromFile("Dinosaur.ani"); HCURSOR Kursor_2 = LoadCursorFromFile("Map.ani"); Screen->Cursors[1] = Kursor_1; Screen->Cursors[2] = Kursor_2; Memo1->Cursor = (Controls::TCursor)1; Form1->Cursor = (Controls::TCursor)2; } //--------------------------------------------------------------------------- |
Jednak przy wczytywaniu więcej niż jednego animowanego kursora, korzystniej jest stworzyć funkcję która zlikwiduje konieczność pisanie powtarzających się elementów kodu, funkcję nazwałem SetMyCursor:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- Controls::TCursor SetMyCursor(String FileName, int index) { HCURSOR Kursor = LoadCursorFromFile(FileName.c_str()); Screen->Cursors[index] = Kursor; return (Controls::TCursor)index; } //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { Memo1->Cursor = SetMyCursor("Dinosaur.ani", 1); Form1->Cursor = SetMyCursor("Map.ani", 2); } //--------------------------------------------------------------------------- |
No i na koniec krótka informacja, otóż jak zapewne niektórzy zauważyli nie tworzyłem w pliku nagłówkowym deklaracji funkcji SetMyCursor, albowiem nie ma potrzeby przypisywania tej funkcji do klasy TForm1 i pomimo tego funkcja jest dostępna globalnie wewnątrz klasy TForm1. Taki sam efekt uzyskalibyśmy umieszczając w pliku nagłówkowym, w sekcji private odpowiednią deklarację. Ominięcie deklaracji jest możliwe dlatego, że funkcja SetMyCursor nie odwołuje się do żadnego obiektu należącego do klasy TForm1.
Odczytywanie czasu i daty systemowej.
Do odczytania daty i czasu można posłużyć się klasą TDateTime, wykorzystując jej metodę CurrentDateTime(). W celu odczytania daty lub czasu należy zdefiniować nowy obiekt (o dowolnej nazwie) typu TDateTime:
| TDateTime Todey = TDateTime::CurrentDateTime(); |
i to w zasadzie już wystarczy, w podanym przykładzie obiekt Todey pobiera i przechowuje aktualną datę i czas systemowy. Pomimo iż obiekt jest typu TDateTime, to może być traktowany jako obiekt typu AnsiString, a to z kolei oznacza, że nie trzeba dokonywać konwersji w celu wyświetlenia daty. W poniższym przykładzie pokażę jak odczytać datę i czas i wyświetlić je w oknie komunikatu:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { TDateTime Todey = TDateTime::CurrentDateTime(); Showessage(Todey); } //--------------------------------------------------------------------------- |
Taka metoda ma swoje wady wyświetlane są jednocześnie data i czas i to w formie narzuconej, czyli komunikat będzie miał następującą postać:
2003-10-11 10:34:54
Jednak nie zawsze mogą być potrzebne jednocześnie i data i czas, w takich przypadkach można posłużyć się dwiema metodami:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { TDateTime Todey = TDateTime::CurrentDate(); Showessage(Todey); } //--------------------------------------------------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { TDateTime Todey = TDateTime::CurrentTime(); Showessage(Todey); } //--------------------------------------------------------------------------- |
Można posługiwać się jednak znacznie prostszymi funkcjami i osiągnąć ten sam efekt:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { Showessage(DateTimeToStr(Now())); } //--------------------------------------------------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { Showessage(DateToStr(Now())); } //--------------------------------------------------------------------------- void __fastcall TForm1::Button3Click(TObject *Sender) { Showessage(TimeToStr(Now())); } //--------------------------------------------------------------------------- |
Przedstawione metody mają jedną zasadniczą wadę, otóż wyświetlają datę w określonym formacie, czyli rok-miesiąc-dzień. Jednak można to zmienić posługując się metodą FormatString, umożliwiającą dokonanie formatowania. Poniżej przedstawię różne sposoby wyświetlania daty i czasu:
| TDateTime Todey = TDateTime::CurrentDateTime(); Edit1->Text = Todey.FormatString("dd.mm.yyyy"); // wynik: 11.10.2003 Edit2->Text = Todey.FormatString("dddd dd.mm.yyyy"); // wynik: sobota 11.10.2003 Edit3->Text = Todey.FormatString("dd mmm yyyy"); // wynik: 11 paż 2003 Edit4->Text = Todey.FormatString("dd mmmm yyyy"); // wynik: 11 pażdziernik 2003 Edit5->Text = Todey.FormatString("ddd dd mmm yyyy"); // wynik: So 11 paż 2003 Edit6->Text = Todey.FormatString("dddd dd mmmm yyyyr."); // wynik: sobota 11 pażdziernik 2003r. Edit7->Text = Todey.FormatString("dd.mm.`yyr."); // wynik: 11.10.`03r. Edit8->Text = Todey.FormatString("dd.mm.yyyy, hh:nn:ss"); // wynik: 11.10.2003, 11:16:42 Edit9->Text = Todey.FormatString("hh/nn/ss"); // wynik: 11/16/42 |
To oczywiście nie wyczerpuje wszystkich możliwości, jednak pozwala już wyciągnąć pewne wnioski, a mianowicie do formatowania dnia w formie liczbowej używamy oznaczenia "dd", do formatowania dnia w formie skrótu nazwy używamy oznaczenia "ddd", do formatowania dnia w formie pełnej jego nazwy używamy oznaczenia "dddd", analogicznie do dnia formatuje się miesiąc: "mm", "mmm", "mmmm", oraz rok: "yy", "yyyy". W przypadku czasu jest podobnie, lecz do formatowania godzin używamy oznaczeń: "h", "hh", do formatowania minut: "n", "nn" i do formatowania sekund: "s", "ss". Jako separatorów można używać dowolnych znaków, cyfr i liter różnych od: d, m, y, h, n, s.
To nie wyczerpuje wszystkich możliwości, po więcej informacji odsyłam do pliku pomocy, pod hasło TDateTime.
Temat ten był już również poruszany w poradzie
formatowanie tekstu i liczb.
Odczytywanie daty ostatniej modyfikacji pliku.
W celu odczytania daty ostatniej modyfikacji pliku można posłużyć się funkcją FileDateToDateTime przekazując jej jako argument pełną ścieżkę dostępu do pliku:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { TDateTime Dt; Dt = FileDateToDateTime(FileAge("C:\\Katalog\\NazwaPliku.xxx")); Edit1->Text = Dt.FormatString("dd.mm.yyyy hh:nn:ss"); } //--------------------------------------------------------------------------- |
Istnieje również inny, nieco bardziej złożony sposób:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { TDateTime Dt; int Plik = FileOpen("C:\\Katalog\\NazwaPliku.xxx", fmOpenRead && fmShareDenyNone); Dt = FileDateToDateTime(FileGetDate(Plik)); Edit1->Text = Dt.FormatString("dd.mm.yyyy hh:nn:ss"); FileClose(Plik); } //--------------------------------------------------------------------------- |
Do odczytywania atrybutów pliku służy prosta funkcja FileGetAttr, której jako jedyny argument przekazuje się nazwę pliku. Funkcja zwraca wartość typu int, a to z kolei oznacza, że atrybuty są podawane jako wartości liczbowe a nie jako nazwa dlatego trzeba poddać wynik konwersji:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { int attrib = FileGetAttr("C:\\Katalog\\NazwaPliku.xxx"); String atrybut; if(attrib & faArchive) atrybut = atrybut + "archiwalny "; if(attrib & faReadOnly) atrybut = atrybut + "tylko do odczytu "; if(attrib & faSysFile) atrybut = atrybut + "systemowy "; if(attrib & faHidden) atrybut = atrybut + "ukryty"; Edit1->Text = atrybut; } //--------------------------------------------------------------------------- |
Atrybuty katalogów odczytuje się tak samo.
Odczytywanie rozmiaru pliku i katalogu.
W celu odczytania rozmiaru pliku należy posłużyć się klasą TSearchRec w połączeniu z funkcją FindFirst. Wymienione klasa i funkcja były już wykorzystywane w poradzie wyszukiwanie folderów podfolderów i plików. Do określenia rozmiaru pojedynczego pliku wystarczą trzy linie kodu:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { TSearchRec SFile; FindFirst("C:\\Katalog\\NazwaPliku.xxx", faAnyFile, SFile); Edit1->Text = IntToStr(SFile.Size) + " bajtów"; } //--------------------------------------------------------------------------- |
Jak widać funkcja FindFirst pobiera trzy argumenty, pierwszy to nazwa pliku, drugi to atrybut pliku i dostępne są następujące specyfikacje:
Jako trzeci argument pobierany jest obiekt typu TSearchRec na który są rzutowane informacje o pliku. Następnie z obiektu SFile (nazwa dowolna) typu TSearchRec pobierana jest informacja o rozmiarze pliku (Size). Parametr Size jest typu int dlatego należy dokonać jego konwersji na typ AnsiString. Wynik podawany jest w bajtach.
Wykorzystując przedstawioną funkcję FindRect można pobrać znacznie więcej informacji o pliku, np:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { TSearchRec SFile; FindFirst("C:\\Katalog\\NazwaPliku.xxx", faAnyFile, SFile); Edit1->Text = IntToStr(SFile.Size); // informacje o rozmiarze pliku. Edit2->Text = SFile.Attr; // informacje o atrybutach pliku, wynik podawany jest w formie liczbowej dlatego należy // dokonać jego konwersji. Patrz porada: odczytywanie atrybutów pliku. Edit3->Text = SFile.Name; // nazwa pliku. Edit4->Text = FileDateToDateTime(SFile.Time); // informacje o ostatnich dacie i czasie modyfikacji pliku. } //--------------------------------------------------------------------------- |
Jeżeli chodzi o odczytywanie rozmiaru katalogu wraz z podkatalogami to nie znalazłem żadnej gotowej funkcji, która by realizowała to zadanie, dlatego też postanowiłem sam stworzyć taką funkcję i jest to w zasadzie nieco zmodyfikowana funkcja przedstawiona w poradzie wyszukiwanie folderów podfolderów i plików. Funkcję nazwałem DirectorySize:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- int DirectorySize(AnsiString Dir, int size) { TSearchRec sr; if(FindFirst(Dir + "*.*", faAnyFile, sr) == 0) { do{ if(((sr.Attr & faDirectory) > 0) && (sr.Name != ".") && (sr.Name != "..")) size = DirectorySize(Dir + sr.Name + "\\", size); if((sr.Attr & faDirectory) == 0) size = size + sr.Size; } while(FindNext(sr) == 0); FindClose(sr); } return size; } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { Edit1->Text = FloatToStrF(DirectorySize("c:\\Temp2\\", 0)/1000.00, ffNumber, 12, 2) + " KB"; } //--------------------------------------------------------------------------- |
Nie będę dokładnie wyjaśniał działania tej funkcji, nadmienię tylko, że funkcja sprawdza jakie pliki znajdują się w podanym katalogu, sprawdza rozmiar każdego pliku, a następnie sumuje rozmiary wszystkich plików i przechowuje je w zmiennej size i właśnie ta zmienna jest zwracana jako wynik działania funkcji, Jak widać funkcja pobiera dwa argumenty, pierwszy to nazwa katalogu którego rozmiary chcemy poznać, drugi to liczba inicjująca zmienną size i zawsze należy podawać wartość 0, takie rozwiązanie eliminuje konieczność tworzenia zmiennej poza funkcją. W przykładzie wynik działania funkcji jest dzielony przez 1000.00 co pozwala przedstawić rozmiar katalogu w kilobajtach.
Na zakończenie dodam, że funkcja DirectorySize (nazwa dowolna) nie posiada deklaracji ponieważ nie musi być przypisywana do żadnej konkretnej klasy (w przykładzie: do klasy TForm1).
Odczytywanie informacji o dysku (nazwa, rozmiar, system plików, wolne miejsce).
Na samym wstępie muszę wyjaśnić, że w zamieszczanych tutaj poradach jako dysk traktowane są partycje, a nie dysk fizyczny, czyli jeżeli na jednym dysku znajduje się kilka partycji to każda traktowana jest jako dysk. Jednym słowem chodzi o dyski logiczne.
W celu odczytania nazwy dysku należy posłużyć się funkcją:
BOOL GetVolumeInformation(LPCTSTR lpRootPathName, LPTSTR lpVolumeNameBuffer, DWORD nVolumeNameSize, LPDWORD lpVolumeSerialNumber, LPDWORD lpMaximumComponentLength, LPDWORD lpFileSystemFlags, LPTSTR lpFileSystemNameBuffer, DWORD nFileSystemNameSize);
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { char Buf[255]; if(GetVolumeInformation("C:\\", Buf, sizeof(Buf), NULL, NULL, NULL, NULL, NULL)) { ShowMessage(("Nazwa dysku 'C' " + (AnsiString)Buf).c_str()); } } //--------------------------------------------------------------------------- |
Do sprawdzenia jaki system plików znajduje się na dysku, można wykorzystać tą samą funkcję:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { char Buf[255]; if(GetVolumeInformation("C:\\", NULL, NULL, NULL, NULL, NULL, Buf, sizeof(Buf))) { Label1->Caption = (AnsiString)Buf; } } //--------------------------------------------------------------------------- |
W celu odczytania całkowitej pojemności dysku należy posłużyć się funkcją DiskSize, która zwraca jako wynik właśnie rozmiar dysku:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { double size = DiskSize(0); Edit1->Text = "Rozmiar dysku: " + FloatToStrF((size / 1048576), ffNumber, 20, 2) + " MB"; } //--------------------------------------------------------------------------- |
Jak to widać w przykładzie, funkcja DiskSize pobiera jeden parametr, a jest to numer dysku. Jednakże dyski posiadają litery, które je określają, dlatego zasada ich kodowania jest następująca: 0 - aktualnie używany dysk, czyli ten z którego został uruchomiony program, 1 - dysk A (stacja dyskietek), 2 - dysk B, 3 - dysk C, 4 - dysk D, itd...
Zanim rozmiar dysku został wyświetlony w obiekcie Edit1, dokonano jego konwersji i zamiany rozmiaru z bajtów na megabajty, liczba 1048576 jest to wynik mnożenia liczb: 1024x1024. Tak więc gdybym chciał przedstawić rozmiar dysku w kilobajtach powinienem zmienną size (przechowującą rozmiar dysku w bajtach) podzielić przez 1024, gdybym natomiast chciał przedstawić rozmiar dysku w gigabajtach, to zmienną size należałoby podzielić przez 1073741824 (1024x1024x1024). Właściwie dla uzyskania dokładnego wyniku
należałoby używać zamiast 1024, raczej wielokrotności 1000, ponieważ liczba 1024 odnosi bardziej do pamięci RAM, niż do pamięci dyskowej.
Do odczytania wolnego miejsca na dysku służy funkcja DiskFree i działa ona dokładnie tak samo jak funkcja DiskSize:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { double free = DiskFree(3); Edit1->Text = "Wolne miejsce na dysku: " + FloatToStrF((free / 1000000000), ffNumber, 20, 2) + " GB"; } //--------------------------------------------------------------------------- |
No i jak łatwo się już domyślić, do odczytania zajętego miejsca na dysku nie ma żadnej funkcji, a to dlatego, że wystarczy posłużyć się tymi dwiema funkcja, czyli po prostu odjąć od pojemności dysku - wolne miejsce:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { double occupied = DiskSize(3) - DiskFree(3); Edit1->Text = "Zajęte miejsce na dysku: " + FloatToStrF((occupied / 1000000000), ffNumber, 20, 2) + " GB"; } //--------------------------------------------------------------------------- |
No i na zakończenie o tym jak sprawdzić z którego dysku uruchomiono program:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { String dysk = ExtractFileDrive(Application->ExeName); } //--------------------------------------------------------------------------- |
Usuwanie tła z tekstu ikon, zmiana koloru tła i koloru tekstu ikon.
Jeżeli tak się dla ciebie szczęśliwie nie składa i jesteś niestety użytkownikiem systemu Windows to na pewno zauważyłeś już, że ikony znajdujące się na pulpicie zawierają podpisy, jednak podpisy znajdują się na tle w kolorze tła pulpitu, co wygląda nieszczególnie ładnie jeśli posiada się akurat jakąś tapetę. Użytkownicy, którzy używają systemu Windows XP mogą cieszyć się ikonami z podpisami bez tła, co na tapecie wygląda już dużo lepiej.

ikona z tłem tekstu ikona bez tła tekstu
W tej poradzie pokaże jak można usunąć tło z tekstu ikon. Przedstawiona niżej rozwiązanie ma pewne wady. Otóż usunięcie tła nie jest trwałe, tzn. po odświeżeniu pulpitu, lub po zmianie tapety tło powróci tak więc trzeba napisać program, który będzie rezydował w pamięci i usuwał tło. Jeżeli będzie włączona funkcja Active Desktop wtedy usunąć tła się nie da, no i oczywiście funkcja nie działa w środowisku Windows XP.
Tworzymy nowy projekt i w pliku nagłówkowym umieszczamy deklarację funkcji ClearIconTextBackground (nazwa jest dowolna):
| // Plik nagłówkowy, np. Unit1.h //--------------------------------------------------------------------------- private: void __fastcall ClearIconTextBackground(void); //--------------------------------------------------------------------------- |
Następnie przechodzimy do pliku źródłowego i umieszczamy w nim definicję tej funkcji:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::ClearIconTextBackground(void) { HWND hwnd; hwnd = FindWindow("ProgMan", NULL); hwnd = GetWindow(hwnd, GW_CHILD); hwnd = GetWindow(hwnd, GW_CHILD); if(hwnd) { if(::SendMessage(hwnd, LVM_GETTEXTBKCOLOR, 0, 0) != CLR_NONE) { ::SendMessage(hwnd, LVM_SETTEXTBKCOLOR, (WPARAM)0, (LPARAM)CLR_NONE); InvalidateRect(hwnd, NULL, TRUE); } } } //--------------------------------------------------------------------------- |
Funkcja jest już gotowa, żeby jej użyć wystarczy ją wywołać w dowolnym zdarzeniu. Ja proponuję umieścić na formularzu obiekt Timer1 i ustawić jego właściwość Interval na 1000 (żeby tykał co sekundę). Następnie w zdarzeniu OnTimer tegoż obiektu po prostu wywołujemy funkcję ClearIconTextBackground. Obiekt Timer1 co sekunda będzie sprawdzał czy pod tekstem ikon znajduje się tło i w miarę potrzeb będzie je usuwał, to pozwoli wyeliminować niedogodność związaną z odświeżaniem pulpitu:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::Timer1Timer(TObject *Sender) { ClearIconTextBackground(); } //--------------------------------------------------------------------------- |
Co jednak jeżeli zechcemy przywrócić tło ikonom. Otóż w tym celu umieszczamy w pliku nagłówkowym w sekcji private zmienną typu long oraz umieszczamy deklarację funkcji UndoIconClear (nazwa jest dowolna):
| // Plik nagłówkowy, np. Unit1.h //--------------------------------------------------------------------------- private: void __fastcall ClearIconTextBackground(void); void __fastcall UndoIconClear(void); long IconBkColor; //--------------------------------------------------------------------------- |
Teraz należy umieścić definicję funkcji UndoIconClear w pliku źródłowym:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::UndoIconClear(void) { HWND hwnd; hwnd = FindWindow("ProgMan", NULL); hwnd = GetWindow(hwnd, GW_CHILD); hwnd = GetWindow(hwnd, GW_CHILD); if(hwnd) { if(::SendMessage(hwnd, LVM_GETTEXTBKCOLOR, 0, 0) == CLR_NONE) { ::SendMessage(hwnd, LVM_SETTEXTBKCOLOR, (WPARAM)0, (LPARAM)IconBkColor); InvalidateRect(hwnd, NULL, TRUE); } } } //--------------------------------------------------------------------------- |
Pozostało już tylko dodać kod, który przed usunięciem tła z tekstu ikon będzie sprawdzał w jakim jest kolorze i zapisywał je do zmiennej IconBkColor. Proponuję pobrać kolor w momencie uruchomienia programu, czyli w zdarzeniu OnShow dla formularza Form1:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::FormShow(TObject *Sender) { HWND hwnd; hwnd = FindWindow("ProgMan",NULL); hwnd = GetWindow(hwnd, GW_CHILD); hwnd = GetWindow(hwnd, GW_CHILD); if(hwnd) IconBkColor = ::SendMessage(hwnd, LVM_GETTEXTBKCOLOR, 0, 0); } //--------------------------------------------------------------------------- |
Funkcję UndoIconClear przywracającą pierwotne tło można wywołać w dowolnym zdarzeniu, np. OnClick dla przycisku Button1:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { UndoIconClear(); } //--------------------------------------------------------------------------- |
Co niektórzy zapewne już zauważyli, że funkcję UndoIconClear można tak przerobić by zmieniała tło pulpitu, jednak taka zmiana nie będzie trwała, czyli przy odświeżeniu pulpitu powróci stary kolor. Proponuję stworzyć nową funkcję ChangePulpitColor do zmiany tła pulpitu, w tym celu umieszczamy deklarację funkcji w pliku nagłówkowym:
| // Plik nagłówkowy, np. Unit1.h //--------------------------------------------------------------------------- private: void __fastcall ClearIconTextBackground(void); void __fastcall UndoIconClear(void); void __fastcall ChangePulpitColor(TColor kolor); long IconBkColor; //--------------------------------------------------------------------------- |
Następnie umieszczamy definicję funkcji w pliku źródłowym będzie się ona różniła tylko nieznacznie od funkcji UndoIconClear:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::ChangePulpitColor(TColor kolor) { HWND hwnd; hwnd = FindWindow("ProgMan", NULL); hwnd = GetWindow(hwnd, GW_CHILD); hwnd = GetWindow(hwnd, GW_CHILD); if(hwnd) { if(::SendMessage(hwnd,LVM_GETTEXTBKCOLOR, 0, 0) == CLR_NONE) { ::SendMessage(hwnd, LVM_SETTEXTBKCOLOR, (WPARAM)0, (LPARAM)kolor); InvalidateRect(hwnd, NULL, TRUE); } } } //--------------------------------------------------------------------------- |
Funkcję można wywołać w dowolnym zdarzeniu np. OnClick dla przycisku Button2, trzeba jej jednak przekazać jako argument zadany kolor:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { ChangePulpitColor(clRed); // można tak - kolor czerwony. ChangePulpitColor(RGB(255, 255, 255)); // można i tak - kolor biały. Dla czarnego np: RGB(0, 0, 0). ChangePulpitColor((TColor)65280); // ...i tak - kolor zielony. ChangePulpitColor(0x0077BBFF); // tak też można - kolor pomarańczowy. |
Na zakończenie przykład funkcji zmieniającej kolor tekstu ikon:
| // Plik źródłowy, np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::ChangeColorIconText(TColor TxtKolor) { HWND hwnd; hwnd = FindWindow("ProgMan",NULL); hwnd = GetWindow(hwnd, GW_CHILD); hwnd = GetWindow(hwnd, GW_CHILD); if(hwnd) { ::SendMessage(hwnd, LVM_SETTEXTCOLOR, (WPARAM)0, (LPARAM)TxtKolor); InvalidateRect(hwnd, NULL, TRUE); } } //--------------------------------------------------------------------------- |