Delphi od podstaw - rozdział 5
Rozdział 5 - operacje na plikach
Spis treści:
Aplikacje konsolowe
WyjÄ…tki
Selektywna obsługa wyjątków
SÅ‚owo kluczowe raise
Try, except oraz finally
Tworzenie własnych wyjątków
Obsługa wyjątków
Pliki tekstowe
Wczytywanie plików
Tworzenie plików
Dopisywanie do plików
Pliki amorficzne
Odczytywanie TAG'ów z plików mp3
Jeszcze trochÄ™ o plikach amorficznych
Pliki typowane
Podsumowanie
Delphi w prosty sposób umożliwia modyfikację, tworzenie
wczytywanie plików. Wystarczy nauczyć się paru podstawowych komend i to
wszystko. Ta umiejętność może Ci się bardzo przydać. Zacznijmy jednak od
innych równie ważnych rzeczy...
Aplikacje konsolowe
Konsola
- nie chodzi o konsole do grania. Jest to okienko MS-DOS, w którym mogą być
wyświetlane rezultaty wykonania jakichś funkcji.
W Delphi można oczywiście tworzyć takie aplikacje. Nie jest
to trudne. Pamiętasz jak w drugim rozdziale pisaliśmy bez wykorzystania
formularzy? Teraz też tak zrobimy. Zamknij edytor kodu - zamknięty zostanie
także formularz. Teraz z menu Project wybierz View Source. Zobaczysz kod pliku
DPR. Teraz na samej górze po słowie kluczowym program napisz taką linię:
{$APPTYPE CONSOLE}
Jest to tzw. dyrektywa. Nie myl tego z komentarzem. Mówi ona
kompilatorowi o opcji aplikacji. Jeżeli wpiszesz taką linię to Delphi wyświetli
program jako okno DOSa. Nie ma tu za wiele do opanowania. Istnieją dwie główne
komendy, które wpisują tekst do okna. Doprowadź program to takiej postacii:
program Project1;
{$APPTYPE CONSOLE}
uses
Windows;
begin
Writeln('To jest tekst, który zostanie wyświetlony w oknie DOS');
end.
Polecenie Writeln ( tak jak w Turbo Pascalu ) powoduje wyświetlenie
tekstu, który wpisany jest w nawiasie. Nic nadzwyczajnego. Możesz uruchomić
program. Niz nie zauważysz bo program zostanie otwarty, a później zamknięty.
W tym celu po komendzie Writeln napisz jeszcze jedną - Readln; Program będzie
wówczas czekał aż Ty naciśniesz klawisz Enter. Takie coś:
var
Tekst : String;
begin
Readln(Tekst);
Powoduje pobranie napisanego przez użytkownika tekstu, a następnie
przypisanie go do zmiennej Tekst.
Często można się spotkać z programami uruchamianymi wraz z
parametrem. Np. trzeba program uruchomić w oknie MS-DOS, a następnie nie dość,
że podać nazwę samego programu to jeszcze wpisać parametr, z jakim ma być
uruchamiany. Jako przykład podam kompilator PERL. Jeżeli chcemy sprawdzić,
czy napisany przez nas skrypt jest poprawny i nie zawiera błędów trzeba
napisać:
perl.exe -w skrypt.cgi
Powoduje to uruchomienie programu Perl.exe. Następnie program
sprawdza jaki parametr został wpisany - w tym wypadku -w. Kompilator wie teraz
że my chcemy, aby on sprawdził, czy skrypt jest poprawny i to wykonuje. Takie
rzeczy Ty także możesz zrobić w aplikacji konsolowej. Do tego służą
polecenia ParamCount ( sprawdza, czy program jest uruchomiony z parametrem ) i
ParamStr ( odczytuje wartość danego parametru ). Spójrz na poniższy przykład:
{
Copyright (c) 2001 - Adam Boduch
}
program param;
{$APPTYPE CONSOLE} { <-- aplikacja konsolowa }
uses
Windows;
begin
if ParamCount <> 0 then // Jeżeli parametr jest różny od zera - jest parametr
begin
if ParamStr(1) = '-imie' then // odczytaj parametr pierwszy
begin // jeżeli się zgadza...
Writeln('Na imiÄ™ mi Delphi'); //... wypisz odpowiedni tekst
Readln; // czkaj na reakcjÄ™
end;
end;
end.
Na samym początku program sprawdza, czy został uruchomiony z
parametrem. Później następuje odczytanie danego parametru. Jeżeli parametr
się zgadza to następuje wyświetlenie jakiegoś tekstu. Jeżeli parametr się
nie zgadza lub program nie został uruchomiony z parametrem to program nie
wykonuje siÄ™.
Teraz przyszedł czas na uruchomienie programu. Otwórz okno
MS-DOS. Teraz za pomocą poleceń cd, cd.. przejdź do katalogu, w którym
znajduje siÄ™ nasz program. Wpisz takÄ… liniÄ™ komend:
Teraz naciskasz Enter i... program się wykona. Wyświetli się
tekst. W programie możesz mieć o wiele więcej parametrów. Oto zmodyfikowana
wersja poprzedniego projektu:
begin
if ParamCount <> 0 then // Jeżeli parametr jest różny od zera - jest parametr
begin
if (ParamStr(1) = '-imie') and (ParamStr(2) = '-nazwisko') then // odczytaj parametr pierwszy
begin // jeżeli się zgadza...
Writeln('Na imiÄ™ mi Delphi'); //... wypisz odpowiedni tekst
Writeln('Na nazwisko Borland');
Readln; // czkaj na reakcjÄ™
end;
end;
end.
Tym razem program siÄ™ wykona tylko wtedy gdy podczas dwa
parametry - -imie oraz -nazwisko. Wpisz więc w linii komend okna DOS taki
tekst:
param.exe -imie -nazwisko
Program zostanie wykonany.
WyjÄ…tki
Uwierz mi, że wyjątki są bardzo przydatne przy programowaniu.
Wyobraź sobie taką sytuację: piszesz jakąś procedurę, w której pobierasz
tekst z komponentu Edit. W Edit musisz mieć cyfry. Co jeżeli do wykonania
procedury potrzebne Ci liczby z komponentu Edit, a użytkownik wpisze słowa? Wyświetlony
zostanie błąd Delphi o nieprawidłowej operacji. Właśnie dzięki wyjątkom
możesz takie sytuacje kontrolować - kontrolować sytuację, w której użytkownik
popełni błąd. Zareagować możesz różnie - przeważnie wyświetlając
odpowiednie okienko z informacjÄ….
Umieść na formie komponent Edit oraz komponent Button. Teraz
wygeneruj procedurÄ™ OnClick komponentu Button.
procedure TForm1.Button1Click(Sender: TObject);
begin
try
StrToInt(Edit1.Text);
except
ShowMessage('Hej, przecież masz wpisać cyfry, nie?');
end;
end;
Po słowie kluczowym try następuje wykonywanie poleceń. Teraz
program będzie "obserwowany", czyli tutaj następuje sprawdzanie, czy
nie ma błędu. Słowo try od angielskiego - spróbuj. W naszym przykładzie
program próbuje przekształcić tekst na cyfry. Jeżeli mu się udaje dokonać
konwersji - ok. Jeżeli nie zostają wykonane instrukcje znajdujące się po słowie
except, a przed słowem end. W naszym wypadku zostaje wyświetlona informacja z
tekstem. Ogólnie możesz przyjąć, że jeżeli w bloku try pojawią się błędy
to zostanÄ… wykonane operacje w bloku except.
Teraz zmodyfikujemy trochę nasz program View 1.0, który pisaliśmy
w poprzednim rozdziale. Była w nim procedura obslugująca otwarcie pliku
graficznego. Teraz możesz w komponencie OpenPictureDialog dodać nowy filtr:
Wszystkie pliki (*.*). Spowoduje to, że po wybraniu tego filtra w oknie będą
wyświetlane wszystkie pliki obojętnie jakiego typu. Załóżmy, że użytkownik
wybierze nie ten typ co trzeba? Np. plik z rozszerzeniem *.exe. Co wtedy? Delphi
wygeneruje standardową obsługę błędu - czyli komunikat. My możemy to zrobić
lepiej - oto zmodyfikowana wersja tej procedury:
procedure TMainForm.FileOpenClick(Sender: TObject);
begin
if OpenPictureDialog.Execute then // jeżeli okienko zostanie wyświetlone...
begin
ChildForm := TChildForm.Create(Self); //...stwórz okno Child
{ załaduj obrazek }
try // spróbuj załadować obrazek
ChildForm.Image.Picture.LoadFromFile(OpenPictureDialog.FileName);
except // w razie błędu...
on EInvalidGraphic do
raise Exception.Create('Błąd! Nie mogę załadować obrazka o niewłaściwym rozszerzeniu!');
end;
with ChildForm do
begin
{ dopasuj rozmiary obszaru roboczego do rozmiarów obrazka }
ClientWidth := Image.Picture.Width;
ClientHeight := Image.Picture.Height;
end;
ChildForm.Caption := OpenPictureDialog.FileName; // do tytułu okna przypisz wybrany plik
ChildForm.Show; // wyświetl okno
end;
end;
Tutaj zostanie wyświetlone nasze okno jeżeli załadowany
obrazek będzie niewłaściwego rozszerzenia. Możesz zauważyć tutaj
niezrozumiałe dla Ciebie słowa jak EInvalidGraphic, czy słowo kluczowe raise.
Exception.Create tworzy standardowe okno z informacjÄ…. Dodatkowo po prawej
stronie okna wyświetlona jest ikona symbolizująca błąd.
Selektywna obsługa wyjątków
Jeżeli wystąpi jakiś błąd to możesz napisać kod, który będzie
wyświetlał okno w zależności od rodzaju błędu. Przykładowo jeżeli format
pliku, który próbujesz otworzyć jest niewłaściwy wyświetli się jeden
komunikat, a jeżeli nastąpi jakiś inny błąd otwarcia pliku to wyświetli się
inny. Stąd w poprzednim kodzie słowo EInvalidGraphic. Selektywną obsługę
wyjątków zapisujemy z użyciem słów on oraz do. Oto przykład obsługi
dwóch wyjątków:
except // w razie błędu...
on EInvalidGraphic do
raise Exception.Create('Błąd! Nie mogę załadować obrazka o niewłaściwym rozszerzeniu!');
on EInvalidImage do
raise Exception.Create('Błąd związany z zasobami pliku! Nie mogę go odczytać!');
end;
Błąd EInvalidImage występuje wtedy gdy uszkodzone są zasoby
pliku graficznego i nie można go odczytać. Inne dość popularne błędy to:
BÅ‚Ä…d
Opis
błędu
EPrinter
Błąd związany z błędem podczas próby
drukowania.
ERegistryException
Wyjątek ten wiąże się z błędem
spowodowanym podczas edycji rejestru Windows lub plików INI.
EDivByZero
Występuje podczas próby dzielenia przez
0.
EOutOfMemory
Brak pamięci.
To oczywiście tylko niektóre błędy - jest ich znacznie więcej i więcej
przypadków możesz obsłużyć w swoim programie. Pytanie skąd wiedzieć jakie
są jeszcze możliwe do obsłużenia wyjątki? Albo z pomocy Delphi ( po
wczytaniu pomocy na temat jednego wyjątku są odnośniki na temat innych ) lub
w pliku Classes.pas znajdujÄ…cym siÄ™ w katalogu Sources.
Standardowo już jeżeli po słowie kluczowym do oprócz komunikatu o błędzie
będzie jeszcze inna instrukcja musisz wszystko wsiąść w słowa begin i end.
on EInvalidGraphic do
begin
raise Exception.Create('Błąd! Nie mogę załadować obrazka o niewłaściwym rozszerzeniu!');
(ActiveMDIChild as TChildForm).Close; // zamknięcie okna
end;
SÅ‚owo kluczowe raise
Jest to bardzo ważne słowo. Powoduje ono wyświetlenie komunikatu z błędem.
Zauważ, że zawsze je stosowałem podczas wyświetlania komunikatu (
Exception.Create ). Jeżeli napiszesz samo słowo raise; zakończone średnikiem
to program wyświetli standardowy komunikat o błędzie Windowsa.
Polecenia tego możesz urywać nie tylko po słowie except. Także w dowolnym
miejscu programu gdzie chcesz wyświetlić okno z informacją o błędzie.
if CosTamCosTam = TRUE then
raise Excception.Create('Aaaaaa! BÅ‚Ä…d');
Try, except oraz.... finally
Zamiast except funkcjonuje także słowo finally. Po tym słowie będą
wykonywane instrukcje bez względu, czy w programie wystąpi błąd, czy też
nie. Instrukcje te będą występowały ZAWSZE.
Klasa := TKlasa.Create;
try
{ jakieÅ› instrukcje klasy }
finally
Klasa.Free; // zwolnienie klasy
end;
W takim wypadku słowo finally stosuje się najczęściej. Teraz klasa będzie
zwalniana za każdym razem ( za każdym razem będzie zwalniana pamięć ) bez
względu na to, czy zaistnieją jakieś błędy, czy też nie.
Istnieje możliwość połączenia obydwu słów - except oraz finally:
Klasa := TKlasa.Create;
try
try
{ jakieÅ› instrukcje }
except
{ wyświetlenie błędu }
end;
finally
Klasa.Free; // zwolnienie klasy
end;
Tworzenie własnych wyjątków
Jest to dziecinnie łatwe. Deklarujesz po prostu klasę, która dziedziczy z
klasy Exception.
type
{ nowe nasze wyjÄ…tki }
ELowException = class(Exception);
EMiddleException = class(Exception);
EHighException = class(Exception);
Przyzwyczajaj się, że kolejną regułą jest to, że każdy wyjątek winien
rozpoczynać się od litery E. Teraz możesz np, generować takie wyjątek, czy
go obsługiwać:
raise ELowException.Create('Treść komunikatu');
Obsługa wyjątków
Możesz napisać swoją procedurę, która obsługiwać będzie wyjątki. W
praktyce odbywa się to tak, że wyjątek "przepływa" przez naszą
procedurę. Możesz z nim zrobić wszystko. Zaraz napiszemy przykładową
aplikacje podsumowujÄ…cÄ… wiedzÄ™ o wyjÄ…tkach.
Umieść na formularzu komponent TPopupMenu. ( paleta Standard ). Jest to
komponent - menu rozwijalne. Tworzenie nowych pozycji odbywa siÄ™ tak samo
jak w wypadku komponentu TMainMenu. Ja stworzyłem trzy pozycje, które
symbolizujÄ… wyjÄ…tki:
Teraz
na formie umieść komponent Button. Po naciśnięciu przycisku wyświetlone (
rozwinięte ) zostanie menu. Nie jest to skomplikowane. Oto kod procedurze
zdarzenia OnClick komponentu Button:
procedure TMainForm.btnErrorClick(Sender: TObject);
var
GetCursor : TPoint; // nowy typ danych
begin
GetCursorPos(GetCursor); // pobierz aktualnÄ… pozycjÄ™ kursora
pmPopupMenu.Popup(GetCursor.X, GetCursor.Y); // wyświetl menu
end;
Wykorzystałem tutaj nowy typ zmiennej - TPoint. W rzeczywistości jest to
rekord, który zawiera "w sobie" dwie zmienne - X i Y. Polecenie
GetCursorPos to procedura, która pobiera pozycje kursora i przypisuje ją do
zmiennej GetCursor. Tak więc GetCursor.X to pozycja X kursora, a GetCursor.Y to
pozycja Y kursora. To wszystko. pmPopupMenu to nazwa komponentu TPopupMenu.
Zawiera on metodę Popup, która powoduje rozwinięcie menu. Trzeba podać dwa
parametry - pozycję X oraz Y w którym menu ma się rozwinąć. Podajemy w tym
miejscu pobrane współrzędne. Tą procedurę już mamy.
Teraz poszczególnym pozycją w PopupMenu trzeba nadać właściwość Tag od
1 do 3. Właściwość Tag służy do niczego. Jest to dodatkowa metoda, którą
programista może wykorzystać jak chce. My napiszemy jedną procedurę dla
wszystkich pozycji i w zależności od tego jaka będzie właściwość Tag
wykonywana będzie określone zadanie. Oto jak wygląda teraz nasza klasa i wyjątki:
type
{ nowe nasze wyjatki }
ELowException = class(Exception);
EMiddleException = class(Exception);
EHighException = class(Exception);
TMainForm = class(TForm)
btnError: TButton;
pmPopupMenu: TPopupMenu;
pmiELowError: TMenuItem;
pmiEMiddleError: TMenuItem;
pmiEHighError: TMenuItem;
lblMessage: TLabel;
procedure btnErrorClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ procedura obsługi wyjątków }
procedure ProccException(Sender: TObject; E : Exception);
published
{ procedura obsługująca kliknięcia w poszczególne pozycje }
procedure PopupClick(Sender: TObject);
end;
Na samej górze oczywiście wyjątki. W sekcji private procedura, która obsługiwać
będzie przepływ wyjątków. W sekcji published procedura, która obsługiwać
będzie kliknięcia w pozycję w komponencie PopupMenu.
Oto procedura przepływu wyjątków:
procedure TMainForm.ProccException(Sender: TObject; E: Exception);
begin
{
Procedura obsługi wyjątku. Nie wyświetlaj błędu w okienku, ale wiadomość
o błędzie wyświetl na komponencie typu TLabel. Jeżeli błąd jest typu EHighException
to wyświetl dodatkowo komunikat.
}
lblMessage.Caption := E.Message;
if E.ClassType = EHighException then
MessageBox(Handle, 'Jejejej! Coś się źle dzieje!', 'Uwaga! Błąd', MB_OK + MB_ICONERROR);
end;
Zamiast wyświetlać informację o wyjątkach treść będzie wpisywana na
komponencie TLabel ( lblMessage ). Jak już zapewne się domyśliłeś E.Message
oznacza treść komunikatu. Następnie następuje sprawdzenie, czy typ wyjątku
to EHighException. Jeżeli tak to następuje wyświetlenie stosowanego
komunikatu. W okienku, które się wyświetli oprócz standardowego przycisku będzie
ikonka błędu. Tak, nie mówiłem o tym wcześniej, ale można takie bajer umieścić.
Zamiast MB_ICONERROR może być także:
MB_ICONWARNING
ostrzeżenie
MB_ICONINFORMATION
informacja
Oto cały kod programu:
{
Copyright (c) 2001 - Adam Boduch
}
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Menus;
type
{ nowe nasze wyjatki }
ELowException = class(Exception);
EMiddleException = class(Exception);
EHighException = class(Exception);
TMainForm = class(TForm)
btnError: TButton;
pmPopupMenu: TPopupMenu;
pmiELowError: TMenuItem;
pmiEMiddleError: TMenuItem;
pmiEHighError: TMenuItem;
lblMessage: TLabel;
procedure btnErrorClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ procedura obsługi wyjątków }
procedure ProccException(Sender: TObject; E : Exception);
published
{ procedura obsługująca kliknięcia w poszczególne pozycje }
procedure PopupClick(Sender: TObject);
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.btnErrorClick(Sender: TObject);
var
GetCursor : TPoint; // nowy typ danych
begin
GetCursorPos(GetCursor); // pobierz aktualnÄ… pozycjÄ™ kursora
pmPopupMenu.Popup(GetCursor.X, GetCursor.Y); // wyświetl menu
end;
procedure TMainForm.PopupClick(Sender: TObject);
begin
{
odczytaj wartość Tag pozycji menu, która została naciśnięta.
W zależności o wartości Tag wygeneruj odpowiedni komunikat błędu.
}
case (Sender as TMenuItem).Tag of
1: raise ELowException.Create('Nastąpił niegroźny błąd');
2: raise EMiddleException.Create('Taki sobie błąd. Trochę groźny. Zawiadom szefa');
3: raise EHighException.Create('Wołaj tego szefa do cholery!!!');
end;
end;
procedure TMainForm.ProccException(Sender: TObject; E: Exception);
begin
{
Procedura obsługi wyjątku. Nie wyświetlaj błędu w okienku, ale wiadomość
o błędzie wyświetl na komponencie typu TLabel. Jeżeli błąd jest typu EHighException
to wyświetl dodatkowo komunikat.
}
lblMessage.Caption := E.Message;
if E.ClassType = EHighException then
MessageBox(Handle, 'Jejejej! Coś się źle dzieje!', 'Uwaga! Błąd', MB_OK + MB_ICONERROR);
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
{ przypisz procedurę obsługi wyjątku }
Application.OnException := ProccException;
end;
end.
Nie omówiłem wcześniej dwóch procedur. W OnCreate następuje przypisanie
zdarzenia OnException do procedury, którą wcześniej napisaliśmy. W
procedurze PopupClick natomiast następuje "wyciągnięcie" właściwości
Tag z pozycji komponentu PopupMenu. Poszczególne pozycji w tym komponencie są
typu TMenuItem. Tak więc w zależności od właściwości Tag wykonywany
zostaje określony wyjątek.
Pliki tekstowe
Wczytywanie plików
Tak jak powiedziałem na początku tego rozdziału nie jest to nic nadzwyczajnego.
Najpierw należy napisać zmienną, która będzie wskazywała na plik. Robi się
to tak:
var
TF : TextFile;
Teraz musisz skojarzyć zmienną z plikiem:
AssignFile(TF, 'C:\Autoexec.bat');
W tym momencie możesz stworzyć plik na dysku ( ReWrite(TF); ), albo otworzyć
już istniejący ( Reset(TF); ).
Napiszmy prosty program. Umieść na formie komponent Memo - służy on do
przechowywania tekstów. Nazwij go Memo. Jego właściwość Align zmień na
alClient. We właściwości Font możesz zmienić czcionkę używaną przez
komponent. We właściwości Lines możesz edytować linie tekstu, które pojawią
się po uruchomieniu programu w tym komponencie. Możesz pozostawić to bez
zmian. Zmień tylko właściwość ScrollBars - zmień na ssVertical. Właściwość
ta określa jakie i czy paski przewijania będą wyświetlane.
Dobra, teraz wygeneruj procedurÄ™ OnCreate formy:
procedure TMainForm.FormCreate(Sender: TObject);
var
TF: TextFile; // zmienna wskazujÄ…ca na plik tekstowy
S : String; // łańcuch przechowujący kolejne linie tekstu
begin
Memo.Clear;
AssignFile(TF, 'C:\Autoexec.bat');
Reset(TF); // otwarcie pliku
while not Eof(TF) do
begin
Readln(TF, S); // odczytaj kolejne linie tekstu
Memo.Lines.Add(S);
end;
CloseFile(TF); // zamknięcie pliku
end;
Na samym początku należy wyczyścić zawartość Memo. Następnie następuje
otwarcie pliku. Funkcja Eof określa koniec pliku. Czyli w tej procedurze
wykonywana jest pętla dopóki program nie odnotuje zakończenia pliku. W pętli
następuje odczytanie kolejnych lini pliku i przypisanie ich do zmiennej S. Następnie
zmienna S dodawana jest do komponentu Memo. W rezultacie po uruchomieniu
programu w komponencie zobaczysz zawartość pliku Autoexec.bat. Na samym końcu
plik zostaje zamknięty poleceniem ( CloseFile ).
Inna sprawa, że cały ten kod dałoby się zastąpić jedną linią ( komendą
) VCL:
Memo.Lines.LoadFromFile('C:\Autoexec.bat');
Warto jednak umieć dokonywać operacji na plikach bez pomocy VCL.
Tworzenie plików
Tworzenie także nie jest trudne. Realizuje się to poleceniem ReWrite.
Zapisz natomiast przy pomocy polecenia Writeln.
var
TF: TextFile; // zmienna wskazujÄ…ca na plik tekstowy
begin
AssignFile(TF, 'C:\plik.txt');
try
ReWrite(TF); // utwórz plik
Writeln(TF, 'Cześć!'); // dopisz linię tekstu
Writeln(TF, 'Ten plik został właśnie stworzony. Co Ty na to?'); // dopisz kolejną
finally
CloseFile(TF); // zamknij plik
end;
end;
Jak widzisz wszystko wziąłem w linie try, finally. Tak samo to zadanie można
by było zrealizować za pomocą polecenia VCL: Memo.Lines.SaveToFile('C:\plik.txt');
ale w takim wypadku zapisana by była zawartość Memo.
Dopisywanie do plików
Dopisywanie na końcu realizuje się komendą Append. Ustawia ona kursor na
końcu pliku - wtedy można dokonywać zapisu treści.
var
TF : TextFile;
begin
AssignFile(TF, 'C:\plik.txt');
try
Append(TF);
Writeln(TF, ''); // jedna linia przerwy
Writeln(TF, 'Oto kolejna linia');
finally
CloseFile(TF);
end;
end;
Widzisz, że zamiast polecenia Reset zastosowałem Append. Jest to
konieczne - program ustawia kursor na samym końcu pliku i wtedy dopiero jest możliwe
dopisywanie. W kolejnej linii stworzyłem w pliku tekstowym pustą pozycję, aby
oddzielić tekst oryginalny od tego co dopisujemy w tejże procedurze. To właściwie
najważniejsze informacje dotyczące plików tekstowych - taka wiedza będzie Ci
wystarczać.
Pliki amorficzne
Ten rodzaj plików służy do operacji na bajtach. Umożliwia on
odczytywanie, wczytywanie bajtów pliku, ustawianie na odpowiednim miejscu gdzie
rozpoczynać się będzie odczyt. Komendy są bardzo podobne jak w przypadku
zwykłych plików tekstowych. Na samym początku musisz zadeklarować zmienną
typu File:
var
F : File;
Tak jak powiedziałem wcześniej pliki amorficzne mogą posłużyć do
operowania większymi porcjami bajtów lub pojedynczymi. Także, przy otwieraniu
takiego pliku należy podać porcję bajtów jaka będzie odczytywana:
Reset(F, 1);
Jak widzisz komenda otwierająca jest taka sama jak w przypadku plików
tekstowych - podajesz tylko o jeden parametr więcej. Jeżeli pominiesz drugi
parametr to Delphi nie potraktuje tego jako błąd, ale przyjmie w takim wypadku
wartość domyślną, czyli 128. Pliki amorficzne w przeciwieństwie do
tekstowych oferują parę dodatkowych, bardzo przydatnych funkcji. Możesz
bowiem pobrać rozmiar pliku w bajtach ( polecenie FileSize ), ustawić pozycję
pliku na odpowiedniej pozycji ( Seek ) oraz odczytać pozycje pliku ( FilePos ).
Przykładowo chcąc pobrać rozmiar pliku w bajtach piszesz:
var
F : File;
FSize : Integer; // przechowuje rozmiar pliku
begin
AssignFile(F, 'C:\plik.exe');
Reset(F, 1);
FSize := FileSize(F); // pobranie rozmiaru
ShowMessage('Rozmiar pliku wynosi: ' + IntToStr(FSize) + ' bajtów');
CloseFile(F);
end;
Pliki amorficzne nie posiadajÄ… za to polecenia Append ustawiajÄ…cego pozycje
do zapisu na końcu pliku. Można to łatwo ominąć dzięki funkcji Seek:
Seek(F, FileSize(F));
I tym sposobem ustawiasz pozycje do zapisu na samym końcu.
Odczytanie Tagu z pliku mp3
Osoby lubiące słuchać muzykę w mp3 zapewne wiedzą co to jest tag. Jeżeli
nie to wyjaśniam. Jest to specjalna informacja zapisana w pliku mp3, w której
znajdują się informacje o wykonawcy utworu, albumie, roku wydania itp. Właśnie
dzięki plikom amorficznym możemy tę informację "wyłuskać" z
pliku mp3. Potrzebna nam będzie wiedza na temat budowy samego formatu mp3.
Wiedzę tę możesz nabyć chociażby na stronie www.4programmers.net
gdzie znajdziesz informacje na ten temat budowy pliku mp3. Cały tag zajmuje 128
bajtów i znajduje się na samym końcu pliku mp3. Autorzy formatu mp3 udostępnili
informacje ile bajtów jest przeznaczonych na wykonawcę, tytuł itp.
Na samym początku będziesz musiał umieścić w sekcji Implementation taki
rekord:
{
Oto rekord, który zawiera elementy, które będziemy odczytywać z pliku mp3.
Każda zmienna rekordu ma przeznaczoną prawidłową ilość znaków na dany element.
}
type
TTag = packed record
ID: String[3]; // czy Tag istnieje?
Title : String[30]; // tytuł
Artist : String[30]; // wykonawca
Album : String[30]; // album
Year : String[4]; // rok wydania
Comment : String[30]; // komentarz
Genre : Byte; // typ - np. POP, Techno, Jazz itp.
end;
Do elementów tego rekordu będą przydzielane informacje. Zauważ, że każdy
element ma określoną max. ilość znaków ( bajtów - każdy znak to 1 bajt )
jaką może mieć. Pierwsza pozycja może mieć max. 3 znaki. Informuje ona, czy
Tag w pliku mp3 istnieje, czy też nie. Jeżeli istnieje i jest prawidłowo
zapisany to ta zmienna powinna mieć wartość "TAG". Na kolejne 3
elementy przydzielone jest 30 znaków. Na rok oczywiście 4 znaki; komentarz to
także 30 znaków. I ostatnia pozycja to jeden bajt ( w formie cyfry ), który
określa typ utworu. Np. jeżeli element ma wartość 0 to utwór jest Blues'owy.
Żeby tę informacje móc odczytać w programie musisz ( także w sekcji
Implementation ) zadeklarować tablicę:
const
{ oto tablica zawierająca typy utworów }
Genre : array[0..79] of ShortString = (
('Blues'), ('Classic Rock'), ('Country'), ('Dance'), ('Disco'),
('Funk'), ('Grunge'), ('Hip-Hop'), ('Jazz'), ('Metal'), ('New Age'),
('Oldies'), ('Other'), ('Pop'), ('R&B'), ('Rap'), ('Reggae'),
('Rock'), ('Techno'), ('Industrial'), ('Alternative'), ('Ska'),
('Death Metal'), ('Pranks'), ('Soundtrack'), ('Euro-Techno'), ('Ambient'),
('Trip-Hop'), ('Vocal'), ('Jazz+Funk'), ('Fusion'), ('Trance'),
('Classical'), ('Instrumental'), ('Acid' ), ('House'), ('Game'),
('Sound Clip'), ('Gospel'), ('Noise'), ('AlternRock'), ('Bass'),
('Soul'), ('Punk'), ('Space'), ('Meditative'), ('Instrumental Pop'),
('Instrumental Rock'), ('Ethnic'), ('Gothic'), ('Darkwave'),
('Techno-Industrial'), ('Electronic'), ('Pop-Folk'), ('Eurodance'),
('Dream'), ('Southern Rock'), ('Comedy'), ('Cult'), ('Gangsta'),
('Top 40'), ('Christian Rap'), ('Pop/Funk'), ('Jungle'), ('Native American'),
('Cabaret'), ('New Wave'), ('Psychadelic'), ('Rave'), ('Showtunes'),
('Trailer'), ('Lo-Fi'), ('Tribal'), ('Acid Punk'), ('Acid Jazz'),
('Polka'), ('Retro'), ('Musical'), ('Rock & Roll'), ('Hard Rock')
);
Na formie umieść 6 komponentów typu Edit, jeden przycisk oraz komponent
OpenDialog. Potrzebna będzie jedna procedura:
procedure TMainForm.btnOpenClick(Sender: TObject);
var
mpFile : File;
Buffer : array[1..128] of char; // tutaj przechowywane będą wszystkie dane
Tag : TTag; // zmienna wskazujÄ…ca na rekord
begin
if OpenDialog.Execute then
begin
AssignFile(mpFile, OpenDialog.FileName);
Reset(mpFile, 1); // otwórz plik
Seek(mpFile, FileSize(mpFile) -128); // ustaw na ostatnich 128 bajtach
BlockRead(mpFile, Buffer, SizeOf(Buffer)); // odczytaj bajty i przypisz do zmiennej Buffer
CloseFile(mpFile); // można już zamknąć plik
with Tag do
begin
{ tutaj następuje rozdzielenie tekstu i przypisanie odpowiednim elementom rekordu }
ID := Copy(Buffer, 1, 3);
Title := Copy(Buffer, 4, 30);
Artist := Copy(Buffer, 34, 30);
Album := Copy(Buffer, 64, 30);
Year := Copy(Buffer, 94, 4);
Comment := Copy(Buffer, 98, 30);
Genre := Ord(Buffer[128]);
end;
if TAG.ID = 'TAG' then // czy wougle tak został odczytany?
begin
edtTitle.Text := Tag.Title;
edtArtist.Text := Tag.Artist;
edtAlbum.Text := Tag.Album;
edtYear.Text := Tag.Year;
edtComment.Text := Tag.Comment;
edtGenre.Text := Genre[Tag.Genre];
end else Application.MessageBox('Tag w tym pliku mp3 NIE ISTNIEJE!', 'Nie istnieje...', MB_OK + MB_ICONINFORMATION);
end;
end;
Po naciśnięciu przycisku otwierane będzie okno, w który można będzie
wybrać dowolny plik mp3. Kluczowym elementem w tej procedurze stanowi polecenie
Seek:
Seek(mpFile, FileSize(mpFile) -128);
Następuje tutaj przesunięcie na sam koniec pliku, a następnie odjęcie od
tego 128 bajtów co w rezultacie daje przesunięcie na ostatnie 128 bajtów.
Kolejne polecenie powoduje odczytanie wszystkich 128 bajtów i przypisanie do
zmiennej Buffer.
BlockRead(mpFile, Buffer, SizeOf(Buffer));
Zmienna Buffer to tablica składająca się ze 128 elementów typu Char (
Char może zawierać tylko jeden bajt ( znak )). Kolejne polecenie mają za
zadanie porozdzielać tablice Buffer na poszczególne elementy rekordu -
dokonuje to polecenie Copy. Pierwszy parametr to oczywiście zmienna, której
dotyczyć będzie operacja. Kolejne to miejsce od którego dokonywane będzie
"wycinanie" liter. Ostatnie to ilość bajtów do wycięcia. Rezultat
wykonania tej operacji przypisywany jest do zmiennej. Tak po kolei aż dojdziemy
to elementu Genre. Zastosowałem tu funkcję Ord, która zamienia literę na
cyfrę. Polecenie Ord jest przydatne do określania znaków ASCII. Jeżeli
chcesz się dowiedzieć jaki jest numer ASCII danego znaku to piszesz Ord('A').
Dobrze, zboczyłem trochę z tematu. Odczytujemy typ utworu, ale trzeba go
przedstawić w formie słownej. Korzystamy tutaj z tablicy, która wcześniej
napisaliśmy. Jeżeli już wszystkie elementy są przypisane do rekordu trzeba
jeszcze wyświetlić wszystko w komponentach. Nim to nastąpi trzeba sprawdzić,
czy Tag dało się odczytać - jeżeli tak to w zmiennej ID będzie wartość
'TAG'.
Oczywiście źródła tego programu są dołączone do książki. Oto cały
program w działaniu:
Jeszcze trochÄ™ o plikach amorficznych...
Przy okazji budowania aplikacji odczytującej tag plików mp3
zastosowałem polecenie BytesRead. Powodowało to odczytanie bajtów. Jeżeli można
odczytać to można także zapisać i czyni to procedura BytesWrite. Zaraz
napiszemy procedurę odpowiedzialną za kopiowanie plików z jednego do
drugiego. W rzeczywistości będziemy kopiowali bajty pliku - z jednego do
drugiego porcjami 500 bajtowymi. Najpierw przypatrz się tej procedurze, a później
ją omówię:
procedure TForm1.Button1Click(Sender: TObject);
var
Src, Dst: File;
Buff : array[0..500] of char; // kopiowanie bezie się odbywało porcjami 500 bajtowymi
Read : Integer;
begin
AssignFile(Src, 'C:\101.jpg');
AssignFile(Dst, 'C:\202.jpg');
try
Reset(Src, 1); // otwórz plik
try
Rewrite(Dst, 1); // stwórz plik, który będzie skopiowany
repeat
BlockRead(Src, Buff, SizeOf(Buff), Read); // odczytaj pierwszą porcję bajtów
if Read > 0 then // jeżeli odczytano te bajty i przypisano zmiennej Buff...
BlockWrite(Dst, Buff, Read); //...zapisz je do pliku - przeznaczenia
until Read = 0; // dopóki program nie odczyta 0 bajtów
finally
CloseFile(Dst);
end;
finally
CloseFile(Src);
end;
end;
Trzeba było zastosować tutaj pętle, która będzie wykonywana
w zależności od rozmiaru pliku - im plik większy tym pętla wykonywana będzie
dłużej. Jeżeli plik, który kopiujemy byłby mały ( do 500 bajtów ) to nie
trzeba by było stosować nawet pętli - wszystko dałoby się przypisać do
tablicy i zapisać do kolejnego pliku. Ale w naszym wypadku tablica ma tylko 500
bajtów, a jeżeli plik ma 1 kB to nie można pliku skopiować - trzeba
zastosować pętle. Oczywiście można by było zastosować tablice o wielkości
1024 bajtów ( 1 kB ) i znowu nie stosować pętli no, ale ile tak można? Co jeśli
plik będzie zajmował 1 MB? Będziemy wtedy tworzyć tak dłużą tablice? Nie,
trzeba to zrobić z wykorzystaniem pętli.
Polecenie BlockRead ma w naszym wypadku 4 parametry - ostatni
jest opcjonalny. To do zmiennej Read przypisana zostanie ilość bajtów, która
będzie odczytana. Następnie należy sprawdzić, czy w zmiennej tej znajduje się
wartość większa od 0. Jeżeli nie to znaczy, że cały plik został już
skopiowany. No i na końcu polecenie BlockWrite, które zapisuje do pliku
zawartość zmiennej buff.
Pliki typowane
Pliki typowane są tym co najbardziej lubię. Służą one
bowiem do zapisywania całych rekordów do pliku. Przypatrz się temu rekordowi:
{ w tym rekordzie przechowywane będą dane }
TDatBase = packed record
Name : String[30]; // imiÄ™
Mail : String[30]; // e-mail
Number: Int64; // telefon
end;
Dla każdej pozycji określona jest max. ilość znaków. Z wyjątkiem
pozycji ostatniej, która przechowywać będzie numer telefonu ( liczbę ). Cały
ten rekord można zapisać do pliku, a następnie odczytać jego poszczególne
pozycje. Oto jak wygląda plik, w którym zapisane są dwa takie rekordy:
Jan Kowalski h Ló2
kow@serwer.pl r5½Ä„
Z›‡* Krzysztof Nowak h Ló2 nowakk@polska.pl5½Ä„
Ä…5
Obsługa takich plików także nie powinna sprawić Ci problemów.
W przypadku plików typowanych, amorficznych oraz tekstowych komendy są prawie
takie same. W tym rozdziale napiszemy aplikację, która będzie bazą danych.
Doprowadź formularz do takiej postaci:
Oto jak powinna wyglądać sekcja Interface:
{
Copyright (c) Adam Boduch 2001
}
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TMainForm = class(TForm)
GroupBox1: TGroupBox;
lblName: TLabel;
edtName: TEdit;
lblMail: TLabel;
edtMail: TEdit;
lblTel: TLabel;
edtTel: TEdit;
lblRecords: TLabel;
cmCounts: TComboBox;
btnAdd: TButton;
procedure btnAddClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure cmCountsChange(Sender: TObject);
private
procedure ShowRecords;
procedure LoadRecord(Num : Longint);
end;
{ w tym rekordzie przechowywane będą dane }
TDatBase = packed record
Name : String[30]; // imiÄ™
Mail : String[30]; // e-mail
Number: Int64; // telefon
end;
const DatName = 'data.dbt'; // w tym pliku przechowywane będą dane
var
MainForm: TMainForm;
implementation
{$R *.DFM}
Na początek napiszemy procedurę, która wykonywana będzie po naciśnięciu
przycisku. Będzie najłatwiejsza i powodować będzie oczywiście dodawanie
nowych rekordów do pliku.
procedure TMainForm.btnAddClick(Sender: TObject);
var
DatBase : file of TDatBase; // plik typowany
DB : TDatBase;
FSize : Integer;
begin
AssignFile(DatBase, DatName);
if not FileExists(DatName) then // jeżeli plik nie istnieje...
ReWrite(DatBase){ stworz go } else ReSet(DatBase); // w przeciwnym wypadku - otwórz
FSize := FileSize(DatBase); // odczytaj rozmiar pliku
if FSize > 0 then // jeżeli cos już w nim jest zapisane...
Seek(DatBase, FSize); // przesuń na sam koniec
{ przypisz wszystkie dane z komponentów do rekordu }
DB.Name := edtName.Text;
DB.Mail := edtMail.Text;
DB.Number := StrToInt(edtTel.Text);
Write(DatBase, DB); // zapisz rekord do pliku
CloseFile(DatBase); // zamknij
ShowRecords; // odśwież listę rekordów
end;
Zwróć uwagę na deklaracje zmiennych. W przypadku plików typowanych należy
stosować sekwencję file of. Deklarujemy nową zmienną DatBase, która będzie
zmienną plikową, ale jeżeli ma to być zmienna typowana to musi wskazywać na
rekord. Pierwsze trzy linijki sprawdzajÄ…, czy plik data.dbt istnieje (
przypominam, że data.dbt podstawiona jest pod stałą DatName ) - jeżeli tak
to ją otwiera - jeżeli nie tworzy nowy plik. Każdy rekord dopisywany będzie
na samym końcu pliku. Następnie do rekordu TDatBase przypisane zostają dane z
komponentów. Później kluczowe słowo - Write, które dopisuje rekord do
pliku. Na końcu następuje wywołanie procedury ShowRecords, która odczytuje
ile jest w pliku rekordów. Oto ta procedura:
procedure TMainForm.ShowRecords;
var
DatBase : file of TDatBase;
I : Integer;
begin
AssignFile(DatBase, DatName);
if not FileExists(DatName) then Exit else
Reset(DatBase);
cmCounts.Clear; // czyść listę rekordów, które są w pliku
{ odczytaj ilość rekordów i wczytaj do listy }
for I := 0 to FileSize(DatBase) -1 do
cmCounts.Items.Add('Rekord nr ' + IntToStr(i+1));
lblRecords.Caption := 'Ilość rekordów: ' + IntToStr(i); // na komponencie wyświetl ilość
CloseFile(DatBase);
end;
Do komponentu TComboBox zostają dopisane linie, które symbolizują kolejne
rekordy. Gdy użytkownik kliknie na którąś z linii to cały rekord zostaje załadowany
do komponentów. Cóż, na dole w komponencie Label wyświetlony jest także
napis, który mówi o ilości rekordów w pliku.
W końcu ostatnia procedura, która ładuje cały rekord ( jego pozycje ) do
komponentów.
procedure TMainForm.LoadRecord(Num: Integer);
var
DatBase : file of TDatBase;
DB : TDatBase;
begin
AssignFile(DatBase, DatName);
Reset(DatBase);
Seek(DatBase, Num); // przesuń na rekord, który chcesz odczytać
Read(DatBase, DB); // odczytaj do rekordu
{ przypisz dane z rekordu do komponentów }
with DB do
begin
edtName.Text := Name;
edtMail.Text := Mail;
edtTel.Text := IntToStr(Number);
end;
CloseFile(DatBase);
end;
Procedura posiada jeden parametr, który symbolizuje rekord, który zostanie
załadowany z pliku do poszczególnych kontrolek typu TEdit. Po prostu pozycja
pliku zostaje ustalona na rekordzie, który chcemy odczytać. Następnie za
pomocą polecenia Read przypisujemy rekord z pliku do rekordu TDatBase. Końcówka
jest już prosta bo przypisanie danych z rekordu do komponentów.
Oto cały kod programu:
{
Copyright (c) Adam Boduch 2001
}
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TMainForm = class(TForm)
GroupBox1: TGroupBox;
lblName: TLabel;
edtName: TEdit;
lblMail: TLabel;
edtMail: TEdit;
lblTel: TLabel;
edtTel: TEdit;
lblRecords: TLabel;
cmCounts: TComboBox;
btnAdd: TButton;
procedure btnAddClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure cmCountsChange(Sender: TObject);
private
procedure ShowRecords;
procedure LoadRecord(Num : Longint);
end;
{ w tym rekordzie przechowywane będą dane }
TDatBase = packed record
Name : String[30]; // imiÄ™
Mail : String[30]; // e-mail
Number: Int64; // telefon
end;
const DatName = 'data.dbt'; // w tym pliku przechowywane będą dane
var
MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.btnAddClick(Sender: TObject);
var
DatBase : file of TDatBase; // plik typowany
DB : TDatBase;
FSize : Integer;
begin
AssignFile(DatBase, DatName);
if not FileExists(DatName) then // jeżeli plik nie istnieje...
ReWrite(DatBase){ stwórz go } else ReSet(DatBase); // w przeciwnym wypadku - otwórz
FSize := FileSize(DatBase); // odczytaj rozmiar pliku
if FSize > 0 then // jeżeli cos już w nim jest zapisane...
Seek(DatBase, FSize); // przesuń na sam koniec
{ przypisz wszystkie dane z komponentów do rekordu }
DB.Name := edtName.Text;
DB.Mail := edtMail.Text;
DB.Number := StrToInt(edtTel.Text);
Write(DatBase, DB); // zapisz rekord do pliku
CloseFile(DatBase); // zamknij
ShowRecords; // odśwież listę rekordów
end;
procedure TMainForm.LoadRecord(Num: Integer);
var
DatBase : file of TDatBase;
DB : TDatBase;
begin
AssignFile(DatBase, DatName);
Reset(DatBase);
Seek(DatBase, Num); // przesuń na rekord, który chcesz odczytać
Read(DatBase, DB); // odczytaj do rekordu
{ przypisz dane z rekordu do komponentów }
with DB do
begin
edtName.Text := Name;
edtMail.Text := Mail;
edtTel.Text := IntToStr(Number);
end;
CloseFile(DatBase);
end;
procedure TMainForm.ShowRecords;
var
DatBase : file of TDatBase;
I : Integer;
begin
AssignFile(DatBase, DatName);
if not FileExists(DatName) then Exit else
Reset(DatBase);
cmCounts.Clear; // czyść listę rekordów, które są w pliku
{ odczytaj ilość rekordów i wczytaj do listy }
for I := 0 to FileSize(DatBase) -1 do
{ rekordy liczone są od 0 - my wyświetlimy cyfrę 1 zamiast 0, itd.}
cmCounts.Items.Add('Rekord nr ' + IntToStr(i+1));
lblRecords.Caption := 'Ilość rekordów: ' + IntToStr(i); // na komponencie wyświetl ilość
CloseFile(DatBase);
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
ShowRecords; // wyświetl rekordy zapisane w pliku
end;
procedure TMainForm.cmCountsChange(Sender: TObject);
begin
// załaduj rekord, który został wybrany z listy
// właściwość ItemIndex komponentu określa numer pozycji, która została kwiknięta
LoadRecord(cmCounts.ItemIndex);
end;
end.
Podsumowanie
Ten rozdział miał być zdominowany przez pliki. Nie udało się do końca.
Na samym początku słowo o aplikacjach konsolowych, później o wyjątkach.
Mniej więcej w połowie dokumentu zająłem się omawianiem plików i
przerobiliśmy pliki tekstowe, amorficzne oraz typowane. Z plikami możesz
pracować już spokojnie. Mam nadzieje, że przy okazji tego rozdziału nauczyłeś
się czegoś i ten rozdział Cię zainteresował bo często się to przydaje, a
trudne nie jest...
Wyszukiwarka
Podobne podstrony:
dsg roz05haasPl roz05ig roz05więcej podobnych podstron