Delphi - Strumienie - TFileStream
Wed, 9 June 2004
#2 TFileStream
Wstęp
W pierwszej części zajmiemy się strumieniem obsługującym
operacje na plikach -TFileStream. Dzięki temu strumieniowi możemy zapisywać
dowolne dane do pliku. Zamiast prawić długi wstęp, na który pozwoliłem sobie
w części I, przechodzę od razu do rzeczy.
Pierwszym zadaniem, jakie musimy wykonać uzyskując dostęp do pliku przez
strumienie to ich utworzenie. Aby utworzyć strumień piszemy:
var
strumien : TFileStream;
begin
strumien := TFileStream.Create('C:\plik.txt',fmCreate);
Jak widzimy, najpierw deklarowana jest zmienna typuTFileStream, do której potem będziemy się odwoływać np. przy zapisywaniu. Potem następuje utworzenie strumienia za pomocą:
TFileStream.Create(1,2)
gdzie:
1 - ścieżka dostępu do pliku
2 - parametr określający typ dostępu do tego pliku. Może on przybrać 4
podstawowe formy:
fmCreate- po zastosowaniu tego parametru plik określony w pierwszym
parametrze zostanie utworzony jeśli go jeszcze nie ma lub wyczyszczony gdy już
istnieje (coś jak ReWrite). Będziemy też mieli uprawnienia do zapisu.
fmOpenRead- otwiera plik jedynie do odczytu. Próba zapisu z tym parametrem
wywoła błąd.
fmOpenWrite- otwiera plik jedynie do zapisu. Próba odczytu z tym parametrem
spowoduje błąd.
fmOpenReadWrite- otwiera plik do odczytu i zapisu. Jednym słowem mamy pełną
kontrolę nad treścią ;)
Po utworzeniu pliku, przydało by się jeszcze coś zrobić, dlategoTFileStreamwyróżniamy 4 (a właściwie 2 pary) bardzo przydatne funkcje zapisu i odczytu, mianowicieWrite,Read,WriteBuffer,ReadBuffer.
Write
Funkcja ta pozwala zapisać dane do pliku. Jej budowa prezentuje się następująco:
function Write(const Buffer; Count: Longint): Longint; override;
Widzimy, że jej pierwszym parametrem jest stała która przechowuje w sobie
dane przeznaczone do zapisania. Następny parametr "mówi" funkcji ile
danych ma zapisać. Na końcu zadeklarowany jest typ danej zwracanej przez
funkcję. A funkcja ta zwraca ilość danych które udało jej się zapisać do
pliku.
Przyjrzyjmy się teraz dokładniej parametrom funkcjiWrite. Kiedy do pliku próbujemy
zapisać jakiś tekst - przykładowo:
Ala ma kota.
... liczący 12 znaków, a Ty wprowadzisz jako drugi parametr funkcjiWritecyfrę 6, funkcja zapisze do pliku jedynie urywek:
Ala ma
Zapis tekstu przeważnie przeprowadza się w całości, dlatego aby nie liczyć ręcznie ilości danych do zapisania (już nie mówię o przypadkach, w których jest to niewykonalne) stosujemy funkcjeSizeOf(zmienna), która zwraca wielkość zmiennej i w ten sposób gwarantuje, że wszystkie znaki zostaną zapisane:
SizeOf(buff)
(gdzie "buff" jest zmienną przechowującą tekst przeznaczony do zapisania) co daje nam zapisanie wszystkiego co chcieliśmy zapisać. Nasza funkcjaWrite, po wszystkich tych przejściach wygląda zatem następująco:
zapisanych := Write(buff, SizeOf(buff));
Zmienna "zapisanych" będzie w tym wypadku (po wykonaniu funkcjiWrite) zawierała liczbę bajtów, które funkcja zdołała zapisać do pliku. Jeden bajt, jak wiemy, charakteryzuje się tym, że możemy za jego pomocą zapisać dowolny znak ASCII, tak więc wartość zmiennej "zapisanych" będzie w rzeczywistości liczbą poprawnie zapisanych znaków (bajtów).
Read
Funkcja ta pozwala odczytać dane z pliku. Jej budowa prezentuje się następująco:
function Read(var Buffer; Count: Longint): Longint; override;
Widzimy, że jej pierwszym parametrem jest zmienna, która przechowuje w sobie
odczytane z pliku dane. Następny parametr "mówi" funkcji, ile danych
ma odczytać. Na końcu zadeklarowany jest typ danej zwracanej przez funkcje.
Funkcja ta zwraca ilość danych, które udało jej się odczytać pliku.
Przyjrzyjmy się teraz dokładniej parametrom funkcjiRead. Gdy w pliku znajduje
się jakiś tekst - przykładowo:
Ala ma kota.
... liczący 12 znaków, a Ty wprowadzisz jako drugi parametr funkcjiReadcyfrę 6, funkcja zwróci Ci jedynie:
Ala ma
Bezsensem jednak byłoby podawanie za każdym razem ilości danych do odczytania. Możemy przecież nie mieć pojęcia, ile znaków znajduje się w pliku i tym samym ile znaków my mamy odczytać. Dlatego przeważnie jako drugi parametr podajemy:
SizeOf(buff)
gdzie "buff" jest zmienną do której odczytujemy tekst. Nasza funkcjaReadwygląda zatem następująco:
odczytanych := Read(buff, SizeOf(buff));
Zmienna "odczytanych" będzie w tym wypadku (po wykonaniu funkcjiRead) zawierała liczbę bajtów zapisanych do pliku. Jeden bajt, jak wiemy, charakteryzuje się tym, że możemy za jego pomocą zapisać dowolny znak ASCII, tak więc wartość zmiennej "odczytanych" będzie w rzeczywistości liczbą odczytanych znaków (bajtów).
WriteBuffer i ReadBuffer
W zasadzie procedury te nie różnią się zbytnio od funkcjiWriteiRead, poza jednym wyjątkiem - nie zwracają one żadnych danych. Tak więc proceduraWriteBuffernie zwróci nam ilości zapisanych danych do pliku, tak jak to miało miejsce przy funkcjiWrite, a proceduraReadBuffernie zwróci nam ilości danych odczytanych z pliku (jak to miało miejsce przyRead).
Zwolnienie pamięci
Po zakończeniu korzystania ze strumieni wypadałoby zwolnić pamięć, którą one zajmują. Dlatego przeważnie po zakończeniu zapisu/odczytu zwalnia się pamięć zajmowaną przez strumień. Robimy to następująco:
strumien.Free;
Gwarantuje nam to brak błędów w przypadku, gdy coś zapisaliśmy, a teraz chcemy to odczytać.
Trochę kodu
Aby zapisać tekst wystarczą jedynie trzy wiersze kodu właściwego, tzn. kodu który służy jedynie do zapisu!
var
Strumien : TFileStream;
tekst: string;
begin
tekst := 'Ala ma kota.';
// Zapis zwykłego tekstu
Strumien := TFileStream.Create('C:\plik.txt', fmCreate);
Strumien.WriteBuffer(tekst, SizeOf(tekst));
Strumien.Free;
Widzicie? Utworzenie strumienia, zapisanie danych, zwolnienie pamięci po
strumieniu.
Teraz tak zapisany tekst spróbujemy odczytać:
var
Strumien : TFileStream;
tekst: string;
begin
// Odczytanie zwykłego tekstu
Strumien := TFileStream.Create('C:\plik.txt', fmOpenRead);
Strumien.ReadBuffer(tekst,SizeOf(tekst));
Strumien.Free;
I również 3 linijki. Odczytany tekst siedzi nam w zmiennej
"tekst".
Ale dlaczego mówiłem o zwalnianiu pamięci po zakończonej operacji? Ponieważ
gdybyśmy chcieli zapisać i odczytać coś w jednej procedurze bez zwalniania
pamięci, uzyskalibyśmy piękny błąd:"String read error"lub"String write error". Szczególnie chciałbym to zaznaczyć, ponieważ kiedyś zajęło
mi dużo czasu dojście dlaczego ten błąd występuje i jak sobie z nim poradzić.
Jeśli chcielibyśmy teraz napisać procedurę najpierw zapisującą, potem
odczytującą dany tekst (bez sensu ;) to najpierw musimy:
- utworzyć strumień
- zapisać
- zwolnić pamięć
- utworzyć strumień
- odczytać
- zwolnić pamięć
Wyglądałoby to następująco:
var
Strumien: TFileStream;
tekst: string;
begin
tekst := 'Ala ma kota.';
// Zapis zwykłego tekstu
Strumien := TFileStream.Create('C:\plik.txt', fmCreate);
Strumien.WriteBuffer(tekst, SizeOf(tekst));
Strumien.Free;
tekst := '';
// Odczytanie zwykłego tekstu
Strumien := TFileStream.Create('C:\plik.txt', fmOpenRead);
Strumien.ReadBuffer(tekst,SizeOf(tekst));
Strumien.Free;
ShowMessage('Odczytałem taki tekst:'+tekst);
Bez większego sensu, ale powinno działać. Gdybyśmy jednak nie zwolnili pamięci przed odczytem i nie utworzyli na nowo strumienia, system poinformowałby nas o tym wyświetlając stosowny komunikat ;)
var
Strumien: TFileStream;
tekst: string;
begin
tekst := 'Ala ma kota.';
// Zapis zwykłego tekstu
Strumien := TFileStream.Create('C:\plik.txt', fmCreate);
Strumien.WriteBuffer(tekst, SizeOf(tekst));
tekst := '';
// Odczytanie zwykłego tekstu
Strumien.ReadBuffer(tekst,SizeOf(tekst));
ShowMessage('Odczytałem taki tekst:'+tekst);
Taki kod nie będzie działał - zresztą możesz spróbować. Owszem, kod skompiluje się
i kompilator nie poinformuje nas o zaistniałym błędzie. Dopiero podczas
testowania programu wykryjesz błąd.
Myślę, że powyższe kody zostały już opanowane - przejdźmy do ciekawszych
rzeczy, jak na przykład - zapis całego komponentu do pliku.
Zapis ustawień komponentu do pliku - WriteComponent()
Strumienie posiadają bardzo użyteczną w wykorzystaniu
funkcję, o której wspomniałem już w części I - zapis komponentu. Nie widzę
żadnych przeszkód, dla których miałbym przedłużać teorię - przejdźmy więc
do praktyki ;)
Aby zapisać komponent, musisz najpierw ustawić go na formie. W tym celu z
palety komponentów, wybierz komponentTEdit(paletaStandard) i połóż
go na formie. Potrzebne będą jeszcze dwaTButton, które również
znajdziesz na palecie Standard. Dla pierwszego przycisku (Button1) ustaw właściwość
"Caption" na "Zapis" (zresztą jak uważasz), drugiemu nadaj
etykietę "Odczyt".
Po wykonaniu czynności kosmetycznych, przechodzimy do kodu, klikając
dwukrotnie na przycisk "Zapis". W nowoutworzonej procedurze wpisujemy
taki oto kod:
var
strumien : TFileStream;
begin
strumien := TFileStream.Create('C:\plik.txt',fmCreate);
strumien.WriteComponent(Edit1);
strumien.Free;
Już tłumaczę...
Pierwsza linia
Słówkovar- bez zbędnych objaśnień ;)
Druga linia
Zadeklarowanie zmiennej "strumien" jako nowego strumienia
typuTFileStream
Czwarta linia
Utworzenie nowego strumienia (fmCreate) do(?) pliku 'C:\plik.txt'.
Piąta linia
Tutaj zatrzymamy się trochę dłużej. ProceduraWriteComponent()przeprowadza zapis podanej jako jej parametr kontrolki. Ma ona następującą
budowę:
procedure WriteComponent(Component: TComponent);
Jak widzimy jako parametr procedury podajemy nazwę kontrolki (czyli w naszym przypadkuEdit1). Dzięki temu procedura nie pyta się programisty o dodatkowe właściwości komponentu, który właśnie zapisuje, tylko pobiera sobie je sama (sama odwołuje się do podanego komponentu i bierze co jej potrzebne). My również możemy pisać takie procedury, aczkolwiek częściej podajemy konkretne parametry, jak tekst komponentu czy jego wysokość. OK.
Odbiegłem trochę od tematu - przepraszam i biorę się ponownie do roboty.
Szósta linia: zwolnienie pamięci strumienia. Jeśli udało Wam się zapisać
komponent do pliku (a na pewno Wam się udało :) to przechodzimy do
odczytywania.
Odczyt ustawień komponentu z pliku - ReadComponent()
Procedura jest bardzo podobna, jak nie identyczna z WriteComponent(). W sumie, nie musimy nic zmieniać, oprócz praw dostępu do
pliku. Musimy uzyskać prawa otworzenia/odczytu. Dlatego zamiast parametru
"fmCreate" stosujemy "fmOpenRead".
Wygenerujcie teraz drugą procedurę - tj. po naciśnięciu przycisku
"Odczyt". W tym celu dwukrotnie klikamy na przycisk "Odczyt"
i w nowoutworzonej procedurze piszemy:
var
strumien : TFileStream;
begin
strumien := TFileStream.Create('C:\plik.txt', fmOpenRead);
strumien.ReadComponent(Edit1);
strumien.Free;
Jak widzicie - w powyższej procedurze odczytującej zmienione zostały tylko dwie rzeczy. Gdybyśmy zamiast "fmOpenRead" zastosowali "fmCreate" bądź "fmOpenWrite" to system odwdzięczyłby się nam wyświetlając komunikat o błędzie. W rzeczywistości system domaga się praw odczytu i nie pozwoli nam odczytać z pliku jeśli mamy jedynie prawo zapisu, tak samo jak nie pozwoli nam zapisać do pliku, gdy mamy jedynie prawa odczytu. Lekarstwem na to jest "fmOpenReadWrite", która pozwala zapisywać i odczytywać plik - daje nam prawa odczytu i zapisu. Jest ona połączeniem "fmOpenRead" z "fmOpenWrite".
fmOpenReadWrite = fmOpenRead + fmOpenWrite
Nie pozwala nam natomiast tworzyć pliku, tak jak to robi "fmCreate".
W zasadzie moglibyśmy używać jedynie dwóch praw "fmCreate" i
"fmOpenReadWrite" (która pozwala odczytywać i zapisywać do pliku) i
racja. Jednak czasami będziesz musiał ograniczyć prawa dostępu do pliku np.
do samego czytania. Wtedy przyda Ci się znajomość wszystkich parametrów.
Podsumowując "fmCreate" używamy chcąc utworzyć (na przykład po
raz pierwszy) nie istniejący plik i zapisać do niego lub gdy chcemy wyczyścić
już istniejący plik. Następnie w zależności od sytuacji używamy "fmOpenRead",
bądź "fmOpenWrite", lub też "fmOpenReadWrite" by uzyskać
odpowiednie prawa dostępu. Sprzeczne prawa dostępu w stosunku do wykonywanej
operacji (np. zapisu) spowodują błąd.
Uruchomcie teraz program (F9) i sprawdźcie, czy działa.
Poznaliśmy już podstawowe prawa rządzące strumieniami i nauczyliśmy się zapisywać i odczytywać dane za pomocą strumieni. Ale strumienie danych potrafią znacznie więcej. Przekonacie się o tym w dalszych częściach. Na razie zostańmy przy tym (nie chcę Wam za bardzo mieszać). Umiemy już to wszystko, więc wypadałoby napisać jakiś program. Napiszemy prostą książkę adresową, która będzie przeprowadzała zapis i odczyt danych właśnie za pomocą strumieni - co wy na to? Fajnie? No to do dzieła:
Piszemy program
"Kosmetyczna obróbka programu"
- Z palety "Additional" wybieramy komponent o nazwie "ValueListEditor" i umieszczamy go na formie. Zmieniamy jego właściwość "Name" (w inspektorze obiektów - po lewej stronie) z "ValueListEditor1" na "lista"
- Teraz wybieramy z palety "Standard" komponentTLabeli umieszamy go na formie. Czynność tę powtarzamy jeszcze trzykrotnie, dopóki na formie nie znajdą się 4 komponentyTLabel.
- Potrzebne będą nam jeszcze 4 pola tekstowe typuTEdit. Wybieramy je z palety "Standard" i wstawiamy na formę. Czynność tę powtarzamy jeszcze trzykrotnie tak, aby na formie figurowały ostatecznie 4TEdit;).
- Teraz kolej na przyciski. Wybieramy z palety "Standard" przycisk i wstawiamy go na formę. Powtarzamy tę czynność jeszcze dwa razy, tak aby na formie widniały w sumie trzy przyciski.
- Ustawiamy wszystkie komponenty w sposób podany na rysunku (szczególnie
ważne jest ustawienie kontrolekTEdit- np.Edit1musi być polem na wpisanie
imienia,Edit2musi być polem na wpisanie nazwiska itd.):
- Zmieniamy właściwość "Caption" wszystkich komponentów - tak jak na obrazku (gdzie etykietaButton1to "Dodaj", dlaButton2to "Usuń", zaś dlaButton3- "Zakończ")
Trochę kodu
- Dwukrotnie klikamy na przycisk "Dodaj" (czyliButton1). Powoduje to wygenerowanie procedury odpowiadającej za zdarzenie - w tym wypadku kliknięcie.
- Kod procedury uzupełniamy następująco:
(* Dodanie do komponentu 'lista' Pierwsza kolumna - Edit1.Text+' "'+Edit3.Text+'" '+Edit2.Text czyli imię + " + nick + " + nazwisko Druga kolumna - Edit4.Text czyli e-mail *) lista.InsertRow(Edit1.Text + ' "' + Edit3.Text + '" ' + Edit2.Text, Edit4.Text, True);
- Z powrotem przechodzimy do formularza (F12) i tym razem dwukrotnie klikamy przycisk "Usuń" (Button2).
- Uzupełniamy jego kod następująco:
lista.DeleteRow(lista.Row);
Co spowoduje usunięcie aktualnie zaznaczonego wiersza z danymi. - Ponownie przechodzimy do formularza (F12) i dwukrotnie klikamy na przycisk "Zakończ"
- W nowo wygenerowanej procedurze piszemy bez większej filozofii:
Close;
- Zaraz, zaraz - pozostała nam jeszcze najważniejsza część przedstawienia - zapis i odczyt. Napiszemy program w taki sposób, aby automatycznie przy zamykaniu programu zapisywał zawartość komponentu "lista", a przy uruchamianiu wczytywał jego zawartość.
- W tym celu przechodzimy do formularza (F12) i klikamy dwukrotnie na wolne miejsce na formie.
- Pojawia się edytor kodu wraz z automatycznie wygenerowaną procedurą
"OnCreate". Instrukcje tej procedury będą wykonywane przy
uruchamianiu programu. Piszemy więc w procedurze:
var dane : TFileStream; begin // Jeśli plik nie istnieje to... If not FileExists('C:\baza.luk') Then // Zakończ wykonywanie tej procedury (nie mylić z wyjściem z programu) Exit; // Te instrukcje zostaną wykonane tylko wtedy gdy plik istnieje // Otwarcie strumienia do odczytu dane := TFileStream.Create('C:\baza.luk', fmOpenRead); // Odczytanie zawartości i ustawień komponentu lista.Strings.LoadFromStream(dane); // Zwolnienie strumienia dane.Free;
- OK. Teraz przydało by się zrobić procedurę, która by zapisywała
dane do pliku przy zamykaniu programu. Można to zrobić w procedurze
przycisku "Zakończ", ale tamte instrukcje wykonają się tylko
wtedy, gdy użytkownik kliknie na przycisk aby wyjść z programu. A jak użytkownik
zamknie program korzystając z krzyżyka? - całkowita klapa!
Co więc zrobić aby wykonywać jakieś instrukcje przy zamykaniu samego programu? Należy skorzystać z "Inspektora Obiektów". W tym celu: - Przechodzimy do formularza (F12). Zaznaczamy go - klikając nań raz (nie dwukrotnie!) lewym przyciskiem myszy. Przechodzimy do "Inspektora Obiektów", wybierając zakładkę "Events". Na tej zakładce znajdują się wszystkie zdarzenia obsługiwane przez zaznaczony komponent. Odszukujemy zdarzenie "OnClose" i klikamy dwukrotnie na białe pole obok. Zostaje wygenerowana procedura, której instrukcje będą wykonywane przy zamykaniu programu.
- Uzupełniamy ją następująco:
var dane : TFileStream; begin // Utworzenie strumienia (jeśli plik istnieje to go czyści ;) dane := TFileStream.Create('C:\baza.luk', fmCreate); // Zapisz komponent lista.Strings.SaveToStream(dane); // Zwolnij strumień. dane.Free;
- Dotarłeś wreszcie do końca. Sprawdź czy program działa.
Ochrona danych osobowych, że się tak wyrażę ;)
Jak pewnie zauważyłeś nie stosujemy tu typowych procedurWriteComponent()iReadComponent(), ale procedury własne komponentu, które zapisują dane do
strumienia. Zastosowanie tutaj np.ReadComponent()spowodowałoby błąd przy
odczycie.
Trochę namieszałem, więc spróbuje to teraz odkręcić. Jeśli nie wiesz,
kiedy masz stosować proceduryReadComponent()iWriteComponent(), a kiedy
procedury zapisu strumieni należące do danego komponentu - wyjaśniam prostą
regułą.
Jeśli będziesz się trzymał tej prostej zasady, na pewno nie będziesz miał problemów. Takie kontrolki jakRichEdit,ValueListEditoritp. mają własne procedury zapisu, zarówno do strumieni, jak i "normalnego" pliku np. tekstowego.
Zakończenie
Poznaliście właśnie strumienie typuTFileStream. Na razie oswójcie się z nowym kodem, potem będzie reszta. Większe programy, jakie pisaliśmy w tym artykule:
- Zapisywanie komponentu TEdit (2 kB)
- Książka adresowa (2 kB)
Pozostały nam jeszcze takie rzeczy, jak poleceniaSeek,CopyFromitd. Są to w miarę proste sprawy, ale również bardzo przydatne, więc nie mogę ich zostawić na pastwę losu. Postaram się o takie dopełnienie wiadomości w ostatniej części tego cyklu. A póki co - EEEDIS ;)





Podobne artykuly:
- Delphi - Pobieranie plików z Internetu
- Delphi - Strumienie - Wstęp
- Delphi - Strumienie - TStringStream
- Delphi - Strumienie - TMemoryStream
- Delphi - Strumienie - TResourceStream
- Delphi - Strumienie - Informacje dodatkowe
- KillAd
Skomentuj
Komentarze czytelników
-
- big_zygi
- Mon, 30 August 2010, 10:19
- Wybacz nie czytałem do konca tekstu ale jak sam napisałeś odczytywanie strumieniowe zapisanego pliku jest "głupie".
TAK. Bo jezeli nie wiesz to czytanie lub zapisywanie strumieniowe jednocześnie przesuwa położenie wskaźnika, więc aby błąd nie pojawial się w w twoim przykładzie należy wpisać dodatkowo jedną linijkę po zapisaniu do strumienia.
strumien.Position:=0;
i tera mozna odczytywać bez najmniejszego błędu.
-
- jd61
- Sun, 12 April 2009, 13:47
- OK, z wykorzystaniem strumienia do wczytania pliku tekstowego i wykorzystaniem tego odczytu - to jest jasne.Ale jak uruchomić odczytany ze strumienia dowolny plik z odpowiednim rozszerzeniem, do uruchomienia poprzez WINSHELL, bez tworzenia kopii tymczasowej na dysku. Chodzi mi o wykorzystanie możliwości zapisu dowolnego w bazie danych, odczycie poprzez stream i jego uruchomienie bezpośrednio ze zmiennej w programie?