Było już o odczytywaniu atrybutów pliku za pomocą funkcji FileGetAttr, jeśli chodzi o zmianę atrybutu pliku lub folderu to służy do tego funkcja FileSetAttr i stosuje się ją tak:
// Plik źródłowy np.
Unit1.cpp. |
W podanym przykładzie atrybut pliku został zmieniony na tylko do odczytu, więc sprawa wydaje się bardzo prosta, ale taka nie jest ponieważ jeżeli będziemy za każdym razem wywoływać tą funkcję z innym atrybutem to wcześniejszy atrybut zostanie zastąpiony nowym, więc jeżeli chcemy zmienić więcej niż jeden atrybut na raz, to należy to zrobić wewnątrz tego samego zdarzenia:
// Plik źródłowy np.
Unit1.cpp. |
Ta metoda się sprawdza, jednak przed zmianą atrybutu pliku, dobrze jest odczytać atrybut jaki ona aktualnie posiada i zmieniać go lub pozostawić bez zmian a dodać nowy, do realizacji tego zadania najlepiej nadają się komponenty typu TCheckBox, będziemy potrzebowali cztery tego typu obiekty, jak cztery rodzaje atrybutów, posłużymy się również dwoma przyciskami typu TButton z których jeden będzie odczytywał atrybuty pliku, a drugi będzie je zmieniał w zależności od ustawień obiektów TCheckBox, stworzymy również dwie funkcje jedną odczytującą atrybuty pliku i ustawiającą odpowiednio obiekty TCheckBox i drugą funkcję zmieniającą atrybuty pliku w zależności od ustawień obiektów TCheckBox, może wydaje się to trochę skomplikowane, ale takie nie jest:
// Plik źródłowy np.
Unit1.cpp. |
No i nic dodać, nic ująć, to po prostu działa i jest banalnie proste. Atrybuty folderów zmienia się dokładnie tak samo, czyli traktuje się je jako pliki.
Atrybuty folderów i plików kasuje się przekazując funkcji FileSetAttr jako drugi argument wartość NULL.
// Plik źródłowy np.
Unit1.cpp. |
Pobieranie informacji o pamięci.
W celu pobrania informacji o pamięci RAM, pliku wymiany i pamięci wirtualnej należy posłużyć się klasą TMemoryStatus, klasa ta udostępnia osiem parametrów dotyczących pamięci komputera. Na przykładzie pobierania całkowitej wielkości pamięci RAM pokażę jak stosować tą klasę:
// Plik źródłowy np.
Unit1.cpp. |
Jak widać stosowanie klasy jest bardzo proste, niżej przedstawiam wszystkie funkcje tej klasy:
dwAvailPageFile - Dostępny rozmiar pliku wymiany.
dwAvailPhys - Dostępny rozmiar pamięci fizycznej RAM.
dwAvailVirtual - Dostępny rozmiar pamięci wirtualnej.
dwMemory - Procent wykorzystania pamięci fizycznej RAM. Ta wartość jest wyrażana w procentach i nie należy jej dzielić przez 1024.
dwTotalPageFile - Całkowity rozmiar pliku wymiany.
dwTotalPhys - Całkowity rozmiar pamięci fizycznej RAM.
dwTotalVirtual - Całkowity rozmiar pamięci wirtualnej.
Pobieranie informacji o dyskach.
Istnieje szereg gotowych funkcji służących do pobierania istotnych informacji na temat dysków zainstalowanych w komputerze. A oto co udało mi się zebrać:
Odczytywanie litery dysku z którego uruchomiono program:
// Plik źródłowy np.
Unit1.cpp. |
Odczytywanie nazwy dysku:
// Plik źródłowy np.
Unit1.cpp. |
Odczytywanie systemu plików:
// Plik źródłowy np.
Unit1.cpp. |
Odczytywanie numeru seryjnego dysku (chyba):
// Plik źródłowy np.
Unit1.cpp. |
Odczytywanie całkowitego rozmiaru dysku:
// Plik źródłowy np.
Unit1.cpp. |
Funkcja DiskSize pobiera jako
parametr wartość typu
unsigned char, inaczej mówiąc nie literę ani nazwę dysku, lecz kolejny numer
dysku, np. 1 = A; 2 = B; 3 = C ... i tak dalej, można podać wartość zero wtedy
będzie to dotyczyło dysku z którego program został uruchomiony.
Odczytywanie informacji o wolnej przestrzeni na dysku:
// Plik źródłowy np.
Unit1.cpp. |
Odczytywanie procentowej wolnej i pozostałej przestrzeni na dysku:
// Plik źródłowy np.
Unit1.cpp. |
Kopiowanie, przenoszenie i kasowanie katalogów, i plików za pomocą "latających folderów".
Pisząc o "latających folderach" mam na myśli to co widać na rysunku:
Opisywałem już jak kopiować pliki, jak tworzyć i kasować katalogi i pliki, ale umknęło mi kopiowanie katalogów wraz ze znajdującymi się w nich plikami i podkatalogami. Oczywiście można przerobić podane przeze mnie funkcję i napisać własną kopiującą całe katalogi z plikami, poprzez pobranie nazwy kopiowanego katalogu, utworzenie takiego samego w miejscu docelowym, zaindeksowanie całej zawartości katalogu źródłowego i przekopiowanie do katalogu docelowego pliku za plikiem. Takie kopiowanie wymaga jednak stworzenia odpowiedniej funkcji, dlatego pokażę znacznie prostszy sposób i krótszy, więc zaczynamy od kopiowania pojedynczego pliku za pomocą "latających folderów", do projektu należy włączyć bibliotekę #include <shellapi.h>, to właśnie ona odwali całą robotę:
// Plik źródłowy np.
Unit1.cpp. |
Zanim przejdziemy dalej, krótkie objaśnienie. Plik
źródłowy musi istnieć żeby można go było skopiować, nazwa pliku docelowego nie
musi być taka sama, jeśli podamy inną nazwę niż nazwa pliku źródłowego, to plik
docelowy zostanie zapisany z inną nazwą. Na końcu każdej ścieżki dostępu
znajduje się znak "\0", jest to oznaczenie końca łańcucha znaków i zawsze
musi tam być. Element struktury fos.hwnd pobiera uchwyt do czegoś, w
przykładzie jest to uchwyt do okna programu, czyli okienko z latającym folderem
pojawi się na tle naszego programu, jeżeli podamy tam jako parametr wartość
NULL, to okienko pojawi się w lewym górnym rogu pulpitu, można również
przekazać tam parametr this, wtedy okienka w ogóle nie będzie widać, ale
nie będzie też widać kiedy kopiowanie zostało zakończone. Podobny, ale lepszy
efekt uzyskamy jeśli ustawimy odpowiednio flagę: fos.fFlags = FOF_SILENT;.
Jeżeli w miejscu docelowym będzie się już znajdował plik o takiej samej nazwie,
wyskoczy okienko z zapytaniem, czy chcemy zastąpić istniejący pliki.
Chcą przekopiować cały katalog wraz z zawartością, czyli ze
wszystkimi podkatalogami i plikami ni należy podawać nazwy pliku, a jedynie
nazwę katalogu źródłowego i nazwę dla katalogu docelowego:
// Plik źródłowy np.
Unit1.cpp. |
W ten sposób zostanie przekopiowana cała zawartość katalogu i podobnie jak to miało miejsce w przypadku plików nazwa katalogu źródłowego i nazwa katalogu docelowego nie muszą być takie same. Jeżeli podając ścieżkę dostępu do katalogu źródłowego podamy na końcu symbol gwiazdki, to przed skopiowaniem pojawi się dialog z zapytanie czy utworzyć katalog w miejscu docelowym:
// Plik źródłowy np.
Unit1.cpp. |
Teraz krótkie przykłady na przenoszenie plików i katalogów:
// Plik źródłowy np.
Unit1.cpp. |
Kasowanie plików i folderów (niestety u mnie to nie działa) wyskakuje komunikat o błędzie, więc jeśli i u Ciebie to nie zadziała, to należy zastosować tradycyjną metodę podaną w poradzie kasowania plików i folderów:
// Plik źródłowy np.
Unit1.cpp. |
Zmiana nazwy pliku lub folderu:
// Plik źródłowy np.
Unit1.cpp. |
Zmiana nazwy katalogu nie wpływa w żaden sposób na pliki które się w nim znajdują.
Na zakończenie przykład kopiowania całych katalogów poprzez podanie nazwy katalogu źródłowego w Edit1 i katalog docelowego Edit2. Tutaj od razu zaznaczam, że kod ulegnie drobnej modyfikacji w związku z problemami jakie występowały przy kopiowaniu katalogów (nie dotyczy kopiowania plików) przy przekazywaniu nazwy katalogu źródłowego nie bezpośrednio do struktury, lecz właśnie poprzez obiekt Edit:
// Plik źródłowy np.
Unit1.cpp. |
O pobieraniu rozmiaru pliku było już w poradzie odczytywanie rozmiaru pliku i katalogu, ale tym razem chcę pokazać prosty kod w języku C odczytujący rozmiar pliku, żeby zrealizować to zadanie należy włączyć do projektu bibliotekę #include <io.h>, a kod jest banalnie prosty i nie wymaga wyjaśnień:
// Plik źródłowy np.
Unit1.cpp. |
Inny sposób w C++ to skorzystanie z funkcji GetFileSize, niestety nie wiem czy występuje w środowisku BCB 6 czy też dopiero w BDS 2006.
// Plik źródłowy np.
Unit1.cpp. |
Zapisywanie i odczytywanie komponentów do/z pliku.
W tej poradzie chcę przedstawić bardzo użyteczną rzecz, a mianowicie, jak to wynika z tytułu, chodzi o zapisywanie komponentów do pliku. Co to właściwie znaczy? Załóżmy że mamy na formularzu trzy różne komponenty Edit1, Memo1 i Image1, istnieje sposób na to żeby zapisać te trzy komponenty wraz z ich właściwościami i zawartością do jednego pliku, a potem to odczytać. Zapisując komponenty do pliku, jak już wspomniałem zapisujemy ich właściwości, takie jak rozmiar, położenie na formularzu, typ czcionki itp., ale zapisujemy również to co zostanie do tych komponentów wprowadzone, czyli w przypadku np. Edit1 i Memo1, będzie to tekst który został do nich wprowadzony, przy czym nie musi to być tekst wprowadzony jeszcze przed skompilowaniem programu, lecz w dowolnym momencie już w trakcie działania programu. Kolejna ciekawostka jest taka, że jeśli zmienimy położenie i właściwości komponentów takie jak np. rozmiar to po wczytaniu takiego komponentu z pliku, właściwości tegoż komponentu zostaną ustawione ponownie na takie jakie zostały do tego pliku zapisane. Mam nadzieję, że już wszyscy to zrozumieli, dodam tylko że tak stworzony plik, jest plikiem binarnym, a zapisanie i odczytanie komponentów jest banalnie proste ponieważ ogranicza się do wykorzystania funkcji WriteComponent(TComponent *) i ReadComponent(TComponent *), a oto jak wyglądają kod zapisywanie i kod odczytywania:
// Plik źródłowy np.
Unit1.cpp. |
Komponenty zapisywane i odczytywane muszą istnieć na formularzu, kolejność
odczytywania komponentów z pliku zawsze musi być taka sama jak kolejność
zapisywania, można zapisywać formularz np. Form1, ale przy próbie odczytywania
wyskoczą komunikaty o błędach. Jeżeli umieścimy na formularzu np. Panel1 a na
nim inne komponenty, to Panel1 stanie się ich "rodzicem", zapisując Panel1 do
pliku nie zapisujemy jednak komponentów, które się na nim znajdują, tak więc
można zapisywać tylko każdy komponent oddzielnie. Można odczytać tylko jeden
komponent z pliku (jeśli zachodzi taka potrzeba) nawet jeśli do jednego pliku
zapisaliśmy ich więcej, jednak wciąż obowiązuje kolejność w jakiej zostały
zapisane, jeśli jako pierwszy zapiszemy komponent Memo1, to możemy go odczytać z
pliku jako pierwszy, ale jeśli zapisaliśmy jako pierwszy Memo1, jako drugi Edit1
to żeby odczytać zawartość tylko Edit1 należy odczytać najpierw zawartość Memo1
i dopiero potem Edit1. Zapisywanie komponentów działa na wszystkie komponenty,
ale nie wszystkie komponenty da się odczytać, problemy występują z komponentami
wykorzystującymi wirtualne klasy i tak np. nie da się odczytać komponentu
TStringGrid.
Poradę można wykorzystać przy tworzeniu programów, w których
chcemy zmieniać "skórki", wystarczy utworzyć kilka różnych skórek i zapisać je
do wielu plików, a potem można je już bez problemu odczytywać i nie trzeba
pobierać oddzielnie konfiguracji każdego komponentu. Po odpowiednim
zaprogramowaniu można umożliwić użytkownikowi tworzenie własnych skórek i jest
to w zasadzie banalnie proste.
Zapisywanie i odczytywanie bufora do/z pliku
Pisząc o zapisywaniu bufora do pliku mam na myśli zapisywanie i odczytywanie danych różnych typów do/z jednego pliku. Zadanie to można zrealizować za pomocą klasy TFileStream, wykorzystując funkcję WriteBuffor i ReadBuffor. Pokażę na przykładzie jak zapisać do jednego pliku łańcuch znaków (char), data (TDateTime) i liczbę (Integer). Umieszczamy na formularzu trzy obiekty Edit1 dla łańcucha znaków, Edit2 dla daty i ComboBox1 zapiszemy numer wybranego w tym obiekcie indeksu:
// Plik źródłowy np.
Unit1.cpp. |
Funkcje WriteBuffer i ReadBuffer pobierają dwa argumenty, pierwszy argument to zmienna, obiekt lub struktura, przy czym zmienna AnsiString jest niedopuszczalna, drugi parametr to rozmiar zapisywanego obiektu. Kolejność odczytywania musi być taka sama jak kolejność zapisywania. Rozmiary odczytywanych i zapisywanych danych muszą być takie same.
Istnieje instrukcja goto umożliwiająca wykonanie skoku do wybranej pozycji w kodzie. Instrukcja działa w obrębie tej samej funkcji, zdarzenia, występuje ona zawsze z etykietą, czyli najpierw w jakimś miejscu kodu należy umieścić etykietę, do której wykonany będzie skok za pomocą goto, etykieta jest to dowolny wyraz jednoczłonowy zakończony nie średnikiem lecz dwukropkiem. Najlepiej to widać na przykładzie:
// Plik źródłowy np.
Unit1.cpp. |
Przedstawiony przykład jest banalny i nie przychodzi mi do głowy, żadne praktyczne zastosowanie tej instrukcji. Z korzystaniem z tej instrukcji wiąże się niebezpieczeństwo, ponieważ nieprawidłowo użyta będzie działać w nieskończoność powodując "zawieszenie" programu, dlatego powinna ona występować zawsze z jakimś warunkiem, który będzie ograniczał jej zasięg.
Tworzenie przestrzeni dla zmiennych i funkcji.
W języku C++ istnieje możliwość tworzenia przestrzeni wewnątrz których można umieszczać zmienne lub funkcje, takie przestrzenie przypominają trochę klasy, ale są znacznie prostsze w użyciu. Słowem kluczowym do tworzenia przestrzeni jest namespace po którym występuje nazwa przestrzenia, a potem nawiasy. Wewnątrz nawiasów umieszcza się zmienne lub funkcje, nie są one przypisane do żadnej klasy ani obiektu podobnie jak sama przestrzeń. Nazwa przestrzeni jest dowolna, ale jednoczłonowa. Przestrzeń można tworzyć zarówno w pliku źródłowym jak i nagłówkowym, jednak zawsze poza obrębem klasy, funkcji czy zdarzenia, nigdy wewnątrz:
// Plik źródłowy np.
Unit1.cpp.
void __fastcall TForm1::Button17Click(TObject *Sender) { |
Lub
// Plik nagłówkowy np.
Unit1.h.
class
TForm1 : public TForm |
// Plik źródłowy np.
Unit1.cpp. void __fastcall TForm1::Button17Click(TObject *Sender) { |
Proszę zwrócić uwagę, że do adresowania zmiennych będących częścią przestrzeni używa się nazwy przestrzeni i podwójnego dwukropka. Można zdefiniować dowolną liczbę przestrzeni o dowolnych nazwach, ale w granicach zdrowego rozsądku. Stosowanie przestrzeni ma tą zaletę, że można tworzyć zmienne i funkcje o takich samych nazwach i odpowiednio je adresować w zależności od potrzeb.
Odczyt i zmiana daty utworzenia pliku.
nadesłał: KamyQ
W celu odczytania daty utworzenia pliku należy posłużyć się funkcją GetFileTime, jednak żeby wyświetlić datę w zrozumiałej formie należy dokonać konwersji najpierw do formatu SystemTime, a następnie do DateTime:
// Plik źródłowy np.
Unit1.cpp. //-------------------------------- |
Gwoli wyjaśnienia najpierw
został utworzony uchwyt do pliku (hFile), następnie pobierana jest do
struktury FILETIME data utworzenie, modyfikacji i ostatniego użycia
pliku, poprzez funkcję GetFileTime, potem jest przeprowadzana konwersja
do struktury SYSTEMTIME, a w ostatecznym rachunku wszystko zostaje
przepisane do wirtualnej klasy TDateTime i sformatowane za pomocą funkcji
FormatDateTime do wartości typu AnsiString. Zmienna aFT
pobiera informację o dacie utworzenia pliku, bFT pobiera
informację o dacie modyfikacji pliku, a cFT o dacie ostatniego
użycia pliku. W przykładzie zostanie wyświetlona informacja o dacie utworzenia
pliku, ponieważ to właśnie zmienna aFT zostaje przepisana za
pomocą funkcji FileTimeToSystemTime do struktury SYSTEMTIME, ale
oczywiście można w ten sam sposób pobrać wszystkie trzy daty dotyczące pliku.
Zmiana daty utworzenia pliku będzie przebiegać podobnie, tym
razem jednak skorzystamy z funcji SetFileTime:
// Plik źródłowy np.
Unit1.cpp. |
Jak widać kod wygląda podobnie, różnica polega na sposobie wprowadzenia nowej daty do klasy TDateFile za pomocą funkcji EncodeDate i EncodeFile, należy zawsze wprowadzać datę i czas w przeciwnym razie konwersja może mieć nieprawidłowy przebieg. To co się rzuca w oczy to, że przed zmianą daty utworzenia została przywołana funkcja odczytująca daty GetFileTime, jest to konieczne, ponieważ przy zmianie daty utworzenia należy wprowadzić wszystkie trzy rodzaje daty, a w przykładzie zmieniamy tylko datę utworzenia pliku, więc pozostałe powinny być wprowadzone takie jakie już ten plik posiada, więc oczywistym wydaje się, że najpierw trzeba te daty odczytać. Podobnie jak to było z odczytywaniem daty, struktura aFT odczytuje datę utworzenia pliku, bFT datę modyfikacji pliku, a cFT datę ostatniego użycia pliku.
Sprawdzanie stanu klawiszu Caps Lock i Insert.
Do sprawdzania stanu klawiszy służy funkcja GetKeyState, która pobiera jako argument klawisz stan którego chcemy sprawdzić. W przykładzie stan klawisza będzie sprawdzany w funkcji przechwytującej komunikaty, dlatego trzeba umieścić na formularzu komponent ApplicationEvents1 z zakładki Additionals. Na formularzu należy również umieścić komponent StatusBar1 i utworzyć w nim dwa panele. W panelu pierwszym będzie wyświetlany stan klawisza Caps Lock, a w panelu drugim stan klawisza Insert. Dla komponentu ApplicationEvents1 tworzymy zdarzenie OnMessage i umieszczamy w nim kod przechwytujący stan klawiszy:
// Plik źródłowy np.
Unit1.cpp. |
W podanym przykładzie stan klawisza będzie sprawdzany tylko przy aktywnym
formularzu. Jeżeli chcemy przechwytywać stan klawisza nawet wtedy gdy program
jest nieaktywny należy posłużyć się zegarem (TTimer) lub umieścić pętlę for
wykonującą się w nieskończoność w nowym wątku. Można sprawdzać stan każdego
klawisz podając np. dla klawisza A:
Rezerwowanie pliku wymiany na potrzeby programu.
Istnieje funkcja
GetMemory która rezerwuje, a właściwie przypisuje sobie na własność
pewien obszar pliku wymiany, funkcja pobiera tylko jeden argument
określający w bajtach wielkość rezerwowanego obszaru:
// Plik źródłowy np.
Unit1.cpp. |
Zmiana położenia ScrollBox w drugim obiekcie w oparciu o położenie ScrollBox w obiekcie pierwszym.
Swego czasu na forum pojawiło się pytanie jak przesuwając w obiekcie Memo1 ScrollBox przesunąć go do takiej samej pozycji w obiekcie Memo2. Do przesuwania ScrollBox można posłużyć się funkcją SetScrollInfo lub SetScrollPoz, ale te funkcje pozwalają tylko na przesunięcie samego ScrollBox, nie przewijają natomiast strony w obiekcie np. Memo1. Do realizacji tego zadania niezbędna jest struktura SCROLLINFO i dwie funkcje: GetScrollInfo pobierająca informacje o położeniu ScrollBox w jednym obiekcie i przepisująca je do stuktury, oraz funkcja PostMessage wysyłająca odpowiedni komunikat do drugiego obiektu, w którym chcemy zmienić położenie ScrollBox. Funkcja PostMessage jest bliźniaczą funkcją SendMessage, różni się od niej tylko tym, że czeka na wykonanie komunikatu:
// Plik źródłowy np.
Unit1.cpp. |
W przedstawionym przykładzie zmiana położenia ScrollBox w obiekcie Memo2 nastąpi dopiero po wywołaniu zdarzenia OnClick dla przycisku Button1, ale można zrobić to w taki sposób, że przesuwanie ScrollBox w obiekcie Memo1 będzie powodowało jednoczesne przesuwanie ScrollBox w obiekcie Memo2. Należy w tym celu stworzyć funkcję przechwytującą komunikat o przesuwaniu ScrollBox i powiązać go z obiektem Memo1, w tym celu deklarujemy w pliku nagłówkowym w sekcji private lub public obiekt typu TWndMethod i funkcję przechwytującą komunikat:
// Plik nagłówkowy np.
Unit1.h. |
Teraz należy tylko powiązać obiekt Memo1 z funkcją MoveScroll i stworzyć definicję tej funkcji:
// Plik źródłowy np.
Unit1.cpp. |
Przedstawiony sposób pozwala na przesuwanie paska pionowego, dla paska poziomego należy zmienić SB_VERT na SB_HORZ, a WM_VSCROLL na WM_HSCROLL, może to wyglądać tak:
// Plik źródłowy np.
Unit1.cpp. |
Tworzenie pliku z zasobami i zapisywanie zasobów do pliku.
O
sposobie tworzenia plików z zasobami i włączania ich do programu pisałem
już wielokrotnie w serwisie dlatego teraz przedstawię to pokrótce,
utworzymy plik z różnymi zasobami by można je było później z niego wydobyć
i ponownie zapisać do pliku.
W celu utworzenie pliku z zasobami przygotowujemy sobie zasoby, które
chcemy do niego włączyć, mogą to być dowolne pliki, ja w przykładzie
posłużyłem się plikiem czcionki, bitmapą, ikoną i animowanym kursorem.
Otwieramy Notatnik i umieszczamy w nim następujące wpisy:
ID_1
RCDATA
"czcionka.ttf" |
Jak widać dla
każdego zasobu został utworzony w pliku kod na który składają się trzy
elementy. Pierwszy element to identyfikator zasobu od ID_1 do
ID_4, identyfikator to po prostu dowolna nazwa po której zasób będzie
rozpoznawalny, nazwa ta musi być jednoczłonowa i może zawierać tylko znaki
języka angielskiego. Drugi element to typ zasobu, występują różne typy
zasobów, jeżeli jednak umieszczamy w zasobach pliki różnych rodzajów i
zamierzamy je później z tych zasobów wydobywać to należy użyć typu
RCDATA, który określa plik jako binarny plik zasobu i nie rozróżnia
typu pliku, co jest o tyle wygodne, że każdy plik zostanie wydobyty z
zasobu w takiej postaci pod jaką został w nim umieszczony. Trzeci element
to nazwa pliku i ścieżka dostępu do pliku, który chcemy umieścić w
zasobach, jeżeli plik znajduje się w tym samym katalogu co plik zasobu to
można podać tylko nazwę pliku.
Tak utworzony plik tekstowy zapisujemy pod dowolną nazwą z
rozszerzeniem *.rc, np. zasoby.rc. Występuje tutaj duża dowolność ponieważ
równie dobrze można ten plik zapisać jako plik tekstowy czyli np. jako
zasoby.txt. Gdy plik jest już gotowy przystępujemy do jego kompilacji w
tym celu uruchamiamy wiersz poleceń, tzw. konsolę CMD i wpisujemy
polecenie: brcc32 <nazwa wejściowego pliku dla zasobów> <nazwa
wyjściowego pliku zasobów>, dla przykładu będzie to wyglądało tak:
brcc32 zasoby.rc zasoby.res
Gdy plik zasobów, w przykładzie zasoby.res jest gotowy, włączamy go do
naszego projektu poprzez menu Project | Add To Project...
I na tym kończy się przygotowanie pliku z zasobami został on włączony do
projektu i jest gotowy do użycia. Teraz stworzymy funkcję, która
wydobędzie poszczególne pliki z pliku zasobów:
// Plik źródłowy np.
Unit1.cpp. |
Jeśli wydobycie zasobu się powiedzie funkcja zwraca wartość true, w przeciwnym razie false. Przedstawię teraz przykład na wydobycie pojedynczego zasobu i wszystkich zasobów:
// Plik źródłowy np.
Unit1.cpp. |
Na zakończenie pokażę jeszcze jeden sposób na wywołanie tej funkcji:
// Plik źródłowy np.
Unit1.cpp. |
Powyższa porada może okazać się bardzo
użyteczna przy tworzeniu wersji instalacyjnej jakiegoś własnego programu,
ponieważ w zasobach można w ten sposób umieszczać pliki dowolnego rodzaju,
w tym również pliki programów, czyli *.exe. Co więcej tak stworzony
instalator można spakować jakimś exepackerem, np. UPX zmniejszając tym
samym rozmiar pliku instalacyjnego, pomimo takiego spakowania zasoby wciąż
będą mogły być wydobywane bez problemu. Spis identyfikatorów i plików do
wypakowania z zasobów można umieszczać w odrębnym pliku i z niego
wczytywać przed instalacją, można równie dobrze taki plik umieścić w
zasobach i wczytywać jego zawartość bezpośrednio z zasobów o czym pisałem
juz w serwisie w poradach
Umieszczanie plików tekstowych w zasobach programu i
Tworzenie tablicy łańcuchów znaków i
umieszczanie jej w zasobach programu.
Jak dodać deinstalator programu do listy Dodaj usuń programy?
Tworząc wersję
instalacyjną własnego programu, dobrze jest stworzyć również program
odinstalowujący, taki program będzie w stanie odinstalować wszystkie
zainstalowane składniki, a na koniec usunie sam siebie poprzez
stworzony przez plik *.BAT no i na koniec usunie sam plik *.BAT.
Najpierw należy utworzyć program odinstalowujący, nazwa jest oczywiście
dowolna np. Uninstall.exe. W takim programie należy umieścić
instrukcje usuwające aplikację i wpisy z rejestru, trzeba również dodać
instrukcje usuwające sam program odinstalowujący. Tutaj pojawia się pewien
problem ponieważ program sam siebie nie usunie, gdyż nie można usunąć
otwartego procesu, dlatego wymyśliłem sposób na usunięcie deinstalatora
poprzez stworzenie pliku *.BAT z odpowiednimi wpisami. Plik *.BAT ma to do
siebie, że jest otwierany za pomocą programu cmd.exe więc zawarte w
nim instrukcje mogą usunąć nie tylko sam deinstalator, ale również sam
plik *.BAT. Na prostym przykładzie pokaże jak stworzyć taki prosty
deinstalator, będzie on usuwał przykładowy program o nazwie Lekcja4.exe i
utworzy odpowiedni plik *.BAT. Przykładowy deinstalator w efekcie swojego
działania usunie trzy pliki, plik aplikacji Lekcja4.exe, plik
deinstalatora Uninstall.exe, oraz pomocniczy plik wsadowy Uninstall.bat,
oczywiście usunięciu ulegnie również wpis w rejestrze dodający odpowiedni
wpis do apletu panelu sterowania Dodaj usuń programy. O tym jak dodawać
program do listy Dodaj usuń programy napiszę w dalszej części tej porady,
a tera przykład deinstalatora:
// Plik źródłowy np.
Unit1.cpp. |
Jak widać jest to maksymalnie uproszczona
wersja deinstalatora. Zwracam uwagę na pewien bardzo istotny fakt, ścieżki
dostępu do wszystkich kasowanych plików powinny być bezwzględne (nie
względne), w przykładzie wszystkie ścieżki zostają obliczone w odniesieniu
do aplikacji deinstalatora (Uninstall.exe). Użycie bezwzględnych ścieżek
dostępu jest istotne z tego powodu, że deinstalatorowi uruchomionemu
poprzez aplet panelu sterowania Dodaj usuń programy, zostanie przypisana
ścieżka dostępu przez sam aplet, więc względne ścieżki dostępu nie będą
odwoływały się do odinstalowywanych plików, użycie bezwzględnych ścieżek
dostępu zawsze przypisze prawidłowe ścieżki dostępu do plików.
Tworząc wersję instalacyjną programu można utworzyć plik tekstowy np.
Install.log w którym zostaną podane bezwzględne ścieżki dostępu do
wszystkich zainstalowanych plików, które w późniejszym czasie mogą zostać
odinstalowane. Deinstalator może odczytać zawartość takiego pliku i w ten
sposób usunąć wszystkie zainstalowane pliki. Niezależnie od tego jakie
pliki odinstaluje, żeby nie pozostawiać zbędnych plików należy utworzyć
plik *.BAT usuwający pozostałości deinstalatora. Zamiast usuwać pliki za
pomocą funkcji DeleteFile można równie dobrze utworzyć w pliku wsadowym
polecenie del c:\nazwa_katalogu /Q /S kasujące całą zawartość folderu.
Oczywiście nie ma w tym nic nowego plik *.BAT zawiera komendy DOS dla
konsoli CDM.exe. Dla tych którzy DOS'a nie znają przypominam (bo może się
przydać) komenda kasujaca katalog to RMDIR, np. RMDIR C:\Windows, ale
przed skasowanie katalogu należy usunąć całą jego zawartość, np: DEL
C:\WINDOWS /Q /S w tym miejscu ostrzegam mniej bystrych, nie należy
kasować katalogu WINDOWS. Jeśli chcecie uzyskać spis wszystkich poleceń
dla konsoli CMD należy wpisać w niej komendę HELP.
Gdy już wyjaśniliśmy sobie jak stworzyć program
odinstalowujący, możemy przystąpić do tworzenie funkcji dodającej
deinstalator do listy Dodaj usuń programy, cała sztuczka polega na dodaniu
odpowiednich wpisów do rejestru:
// Plik źródłowy np.
Unit1.cpp. |
Nazwa klucza Lekcja4 jest w zasadzie
dowolna (jednoczłonowa, tylko angielskie znaki). Wartość DisplayIcon
przechowuje informacje o ikonie wyświetlanej na liście Dodaj usuń
programy. Może to być ścieżka dostępu do pliku *.ICO lub do innego pliku
zawierającego ikony. Wartość DisplayName jest umieszczana jako
nazwa programu odinstalowującego na liście Dodaj usuń programy.
Wartość UninstallString to ścieżka dostępu do deinstalatora.
Istnie jeszcze kilka innych sposobów na odinstalowanie, można się np.
posłużyć systemowymi programami IsUninst.exe lub Uninst.exe,
jednak wymagają one plików z informacjami o odinstalowywanych plikach, są
to pliki *.isi i *.log, ale w tej chwili nie wiem jak się tworzy takie
pliki.