Różne 1.
W tym dziale znajdują się porady, które trudno jest zakwalifikować do odrębnych grup, czyli wszystkiego po trochu.
Wyszukiwanie folderów, podfolderów i plików.
W BCB istnieje funkcja FindFirst umożliwiająca wyszukiwanie plików i zapisywanie wyniku np. do obiektu TListBox. Na początek stworzymy funkcję, która będzie wyszukiwała foldery we wskazanej lokalizacji. W niektórych kompilatorach BCB należy włączyć do projektu w sekcji include plik <filectrl.hpp> najlepiej jest zrobić to w pliku nagłówkowym (np. Unit1.h):
// Plik nagłówkowy np. Unit1.h. //-------------------------------- #include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> #include <filectrl.hpp> // <<--- //-------------------------------- |
Następnie również w pliku nagłówkowym w sekcji private umieszczamy deklarację funkcji o nazwie FindDir (nazwa funkcji jest dowolna):
// Plik nagłówkowy np. Unit1.h. //-------------------------------- private: void __fastcall FindDir(TListBox *lista, String Dir); //-------------------------------- |
Teraz przechodzimy do pliku źródłowego i dodajemy definicję zadeklarowanej funkcji FindDir:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::FindDir(TListBox *lista, String Dir) { TSearchRec sr; if(FindFirst(Dir + "*.*", faAnyFile, sr) == 0) { do{ if(((sr.Attr & faDirectory) > 0) & (sr.Name != ".") & (sr.Name != "..")) lista->Items->Add(sr.Name); } while(FindNext(sr) == 0); FindClose(sr); } } //-------------------------------- |
Funkcja wyszukiwania folderów jest już gotowa do działanie, teraz należy ją tylko wywołać np. w zdarzeniu OnClick przycisku TButton, przekazując do ciała funkcji dwa parametry, pierwszy - wskaźnik do obiektu typu TListBox, w którym chcemy zapisywać wynik wyszukiwania i drugi - lokalizację w której chcemy zacząć wyszukiwanie, przy czym należy pamiętać, żeby podawać bezwzględną ścieżkę dostępu czyli np.: "C:\\Windows\\System\\", lub "C:\\" - wyszukiwanie folderów na dysku C:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ListBox1->Clear(); FindDir(ListBox1, "c:\\Program Files\\"); } //-------------------------------- |
Funkcja, którą właśnie stworzyliśmy potrafi wyszukiwać tylko foldery we wskazanej lokalizacji, nie wyszukuje natomiast podfolderów. Można to łatwo zmienić modyfikując nieznacznie powyższą funkcję. Zasadnicza zmiana będzie poległa na zastosowaniu rekurencji, czyli funkcja będzie wywoływała samą siebie. Zmiany wprowadzone do funkcji FindDir zostały zaznaczone na żółto:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::FindDir(TListBox *lista, String Dir) { TSearchRec sr; if(FindFirst(Dir + "*.*", faAnyFile, sr) == 0) { do{ if(((sr.Attr & faDirectory) > 0) & (sr.Name != ".") & (sr.Name != "..")) { FindDir(lista, Dir + sr.Name + "\\"); lista->Items->Add(Dir + sr.Name); } } while(FindNext(sr) == 0); FindClose(sr); } } //-------------------------------- |
To są wszystkie zmiany, funkcję wywołuje się tak samo jak poprzednio.
Zajmiemy się teraz takim zmodyfikowaniem funkcji, żeby wyszukiwała w folderach i podfolderach pliki a pomijała foldery. W tym celu w pliku nagłówkowym w deklaracji funkcji dołączymy parametr typu String, który będzie pobierał rozszerzenie plików które chcemy wyszukiwać:
// Plik nagłówkowy np. Unit1.h. //-------------------------------- private: void __fastcall FindDir(TListBox *lista, String Dir, String typ); //-------------------------------- |
Należy również zmodyfikować definicję funkcji:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::FindDir(TListBox *lista, String Dir, String typ) { TSearchRec sr; if(FindFirst(Dir + "*.*", faAnyFile, sr) == 0) { do{ if(((sr.Attr & faDirectory) > 0) && (sr.Name != ".") && (sr.Name != "..")) { FindDir(lista, Dir + sr.Name + "\\", typ); } if((sr.Attr & faDirectory) == 0){ if(typ.IsEmpty()) lista->Items->Add(Dir + sr.Name); else if(ExtractFileExt(sr.Name).SubString(2, 5) == typ) lista->Items->Add(Dir + sr.Name); } } while(FindNext(sr) == 0); FindClose(sr); } } //-------------------------------- |
Nieznacznie zmieni się również sposób wywoływania funkcji:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ListBox1->Clear(); FindDir(ListBox1, "c:\\windows\\", "txt"); } //-------------------------------- |
Żeby wyszukać wszystkie pliki wystarczy podać typ pusty (""):
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ListBox1->Clear(); FindDir(ListBox1, "c:\\windows\\", ""); } //-------------------------------- |
// Plik nagłówkowy np. Unit1.h. //-------------------------------- #ifndef Unit1H #define Unit1H //-------------------------------- #include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> #include <fstream.h> // importowany plik fstream.h //-------------------------------- |
Zanim cokolwiek zrobimy z plikiem należy go zadeklarować jako zmienną typu strumień, a potem należy go otworzyć. Nawet jeżeli plik nie istnieje jeszcze na dysku i tak trzeba go otworzyć do zapisu. Może się to wydawać nielogiczne, ale już tak z tym jest. Przy zapisywaniu danych do pliku będziemy posługiwać się strumieniem wyjścia - ofstream.
Istnieje kilka sposobów otwierania pliku jako strumienia. Można przekazać nazwę pliku konstruktorowi zmiennej typu strumień.
Deklarujemy zmienną i otwieramy plik:
ofstream outfile ("nazwa_pliku.rozszeżenie"); |
...lub deklarując wskaźnik do zmiennej i tworząc strumień otwierający plik:
ofstream *outfile; outfile = new ofstream("nazwa_pliku.rozszeżenie"); |
Można również zadeklarować zmienną i potem wywołać metodę open:
ofstream outfile; outfile.open("nazwa_pliku.rozszeżenie"); |
Skupimy się na ostatnim sposobie zapisywania danych, czyli wykorzystamy metodę open. Teraz gdy już wiemy jak otwierać plik do zapisu, trzeba jeszcze wprowadzić do niego jakieś dane. W celu wprowadzenia np. łańcucha znaków (tekstu) do strumienia posłużymy się operatorem
<< :// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ofstream outfile; outfile.open("Test.txt"); outfile << "Przykładowy tekst"; outfile.close(); } //-------------------------------- |
W podanym przykładzie, w katalogu z projektem zostanie utworzony plik o nazwie Test.txt. W pliku będzie znajdował się tekst: Przykładowy tekst. W przykładzie znajduje się kod, którego jeszcze nie omówiłem:
outfile.close(); |
Ten kod służy do zamknięcia strumienia.
Niżej umieszczam kilka sposobów zapisu danych:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ofstream outfile; outfile.open("nazwa_pliku.txt"); outfile << "Tekst 1" << " " << "Tekst 2"; outfile.close(); } //-------------------------------- |
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ofstream outfile; outfile.open("nazwa_pliku.txt"); outfile << "Tekst 1" << endl; outfile << "Tekst 2" << endl; outfile.close(); } //-------------------------------- |
Manipulator endl przenosi tekst do nowego wiersza.
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ofstream outfile; outfile.open("nazwa_pliku.txt", ios::app); outfile << "Tekst 1" << endl; outfile << "Tekst 2" << endl; outfile.close(); } //-------------------------------- |
Operator ios::app zmienia nieco charakter strumienia. We wcześniejszych przykładach wywołanie strumienia wyjścia powodowało zastąpienie istniejącego pliku - plikiem nowo tworzonym, operator ios::app sprawia, że wprowadzany tekst zostanie dopisany na końcu pliku o ile taki plik istnieje, czyli nie spowoduje usunięcia danych które się już w pliku znajdują.
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ofstream outfile; outfile.open("nazwa_pliku.txt", ios::out|ios::app); outfile << "Tekst 1" << endl; outfile << "Tekst 2" << endl; outfile.close(); } //-------------------------------- |
Operator ios::out oznacza otwarcie pliku dla operacji wyjścia. Jest to domyślne ustawienie dla ofstream i nie musi być stosowane.
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ofstream outfile; outfile.open("nazwa_pliku.txt", ios::out|ios::app|ios::binary); outfile << "Tekst 1" << endl; outfile << "Tekst 2" << endl; outfile.close(); } //-------------------------------- |
Operator ios::binary otwiera plik w trybie binarnym.
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ofstream outfile; outfile.open("nazwa_pliku.txt", ios::noreplace); outfile << "Tekst 1" << endl; outfile << "Tekst 2" << endl; outfile.close(); } //-------------------------------- |
Operator ios::noreplace sprawdza czy plik istnieje, jeżeli TAK to otwarcie dla operacji wyjścia kończy się niepowodzeniem, czyli istniejący plik nie ulegnie zmianie, chyba że zastosujemy jeszcze operator ios::app, wtedy dane zostaną dopisane do pliku.
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { char *Buf = new char[255]; ifstream infile; infile.open("Test.txt"); infile >> Buf; infile.close(); Memo1->Text = Buf; delete Buf; } //-------------------------------- |
W przykładzie najpierw utworzyłem zmienną Buf typu char, ale zadeklarowałem ją jako nowy typ i dlatego nie muszę określać ile znaków może przechowywać zmienna Buf. Wada tego sposobu wczytywania danych polega na tym, że zmienna Buf będzie pobierała znaki dopóki nie napotka pierwszej spacji, czyli jeśli w pliku będzie znajdował się tekst: przykładowy tekst to do zmiennej zostanie wczytana tylko wartość: przykładowy. Żeby wczytać całą zawartość pliku należałoby utworzyć kilka zmiennych a następnie wczytywać do nich kolejne wartości, a potem wszystko zsumować i dopiero potem przepisać zawartość np. do obiektu Memo1:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { char Buf[255]; char Temp[255]; ifstream infile; infile.open("Test.txt"); infile >> Temp; sprintf(Buf, "%s", Temp); infile >> Temp; strcat(Buf, Temp); infile.close(); Memo1->Text = Buf; } //-------------------------------- |
W tym przypadku zostaną wczytane dwa łańcuchy znaków z pliku, ale z pominięciem spacji, czyli jeżeli w pliku znajduje się tekst: przykładowy tekst to do zmienna Buf zostanie wypełniona wartością: przykładowytekst.
Tak więc ta metoda jest dobra tylko wtedy, gdy chcemy wczytać z pliku tylko wartości pojedyncze. Jeżeli chcemy wczytać całą linię tekstu z pliku, należy posłużyć się metodą getline:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { char Buf[255]; ifstream infile; infile.open("Test.txt"); infile.getline(Buf, 255); infile.close(); Memo1->Text = (AnsiString)Buf; } //-------------------------------- |
Jak widać w przykładzie metoda getline wymaga podania zmiennej do której zostanie wczytana zawartość pliku, oraz podania maksymalnej długości wczytywanego łańcucha znaków. Nie oznacza to, że podana długość musi być równa faktycznej długości łańcucha znajdującego się w pliku, ponieważ jeżeli podana wartość będzie większa od rzeczywistej długości łańcucha to i tak nic więcej nie zostanie wczytane, jeżeli natomiast podana długość będzie mniejsza od długości łańcucha to do zmiennej zostanie wczytana tylko taka wartość, jaka odpowiada podanej długości.
Jeżeli plik zawiera więcej niż jedną linię tekstu, to metodę getline należy wywoływać wewnątrz pętli:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { char Buf[255]; ifstream infile; infile.open("Test.txt"); for(int i = 0; i < 10; i++) { infile.getline(Buf, 255); Memo1->Lines->Add((AnsiString)Buf); } infile.close(); } //-------------------------------- |
W przykładzie pętla wykonuje dziesięć obiegów i w każdym obiegu pętli jest wczytywana kolejna linia z pliku. Kryje się tu pewne niebezpieczeństwo, mianowicie jeżeli w pliku jest mniej niż dziesięć linii tekstu, to w kolejnych obiegach pętli zmienna Buf zachowa ostatnią pobraną wartość i ta wartość powtórzy się wielokrotnie w obiekcie Memo1.
Można jednak temu zaradzić:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { char Buf[255]; ifstream infile; infile.open("Test.txt"); for(int i = 0; i < 10; i++) { infile.getline(Buf, 255); if(((AnsiString)Buf).IsEmpty()) i = 10; Memo1->Lines->Add((AnsiString)Buf); sprintf(Buf, "%s", ""); } infile.close(); } //-------------------------------- |
W podanym przykładzie, w każdym obiegu pętli zmiennej Buf zostaje przypisana pusta wartość: sprintf(Buf, "%s", ""), a następnie w kolejnym obiegu następuje próba wczytania zawartości pliku do zmiennej: infile.getline(Buf, 255), po czym występuje warunek, który sprawdza czy zmienna zawiera jakąś wartość: if(((AnsiString)Buf).IsEmpty()) i jeżeli zmienna jest pusta to następuje spełnienie warunku pętli i przypisanie zmiennej i wartości końcowej: i = 10. No i tyle wystarczy, żeby wczytać całą zawartość pliku.
We wszystkich przykładach występuje maksymalna liczba znaków określona w zmiennej Buf - równa 255 znakom, ale można podać znacznie większe wartości.
Niżej umieszczam kilka innych sposobów odczytu danych:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { char Buf[4][100]; ifstream infile; infile.open("Test.txt"); infile >> Buf[0] >> Buf[1]; infile >> Buf[2] >> Buf[3]; infile.close(); Memo1->Text = Buf; } //-------------------------------- |
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { char Buf[4][100]; ifstream infile; infile.open("Test.txt", ios::in); infile >> Buf[0] >> Buf[1]; infile >> Buf[2] >> Buf[3]; infile.close(); Memo1->Text = Buf; } //-------------------------------- |
Operator ios::in otwiera plik dla operacji wejścia. Jest to ustawienie domyślne dla ifstream i nie musi być stosowane.
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { char Buf[4][100]; ifstream infile; infile.open("Test.txt", ios::in); if(infile.fail()) { ShowMessage("Otwarcie pliku nie powiodło się."); } infile >> Buf[0] >> Buf[1]; infile >> Buf[2] >> Buf[3]; infile.close(); Memo1->Text = Buf; } //-------------------------------- |
Funkcja składowa fail jest wykorzystywana do badania, czy operacja na strumieniu zakończyła się sukcesem.
Można również zamiast funkcji fail zastosować przeciążenie operatora !:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { char Buf[4][100]; ifstream infile; infile.open("Test.txt", ios::in); if(!infile) { ShowMessage("Otwarcie pliku nie powiodło się."); } infile >> Buf[0] >> Buf[1]; infile >> Buf[2] >> Buf[3]; infile.close(); Memo1->Text = Buf; } //-------------------------------- |
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { DeleteFile("C:\\Windows\\Temp\\nazwa_pliku.tmp"); } //-------------------------------- |
Zwróć uwagę, że podając nazwę pliku który chcemy skasować należy podawać pełną ścieżkę dostępu i nie można przy użyciu tej funkcji skasować wszystkich plików znajdujących się w wybranym katalogu np.:
DeleteFile("C:\\Windows\\Temp\\*.*"); // t o n i e z a d z i a ł a. |
To nie zadziała, podawanie jako parametrów gwiazdek zamiast nazwy i rozszerzenia nie sprawdza się. Żeby skasować np. wszystkie pliki znajdujące się we wskazanym katalogu trzeba stworzyć własną funkcję, która będzie połączeniem funkcji wyszukiwania plików (patrz: wyszukiwanie folderów, podfolderów i plików) oraz funkcji kasowania plików. W tym celu trzeba najpierw zadeklarować w pliku nagłówkowym funkcję typu void, następnie zdefiniować ją w pliku źródłowym:
// Plik nagłówkowy np. Unit1.h. //-------------------------------- private: void __fastcall DeleteFiles(String DirName, String prefix); |
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::DeleteFiles(String DirName, String prefix) { TSearchRec sr; int result; AnsiString FileName; result = FindFirst(DirName + "*.*", faAnyFile, sr); while(result == 0) { if((sr.Name != ".") && (sr.Name != "..") && (!(sr.Attr & faDirectory) > 0)) { if(ExtractFileExt(sr.Name).SubString(2, 5) == prefix) { FileName = DirName + sr.Name; } if(prefix == "*") { FileName = DirName + sr.Name; } DeleteFile(FileName); // lub DeleteFile(FileName.c_str()). } result = FindNext(sr); } FindClose(sr); } //-------------------------------- |
Funkcja została tak zbudowana, żeby można było kasować np. pliki wybranego typu podając rozszerzenie lub kasować wszystkie pliki podając zamiast rozszerzenia gwiazdkę (*). Funkcję można wywołać w dowolnym zdarzeniu, przy czym należy przekazać jej jako parametry nazwę katalogu z którego kasujemy pliki oraz typ pliku (rozszerzenie) który ma być skasowany:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { DeleteFiles("c:\\temp\\", "txt"); } //-------------------------------- |
Kasowanie wszystkich plików (*.*) z katalogu c:\temp:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { DeleteFiles("c:\\temp\\", "*"); } //-------------------------------- |
Żeby skasować katalog należy posłużyć się funkcją RemoveDirectory przekazując jej jako parametr nazwę katalogu, który chcemy skasować. Istnieje jednak pewne ale, a mianowicie można kasować tylko pusty katalog, jeżeli wewnątrz będą się znajdowały jakieś pliki lub katalogi to kasowanie zakończy się fiaskiem. Tak więc chcąc skasować katalog, który nie jest pusty, najpierw trzeba skasować znajdujące się w nim pliki, a dopiero potem można skasować sam katalog:
Kasowanie pustego katalogu:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button5Click(TObject *Sender) { RemoveDirectory("c:\\temp"); } //-------------------------------- |
Kasowanie katalogu w którym zanjdują się pliki:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button5Click(TObject *Sender) { DeleteFiles("c:\\temp\\", "*"); RemoveDirectory("c:\\temp"); } //-------------------------------- |
...powrót do menu.
Zmiana kursora piszącego w obiektach typu TMemo, TRichedit, TEdit itp...
Gdy piszemy dowolny tekst w np. obiekcie Memo1 to znajduje
się tam już domyślny kursor piszący (
} i jest to najczęściej migocząca pionowa kreska. Można to jednak zmienić
i wstawić w to miejsce własny kursor piszący. Żeby to zrobić trzeba najpierw
przygotować sobie plik graficzny w formacie bmp np. taki jak ten:
. Rysunek
jest niewielki ponieważ sam kursor też jest nieduży, grafika posiada jednak
pewną charakterystyczną cechę (co bystrzejsi już to pewnie zauważyli), a
mianowicie tło rysunku powinno być czarne, a sam kursor powinien być biały. To
dlatego, że po podstawieniu kursora do wybranego obiektu nastąpi odwrócenie
kolorów. Tak więc sam kursor nie musi być koniecznie czarny, może to być dowolny
kolor ale tworząc kursor trzeba pamiętać, że po wstawieniu do programu kolory
się "odwrócą".
Mając już przykładowy plik z kursorem tworzymy nowy projekt (File
| New Application) i od razu przechodzimy do pliku nagłówkowego (np.
Unit1.h), i w sekcji private deklarujemy obiekt
bmp typu TBitmap oraz
tworzymy funkcję SetCaret
(nazwa funkcji jest dowolna):
// Plik nagłówkowy np. Unit1.h //-------------------------------- private: Graphics::TBitmap *bmp; void __fastcall SetCaret(TWinControl *Obiekt); //-------------------------------- |
Przechodzimy do pliku źródłowego i wewnątrz konstruktora klasy TForm1 definiujemy obiekt bmp, a następnie wczytujemy do niego utworzony wcześniej plik z kursorem:
// Plik źródłowy np. Unit1.cpp //-------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //-------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { bmp = new Graphics::TBitmap(); bmp->LoadFromFile("caret.bmp"); } //-------------------------------- |
...i drobna uwaga, jeżeli plik z grafiką znajduje się w innym katalogu niż nasz program to należy podać pełną ścieżkę dostępu do tego pliku np.:
bmp->LoadFromFile("c:\\kursory\\caret.bmp");
Można też podać ścieżkę dostępu względną w odniesieniu do naszego programu, ale tylko wtedy, gdy katalog z kursorem znajduje się w katalogu z naszym programem:
bmp->LoadFromFile(ExtractFileDir(Application->ExeName) + "\\kursory\\caret.bmp");
Teraz utworzymy definicję funkcji SetCaret:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::SetCaret(TWinControl *Obiekt) { CreateCaret(Obiekt->Handle, bmp->Handle, 0, 0); ShowCaret(Obiekt->Handle); } //-------------------------------- |
Pozostało już tylko umieścić na formularzu obiekt Memo1, a następnie w zdarzeniu OnPaint formularza Form1 wywołujemy naszą funkcję:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::FormPaint(TObject *Sender) { SetCaret(Memo1); } //-------------------------------- |
I to już powinno działać. Spróbujmy jednak umieścić na formularzu jeszcze obiekt RichEdit1, żeby przełączać kursor pomiedzy obiektami trzeba w zdarzeniach OnChange obydwu obiektów wywołać ponownie funkcję SetCaret z odpowiednim parametrem:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Memo1Change(TObject *Sender) { SetCaret(Memo1); } //-------------------------------- void __fastcall TForm1::RichEdit1Change(TObject *Sender) { SetCaret(RichEdit1); } //-------------------------------- |
...no i to już właściwie wszystko.
Uruchamianie domyślnego programu pocztowego z parametrami.
Żeby uruchomić domyślny program pocztowy po kliknięciu np. na przycisk Button1 trzeba włączyć do projektu aplikacji plik shellapi.h:
// Plik źródłowy np. Unit1.cpp //-------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit1.h" #include <shellapi.h> //--------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //-------------------------------- |
Następnie w zdarzeniu OnClick przycisku Button1 umieszczamy funkcję API ShellExecute przekazując jej jako parametry adres e-mail na który chcemy wysłać list:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ShellExecute(Handle, "open", "mailto:cyfrowy.baron@wp.pl", NULL, NULL, SW_SHOWNORMAL); } //-------------------------------- |
To spowoduje uruchomienie domyślnego programu pocztowego z wpisanym e-mail'em w okienku adresu. Proszę zwrócić uwagę że w kodzie adres e-mail jest poprzedzony słowem
mailto.// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ShellExecute(Handle, "open", "mailto:cyfrowy.baron@wp.pl?subject=...ale o co chodzi", NULL, NULL, SW_SHOWNORMAL); } //-------------------------------- |
Można również dołączyć treść wiadomości posługując się słowem kluczowym
&body:// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ShellExecute(Handle, "open", "mailto:cyfrowy.baron@wp.pl?subject=...ale o co chodzi&body=Pierwsza linia%0ADruga linia%0ATrzecia linia%0Aitd...", NULL, NULL, SW_SHOWNORMAL); } //-------------------------------- |
Każda kolejna linia treści wiadomości musi być rozdzielana czymś takim:
%0A .Losowanie przypadkowych liczb.
Czasami potrzebujemy wylosować jakąś liczbę, może to szczególnie przydatne przy tworzeniu gier. żeby wylosować taką liczbę trzeba posłużyć się funkcją Randomize:
Losowanie przypadkowej liczby z dowolnego zakresu:
// Plik źródłowy np. Unit1.cpp |
Jednak zdecydowanie najlepsza metoda jest taka, losowanie z podanego zakresu, ta
metoda zapewnia dużą niepowtażalność w odrużnieniu od pozostałych:
// Plik źródłowy np. Unit1.cpp |
Losowanie liczby z podanego zakresu:
// Plik źródłowy np. Unit1.cpp |
Można też tak:
// Plik źródłowy np. Unit1.cpp |
Zmiana wyrównania tekstu na obiekcie TButton.
Standardowo tekst na przyciskach (Button) jest zawsze wyśrodkowany i sam komponent nie zawiera żadnej metody, która mogłaby to zmienić. Istnieje jednak bardzo prosty sposób wyrównania tekstu do lewej lub prawej strony.
W tym celu przechodzimy do pliku źródłowego i w konstruktorze klasy np. TForm1 umieszczamy prosty kod:
// Plik źródłowy np. Unit1.cpp |
W przykładzie tekst na przycisku Button1 zostanie wyrównany do prawej strony. Parametr odpowiedzialny za wyrównanie zaznaczyłem na żółto.
Zestaw dostępnych parametrów:
Zmiana sposobu wyświetlania podpowiedzi w chmurkach (właściwość HINT).
Pisząc o "podpowiedziach w chmurkach" mam na myśli takie małe komunikaty wyskakujące gdy wskażemy kursorem jakiś element np. pulpitu lub aplikacji. Przesuń element na ten tekst
"przykładowy tekst" a pokaże się właśnie podpowiedź w chmurce. Sposób wyświetlania podpowiedzi jest chyba wszystkim dobrze znany, jednak przypomnę go dla porządku.
Żeby włączyć chmurki dla wybranego obiektu należy ustawić w inspektorze obiektów jego właściwość ShowHint na true, a następnie wpisać tekst który ma być wyświetlany do jego właściwości Hint.
Wyobraźmy sobie jednak sytuację gdy umieścimy w obiekcie np.
ListBox1 kilka pozycji tekstu, lecz cały tekst nie mieści się w okienku, np.:
Tak jak widać na rysunku tekst jest dłuższy niż okno obiektu i nie można przeczytać całej treści. Żeby wyświetlić całą zawartość linii jako podpowiedź dla obiektu TListBox można w zdarzeniu OnClick dla tego obiektu przypisać jego właściwości Hint wartość na którą klikamy. Należy pamiętać, że jeśli chcemy żeby chmurka była wyświetlana to właściwość ShowHint wybranego obiektu musi mieć wartość ustawioną na true:
// Plik źródłowy np. Unit1.cpp |
To zadziała, lecz żeby odczytać zawartość dowolnej linii w obiekcie ListBox1 trzeba tą linię zaznaczyć, a to nie zawsze wystarcza. Najczęściej potrzebujemy odczytać zawartość linii na którą wskazuje kursor i to bez jej wybierania. To zadanie można bardzo łatwo zrealizować w zdarzeniu OnMouseMove dla obiektu ListBox1:
// Plik źródłowy np. Unit1.cpp |
To również zadziała, lecz i tutaj pojawia się pewien problem, a mianowicie po
wskazaniu kursorem pozycji na liście zostanie wyświetlona chmurka, lecz
przesunięcie kursora na kolejną pozycję w ListBox1 usunie chmurkę i podpowiedź
nie zostanie dla niej wyświetlona. Żeby ponownie wyświetlić chmurkę trzeba
"zjechać" z obiektu ListBox1 i ponownie na niego najechać, co jest raczej mało
wygodne, lecz i ten problem da się rozwiązać.
Żeby to zrobić utworzymy nowy
obiekt hint (
// Plik nagłówkowy np. Unit1.h |
Teraz przechodzimy do pliku źródłowego (np. Unit1.cpp) i w konstruktorze obiektu np. TForm1 definiujemy zadeklarowany obiekt hint:
// Plik źródłowy np. Unit1.cpp |
Następnie w zdarzeniu OnMouseMove dla obiektu ListBox1 umieszczamy instrukcję, która będzie wyświetlała chmurkę:
// Plik źródłowy np. Unit1.cpp |
Zaznaczyłem w trzech kolorach elementy istotne dla wyświetlania chmurki i tak:
W niektórych wersjach BCB (chyba poniżej 4) nie zadziała metoda OffestRect, dlatego konieczne może się okazać zastosowanie bardziej prymitywnej metody:
// Plik źródłowy np. Unit1.cpp |
Jak widać zniknęła metoda OffsetRect, a zamiast niej zastosowałem dodatkowy obiekt TRect.
Jednak to jeszcze nie koniec, ponieważ trzeba przerwać wyświetlanie chmurki gdy kursor "zjedzie" z obiektu ListBox1. Żeby przerwać wyświetlanie etykiety wystarczy posłużyć się funkcją ReleaseHandle. Oczywiście żeby funkcja wywołała pożądany efekt musi być wywołana w odpowiednim zdarzeniu. W tym przypadku można ją wywołać w zdarzeniu OnMouseMove dla formularza np. Form1:
// Plik źródłowy np. Unit1.cpp |
Jednak to nie zawsze może się sprawdzić dlatego lepiej będzie się posłużyć metodą WindowProc, która będzie przechwytywała komunikat, wysyłany gdy kursor myszki opuszcza - w tym przypadku - obiekt ListBox1. W tym celu zadeklarujemy w pliku nagłówkowym (np. Unit1.h) funkcję EraseHint (
nazwa jest dowolna):// Plik nagłówkowy np. Unit1.h Controls::TWndMethod HintListBox; |
Następnie przechodzimy do pliku nagłówkowego i w konstruktorze klasy (np. TForm1) wywołujemy metodę WindowProc z obiektu ListBox1 i przypisujemy ją do funkcji EraseHint, a następnie definiujemy funkcję EraseHint:
// Plik źródłowy np. Unit1.cpp
|
Definicja funkcji EraseHint:
// Plik źródłowy np. Unit1.cpp |
No i na zakończenie mała ciekawostka, z tak utworzonym obiektem Hint można robić prawie to samo co z klasą TCanvas ponieważ obiekt THintWindow dziedziczy tą klasę. Żeby na przykład zmienić kolor chmurki wystarczy wywołać metodę Brush i co ciekawe w tym przypadku nie trzeba posługiwać się metodą Canvas. Można zmienić również krój i rozmiar czcionki, ale zmiana koloru czcionki się nie powiedzie:
// Plik źródłowy np. Unit1.cpp
|
Jak zablokować zamknięcie aplikacji?
Żeby uniemożliwić zamknięcie aplikacji trzeba wywołać funkcję CanClose w zdarzeniu OnCloseQuery dla głównego formularza aplikacji, np. Form1:
// Plik źródłowy np. Unit1.cpp |
Dobrze jest przy tym pamiętać, żeby w jakimś innym zdarzeniu umieścić jednak kod zamykający aplikację gdy już tego zapragniemy. Pokażę to na przykładzie zdarzenia OnClick dla przycisku Button1:
// Plik źródłowy np. Unit1.cpp |
Wysyłanie e-mail'a z plikiem załącznika za pomocą MAPI.
W poradzie
uruchamianie domyślnego programu pocztowego z parametrami
pokazałem sposób na wysyłanie e-mail'i poprzez uruchomienie programu pocztowego i przekazanie kilku parametrów. W tej poradzie pokażę jak można wykorzystując bibliotekę Mapi32.dll wysłać e-mail'a z dołączonym plikiem załącznika.
W tym celu umieszczamy na formularzu (np. Form1) dwa obiekty Edit1 i Edit2, przycisk Button1, obiekt Memo1 i obiekt OpenDialog1. Następnie importujemy w pliku źródłowym, w sekcji include plik mapi.h:
// Plik źródłowy np. Unit1.cpp |
Następnie przechodzimy do pliku nagłówkowego i w sekcji private umieszczamy deklarację funkcji SendMail (nazwa funkcji jest dowolna):
// Plik nagłówkowy np. Unit1.cpp |
Teraz wracamy do pliku źródłowego i umieszczamy w nim definicję funkcji SendMail. Wewnątrz funkcji umieszczę od razu cały kod obsługujący wysyłanie e-mail. Przekazywanie adresu e-mail, tematu, treści i lokalizacji pliku załącznika będzie się odbywało za pośrednictwem parametrów funkcji, czyli Adress: adres e-mail, Title: temat listu, Note: treść listu i FileName: plik załącznika:
// Plik źródłowy np. Unit1.cpp |
Teraz pozostało już tylko wywołać funkcję SendMail w zdarzeniu OnClick przycisku Button1 z wykorzystaniem obiektu OpenDialog1 do zlokalizowania pliku załącznika:
// Plik źródłowy np. Unit1.cpp |
...i to już wszystko, żeby wysłać e-mail'a trzeba w obiekcie Edit1 wpisać adres e-mail, w obiekcie Edit2 podać temat listu, w obiekcie Memo1 wprowadzić treść listu a za pomocą obiektu OpenDialog1 wskazać plik do wysłania.
Kontrolowanie czasu wyświetlania podpowiedzi (właściwość HINT).
Do kontrolowania czasu wyświetlania podpowiedzi w chmurkach - właściwość Hint obiektów, służą trzy funkcje:
Podpowiedzi można kontrolować w ten sposób, tylko dla wszystkich obiektów wchodzących w skład aplikacji, czyli nie można ustawić dla każdego obiektu osobnych parametrów wyświetlania. By możliwe było jednak wywołanie podpowiedzi, trzeba ustawić właściwość ShowHint wybranego obiektu na true. Ustawienia najlepiej jest zainicjować wewnątrz
konstruktora klasy TForm, jest
to pierwsza funkcja jaka zostaje umieszczona w pliku źródłowym. Czas określa się w milisekundach (1 sekunda = 1 000 milisekund), np:
// Plik źródłowy np. Unit1.cpp |
Jak w Windows XP nadać kontrolkom odpowiedni wygląd?.
Jeżeli jesteś "szczęśliwym"
☺posiadaczem systemu Windows XP to na pewno zauważyłeś, że programy które tworzysz za pomocą BCB wyglądają "po staremu", i kontrolki takie jak np Button nie są "pięknie zaokrąglone", czyli wciąż ten sam stary Windows 98 i niezależnie od tego jakiej wersji BCB użyjesz kontrolki się nie zmienią, ale...
Otóż można to zmienić i nie trzeba w tym celu posługiwać się kompilatorem, wystarczy utworzyć Manifest, nadać mu taką samą nazwę jaką nosi nasz program i umieścić go w tym samym katalogu, w którym znajduje się nasz program.
Załóżmy, że utworzyłem program o nazwie MyProgram.exe i chcę mu nadać taki wygląd jaki mają wszystkie programy we Windows XP, więc
uruchamiam notatnik i wpisuję w nim manifest o następującej treści:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> |
Na zielono został zaznaczony jedyny zmienny element w treści całego manifestu, a to znaczy, że niezależnie od tego do jakiego programu tworzymy manifest jego treść zawsze wygląda tak samo zmienia się tylko nazwa programu.
Po utworzeniu manifestu pozostaje nam tylko zapisać go w tym samym katalogu, w którym umieściliśmy aplikację MyProgram.exe. Manifest zapisujemy pod nazwą MyProgram.exe.manifest .
Na zakończenie dodam tylko, że manifest można tworzyć dla wszystkich programów, nie tylko tych, które sami utworzyliśmy. Manifest czasami może wywoływać błędy w działaniu programu, wtedy trzeba go usunąć, oczywiście trzeba usunąć manifest a nie program.
Z własnego doświadczenia wiem, że manifest wywołuje błędy w działaniu programu Windows Commander, a objawia się to tym, że w oknie
katalogów nie widać żadnych katalogów poza tym niewidoczne są również dyski, ale to tak na marginesie.
Ściągnij przykładowy plik manifestu: MyProgram.exe.manifest
Przekazywanie do programu argumentów z linii komend.
O co chodzi z tym przekazywaniem argumentów? Otóż Ci z was, którzy dużo grają w gry komputerowe na pewno często stosują takie techniki, np gdy modyfikują skrót do jakiejś gry. Weźmy taką grę jak The Sims, ścieżka dostępu do tej gry może wyglądać tak:
"C:\Program Files\Maxis\The Sims\Sims.exe"
I taką właśnie ścieżkę można zobaczyć we właściwościach skrótu na zakładce skrót w okienku element docelowy, ale grający w tą grę wiedzą, że można zmodyfikować skrót i przekazać z linii komend argumenty do programu, umożliwiające ustawienie odpowiedniej rozdzielczości jeszcze przed uruchomieniem gry oraz pominięcie intro i weryfikacji sterowników. W ten sposób zmodyfikowany skrót będzie wyglądał tak:
"C:\Program Files\Maxis\The Sims\Sims.exe" -r1024x768 -skip_intro -skip_verify
I właśnie na tym polega przekazywanie do programu argumentów z linii komend. Oczywiście, żeby przekazać argumenty do programu nie trzeba od razu tworzyć skrótu i go modyfikować, można posłużyć się konsolą DOS lub po prostu linią komend dostępną w menu Start | Uruchom, Korzystając z konsoli DOS nie ma potrzeby umieszczania w cudzysłowiu ścieżki dostępu do programu czyli analogicznie do poprzedniego przykładu:
C:\Program Files\Maxis\The Sims\Sims.exe -r1024x768 -skip_intro -skip_verify
Jako ciekawostkę dodam, że istnieje możliwość przekazania argumentu do programu BCB. Gdy uruchamiamy ten program to wyświetla się okienko SplashScreen z logiem BCB, można je wyłączyć przekazując argument -NS:
"D:\Program Files\Borland\CBuilder4\Bin\bcb.exe" -NS
I najprościej jest zrobić to w skrócie do programu.
To tyle jeżeli chodzi o wstęp. Co jednak zrobić, żeby nasz własny program odbierał argumenty które są mu przekazywane z linii komend? Otóż jest to bardzo proste, wystarczy posłużyć się funkcją
ParamStr(). Żeby to lepiej wyjaśnić proponuję posłużyć się przykładem, w tym celu umieszczamy na formularzu obiekt Memo1 i tyle wystarczy, potem przechodzimy do pliku źródłowego i w konstruktorze klasy wpisujemy instrukcje które będą pobierały argumenty, a następnie będą je indeksowały w obiekcie Memo1:
// Plik źródłowy np. Unit1.cpp |
Kompilujemy program, po jego uruchomieniu w Memo1 powinna znajdować się pełna ścieżka dostępu do uruchomionego właśnie programu. Tak więc funkcja ParamStr() pobiera jako argument numer argumentu przekazywanego z linii komend, a zwraca wartość typu AnsiString. Co się zaś tyczy argumentów przekazywanych z linii komend to pierwszy argument posiadający indeks 0 jest zawsze ścieżką dostępu do programu.
Zmodyfikujmy nasz kod tak żeby pobierał trzy argumenty:
// Plik źródłowy np. Unit1.cpp |
Jeżeli teraz skompilujemy program, to po jego uruchomieniu zobaczymy to samo co poprzednim razem, ale wystarczy uruchomić go za pomocą polecenia z menu Start | Uruchom z dowolnymi argumentami, np:
"C:\BCB\Projekty\Project1\MyProgram.exe" -pierwszy_argument -drugi_argument
Teraz obiekt Memo1 powinien zawierać oprócz ścieżki dostępu do programu, również przekazywane argumenty. Należy uważać na spacje ponieważ argumenty są oddzielane od siebie właśnie spacjami. Kreski stawiane przed argumentami nic nie znaczą i nie trzeba ich stosować.
Jak jednak praktycznie wykorzystać funkcję ParamStr(). Znów posłużę się przykładem. Napiszemy program, który będzie oczekiwał argumentu -ShowMsg (nazwa argumentu jest dowolna) i jeżeli otrzyma taki argument to wyświetli odpowiedni komunikat. W tym celu tworzymy taki oto prosty kod w pliku źródłowym, istotne elementy zostały zaznaczone na żółto:
// Plik źródłowy np. Unit1.cpp AnsiString Test; |
Zmienna Test typu AnsiString o zasięgu globalnym (można by ją również zadeklarować w pliku nagłówkowym w sekcji private lub public) służy do przechowywania argumentu pobranego w konstruktorze klasy. Następnie w zdarzeniu OnShow dla formularza Form1 sprawdzana jest zawartość zmiennej Test i jeżeli jej zawartość jest zgodna z oczekiwaną wartością, to wykonywana jest odpowiednia instrukcja, w tym przypadku wyświetlenie komunikatu.