Programowanie w języku Delphi

Pliki

czym byłby język programowania bez możliwości obsługi plików? Nie tylko tekstowych, ale także innych. Pliki mogą posłużyć do przechowywania różnych informacji dotyczących programu, do czego tylko sobie życzy programista. Delphi oczywiście udostępnia odpowiednie funkcje, o czym zaraz się przekonasz. I nie będą to tylko operacje na plikach tekstowych, ale także pliki amorficzne, typowane i strumieniowe. Co to takiego? Czytaj dalej ten artykuł.

Spis treści

     1 Pliki tekstowe
          1.1 Otwieranie, zamykanie, tworzenie
          1.2 Edycja
     2 Pliki typowane
          2.1 Tworzenie plików typowanych
          2.2 Edycja
     3 Pliki strumieniowe
          3.1 Przykład zastosowania strumieni: wyciąganie tagu z pliku mp3
     4 Pliki amorficzne
     5 Obsługa plików w .NET


Pliki tekstowe


Do tego celu służy typ danych - TextFile. Pliki tekstowe jak sama nazwa mówi są strukturą danych zawierającą tekst, w których ogranicznikiem każdej linii jest znak ASCII - #13.  

Otwieranie, zamykanie, tworzenie


Na samym początku wymagane jest skojarzenie ścieżki pliku ze zmienną typu TextFile. Do tego celu służy polecenie AssignFile. Po jej wykonaniu uzyskujemy wypełnioną strukturę (rekord) typu FileRec do danego pliku. Korzystać z pliku możemy od momentu otwarcia pliku za pomocą Reset/ReWrite. Trzeba jednak po tym zadecydować, czy plik ma być otwierany tylko do odczytu, czy także do zapisu. Po zakończeniu korzystania z plików należy zwolnić wszelkie zasoby, zamykając plik poleceniem CloseFile. Może to więc wyglądać tak:

Var
  TF : TextFile;
Begin
  AssignFile(TF, 'C:\Plik.txt');
  Reset(TF);
{ Jakieś operacje na pliku }
  CloseFile(TF);
End;


Jak więc widzisz przy poleceniu AssignFile pierwszy parametr musi być uchwytem pliku, czyli zmienną typu TextFile. Drugi parametr natomiast to ścieżka do pliku tekstowego. Podczas zamykania pliku podawany jest tylko jego uchwyt. Jeżeli jednak uruchomisz taki program i wykonana zostanie ta procedura to wystąpi błąd I/O Error 103. Jest to błąd wejścia/wyjścia. W tym wypadku spowodowany jest tym, że nie dokonujemy żadnych operacji na pliku. Rozwiązaniem będzie otwarcie pliku  do zapisu. Realizuje to polecenie ReWrite. Po jego wywołaniu plik zostanie otwarty do zapisu (jeżeli będzie zawartość - zostanie wyczyszczona) lub jeżeli nie istnieje to zostanie utworzony. Błąd 103 w tym wypadku był spowodowany tym, że zastosowaliśmy polecenie CloseFile, które zamykało plik, który nie został wcześniej otworzony, ani do odczytu, ani do zapisu...

W przypadku, gdy plik istnieje i chcemy tylko z niego odczytać informacje należy zastosować procedurę Reset podając w niej uchwyt do pliku do otwarcia. Jeżeli plik nie istnieje to na ekranie ujrzysz błąd File not found. Myślę, że tłumaczenie do tego komunikatu nie jest konieczne - po prostu trzeba stworzyć plik. Możesz zastosować w takim wypadku polecenie FileExists z modułu SysUtils, które sprawdza, czy podany plik istnieje (funkcja zwraca TRUE), czy też nie (zwraca FALSE). Kod może więc wyglądać tak:

procedure TForm1.Button1Click(Sender: TObject);
Var
  TF : TextFile;
Begin
  AssignFile(TF, 'C:\Plik.txt');
  if Not FileExists('C:\Plik.txt') Then // Sprawdzenie, czy plik istnieje
  Begin
    ReWrite(TF); // Jeżeli nie - stwórz
    ShowMessage('Plik nie istnieje - już utworzono!');
  End Else
  Begin
    Reset(TF); // jeżeli tak - otwórz do odczytu
    ShowMessage('Plik istnieje - otwarto...');
  End;
  CloseFile(TF);
End;


Po zastosowaniu takiego kodu plik w razie konieczności zostanie stworzony - jeżeli już istnieje to tylko otwarty do odczytu. Czasem jednak zajdzie potrzeba otwarcia pliku do zapisu, ale nie można w tym celu użyć polecenia ReWrite, gdyż wyczyści ono poprzednią zawartość pliku. Dlatego wymyślono polecenie Append, które ustawia pole do zapisu na końcu pliku. Gdy zastosujesz je zamiast polecenia ReWrite to otworzy ono plik do zapisu, tyle, że nie wyczyści poprzedniej zawartości pliku tekstowego.

Częstym błędem programistów jest otwarcie pliku przy pomocy polecenia Reset, które jak wiadomo otwiera plik do odczytu. Później próbują oni zapisać do takiego pliku nowe linie co owocuje błędem I/O Error 105. W takim wypadku należy plik otwierać za pomocą ReWrite lub Append.

Opis poszczególnych numerów błędów We/Wy:
100 : Disk read error (Błąd odczytu z dysku)
101 : Disk write error (Błąd zapisu na dysku)
102 : File not assigned (Plik nie został powiązany ze zmienną plikowa)
103 : File not open (Plik nie jest otwarty)
104 : File not open for input (Plik nie jest otwarty do odczytu)
105 : File not open for output (Plik nie jest otwarty do zapisu)
106 : Invalid numeric format (Błędny format numeryczny)
 

Edycja


Każdej osobie korzystającej z Turbo Pascala znane są funkcje ReadLn i WriteLn, czy jak kto woli Read i Write. Pobierają one lub wyświetlają na ekranie znaki. W Delphi te funkcje zapisują lub odczytują pewne informacje. Oto przykładowy kod źródłowy programu, który zapisuje do pliku pewne informacje:

procedure TForm1.Button1Click(Sender: TObject);
Var
  TF : TextFile;
Begin
  AssignFile(TF, 'C:\Plik.txt');
  ReWrite(TF);
  Writeln(TF, 'To jest tekst do zapisania...');
  CloseFile(TF);
End;


Pierwszym parametrem tego polecenia jest uchwyt pliku, a drugim tekst, który ma być do niego zapisany. W Delphi możesz stosować dwa rodzaje polecenie - Write lub WriteLn. To drugie zapisuje tekst do pliku i na końcu tekstu wstawia znak nowej linii. Polecenie Write natomiast tego nie robi. Możesz to sam sprawdzić:

procedure TForm1.Button1Click(Sender: TObject);
var
  TF : TextFile;
begin
  AssignFile(TF, 'C:\plik.txt');
  ReWrite(TF);
  Write(TF, 'Ta linia nie będzie zakończona znakiem nowej linii...');
  Writeln(TF, 'Ta natomiast będzie...');
  Write(TF, 'Ta także nie będzie zakończona znakiem nowej linii...');
  CloseFile(TF);
end;


Należy w tym miejscu wspomnieć, że specyfikacja języka Delphi nakazuje użycie procedury Flush tuż przed procedurą CloseFile. Wielu programistów zapomina o tym, a taka konstrukcja zabezpiecza nas przed wypadkiem, w którym część danych nie została "przepisana" z bufora do zewnętrznego pliku, a my już zamknęliśmy plik. Procedura Flush zrzuca nam dane z bufora do zewnętrznego pliku i gwarantuje, że wszystkie dane zostaną poprawnie zapisane do pliku. Jako jej parametr podajemy uchwyt pliku do którego zapisujemy dane, w naszym wypadku jest to TF.
Jak łatwo się domyślić, instrukcja Flush pracuje jedynie w przypadkach, gdy plik jest otworzony do zapisu (poprzez komendy ReWrite lub Append), w innych przypadkach (gdy plik otworzony jest otworzony do odczytu) niepotrzebne użycie Flush nie powoduje błędu.
Całkowicie prawidłowy zapis do pliku wygląda więc następująco:

procedure TForm1.Button1Click(Sender: TObject);
Var
  TF : TextFile;
Begin
  AssignFile(TF, 'C:\Plik.txt');
  ReWrite(TF);
  Write(TF, 'Ta linia nie będzie zakończona znakiem nowej linii...');
  Writeln(TF, 'Ta natomiast będzie...');
  Write(TF, 'Ta także nie będzie zakończona znakiem nowej linii...');
  Flush(TF);
  CloseFile(TF);
End;


Po wykonaniu takiego kodu plik 'Plik.txt' będzie miał taką zawartość:
 
Ta linia nie będzie zakończona znakiem nowej linii...Ta natomiast będzie...
Ta także nie będzie zakończona znakiem nowej linii...


Czyli zgodnie z naszymi oczekiwaniami. Teraz zajmijmy się odczytaniem takiego pliku. Służy do tego procedura Readln. W pierwszym jej parametrze trzeba podać standardowo uchwyt pliku, a w drugim zmienną do której dany tekst zostanie przypisany. A procedura ta odczytuje jedną linię z pliku tekstowego.

Var
  TF : TextFile;
  S : String;
Begin
  AssignFile(TF, 'C:\plik.txt');
  Reset(TF);
  Readln(TF, S); // Odczytanie linii...
  ShowMessage(S); // Wyświetlenie jej
  CloseFile(TF);


Po wykonaniu takiego kodu na ekranie zobaczysz okienko: Ta linia nie będzie zakończona znakiem nowej linii...Ta natomiast będzie.... Lecz taki kod odczytuje tylko pierwszą linię z pliku tekstowego. Co zrobić, aby odczytać cały plik? Da się to zrobić w połączeniu z funkcją Eof, pętlą while. Funkcja Eof bowiem czyta plik tekstowy aż napotka na jego koniec. Najpierw przypatrz się przykładowej procedurze:

Var
  TF : TextFile;
  S : String;
Begin
  AssignFile(TF, 'C:\Plik.txt');
  Reset(TF);
  While Not Eof(TF) Do
  Begin
    Readln(TF, S);
    ListBox1.Items.Add(S);
  End;
  CloseFile(TF);


Otóż funkcja Eof zwraca TRUE jeżeli cały plik został odczytany i nastąpił koniec pliku - w przeciwnym wypadku zwraca FALSE. I tak pętla jest wykonywana dopóki nie napotka na koniec pliku. W pętli natomiast kolejne linie są odczytywane i dodawane do komponentu ListBox1, który powinieneś umieścić na formie.

Pliki typowane


Ten typ plików bardzo często nazywa się także plikami rekordowymi. Jest to specyficzny typ danych idealnie nadaje się do przechowywania danych. Można bowiem przy ich pomocy zapisywać do pliku całe rekordy danych. Wraz z plikami typowanymi możemy używać dodatkowych procedur, które są bardzo przydatne, ale o tym w dalszej części tego rozdziału. Dostęp do plików typowanych różni się w stosunku do zwykłych plików tekstowych. Co prawda komendy są takie same, ale dostęp do pliku może nastąpić w każdym momencie. W przypadku plików tekstowych jeżeli otwarłeś je tylko do odczytu (poleceniem Reset) to nie mogłeś następnie do pliku dopisać żadnych linii - w przypadku plików typowanych natomiast jest to możliwe.  

Tworzenie plików typowanych


Na samym początku jeżeli chcesz korzystać z tego rodzaju plików musisz zadeklarować rekord, który będzie zapisywany do pliku. Deklaracja taka może przedstawiać się następująco:

Type
{ deklarujemy rekord danych - w takiej postaci będzie zapisywany do pliku }
  TDatebaseRec = Packed Record
    Name : String[125]; // Ważne, aby w rekordach, String miał określoną długość (ShortString = String[255])
    Date : TDate;
  End;


Chciałem w tym momencie wspomnieć o jednej rzeczy. Podczas "zabawy" z rekordami (w przypadku plików typowanych) możesz napotkać na problemy. Otóż program może nie chcieć się skompilować i pokaże się taki błąd: [Error] MainFrm.pas(40): Type 'XXX' needs finalization - not allowed in file type. Jest to związane z tym, że w rekordzie znajduje się zmienna String, która tam znajdować się nie może. Ponieważ dane znajdujące się w typie String mogą być praktycznie nieograniczone kompilator nie może wiedzieć jaką długość będzie zawierał ten element. W takim wypadku należy stosować ograniczenie, czyli w nawiasach klamrowych wpisywać liczbę, która określa ile max. znaków może znajdować się w tym elemencie (tak jak w przykładzie powyżej). Wtedy kompilator nie będzie protestował.

To już mamy - to jednak nie wszystko. Teraz trzeba będzie zadeklarować nowy typ, który będzie wskazywał na tę strukturę - zapisujemy to następująco:

  TDateBase = File Of TDatebaseRec; // Deklaracja pliku typowanego


W ten sposób mamy nowy typ danych - TDateBase, który jest teraz plikiem typowanym. Dalsza część jest już podobna - jak w przypadku plików tekstowych:

Var
  DateBase : TDateBase;
Begin  
  AssignFile(DateBase, FileName);

 
Tak samo działa procedura Reset i ReWrite. W plikach typowanych nie możemy niestety stosować polecenia Assign, lecz i na to się znajdzie sposób. Jest bowiem polecenie Seek. Polecenie to w tym przypadku powoduje zmianę położenia na określonym rekordzie. Jest także polecenie FileSize, które normalnie zwraca wielkość pliku, lecz w tym wypadku zwraca ilość znajdujących się w pliku rekordów. Można więc polecenie Assign zastąpić takim zapisem:

Seek(DateBase, FileSize(DateBase));


Możemy zastosować również funkcję FilePos, które zwraca aktualne położenie w pliku, czyli numer pozycji.

Edycja


Nie można także w połączeniu z plikami typowanymi stosować procedur Writeln/Readln, gdyż spowoduje to błąd: [Error] MainFrm.pas(58): Illegal type in Write/Writeln statement. Należy w takim wypadku pisać jedynie Write/Read. W tych procedurach jako pierwszy parametr podawany jest uchwyt do pliku, a w drugim parametrze nazwa zmiennej wskazującej na dany rekord. Napiszemy teraz przykładową aplikację korzystająca z plików typowanych. Będzie to taka pseudo baza danych. Użytkownik będzie mógł dodać kolejne rekordy, a następnie je przeglądać. Rekord będzie zawierał imię i nazwisko oraz datę. W sekcji Implementation możesz zadeklarować takie instrukcje:

type
{ deklarujemy rekord danych - w takiej postacii będzie zapisywany do pliku }
  TDatebaseRec = Packed Record
    Name : String[125];
    Date : TDate;
  End;
  TDateBase = File Of TDatebaseRec; // deklaracja pliku typowanego
 
  Var DateBase : TDateBase; // zmienna na ten plik
  Const FileName = 'Dane.dat'; // nazwa pliku do zapisania

Procedura zapisująca kolejny rekord będzie wyglądała tak:
procedure TMainForm.btnSaveClick(Sender: TObject);
Var
  StrucRec : TDatebaseRec;
Begin
  AssignFile(DateBase, FileName);
  Reset(DateBase);
  Seek(DateBase, FileSize(DateBase)); // Przesunięcie na koniec pliku
 
  { Pobranie do rekordu danych z komponentów }
  StrucRec.Name := edtName.Text;
  StrucRec.Date := StrToDate(meDate.Text);
 
  Write(DateBase, StrucRec); // Zapisanie rekordu do pliku
 
  CloseFile(DateBase);  // Zamknięcie pliku
  cbRec.Clear; // Wyczyszczenie komponentu
  FormCreate(Sender); // Wywołanie procedury OnCreate
end;


Najpierw deklarujemy zmienną wskazującą na rekord TDatebaseRec. Po otwarciu pliku do edycji i ustawieniu początku zapisu na końcu pliku następuje do tego rekordu pobranie zawartości komponentów edtName oraz meDate. Następnie procedura Write, w której podaje w pierwszym parametrze uchwyt otwartego pliku, a w drugim całą strukturę do zapisania. Na końcu po zamknięciu pliku komponent cbRec jest czyszczony (procedura Clear) i wywoływane jest zdarzenie OnCreate formy (ją napiszemy za chwilę).

Po lewej stronie mamy komponent typu TComboBox, w którym po uruchomieniu znajdą się kolejne rekordy. Po wybraniu któregoś z nich w odpowiednich polach pojawiają się dane odczytane z pliku tekstowego.

W tym programie wykorzystałem komponent TMaskEdit. Jest to komponent, który służy do przechowywania różnych wartości z tym, że sprawdza on przy okazji, czy wprowadzane dane są zgodne. Np. ja w tym komponencie ustawiłem tzw. maskę na Date. I w tym momencie, komponent wymaga wpisania daty w postaci: DD-MM-YY. Maskę, w tym komponencie ustawia się za pomocą właściwości EditMask w Inspektorze Obiektów

W zdarzeniu OnCreate formy następuje odczytanie z rekordu wszystkich rekordów i wyświetlenie skrótu danego rekordu (w postaci imienia i nazwiska) w komponencie typu TComboBox (cbRec). Właściwość przedstawia się tak:

procedure TMainForm.FormCreate(Sender: TObject);
var
  StrucRec : TDateBaseRec;
  I : Integer;
Begin
  AssignFile(DateBase, FileName);
  Reset(DateBase);
  { Pętelka jest wykonywana tyle razy ile jest rekordów }
  For I := 0 to FileSize(DateBase)-1 Do
  Begin
    Read(DateBase, StrucRec); // Po kolei odczytanie kolejnego z rekordów
    cbRec.Items.Add(StrucRec.Name); // Dodanie do komponentu jedynie Imienia i Nazwiska
  End;
  CloseFile(DateBase);  // Zamknięcie pliku
end;


W tej procedurze zastosowałem funkcję FileSize. Tak więc pętla for wykonywana jest tyle razy ile rekordów znajduje się w pliku. Podczas wykonywania pętli za pomocą funkcji Read następuje odczytanie kolejnych rekordów i przypisanie każdego z nich do zmiennej StrucRec, która jest wskazaniem na rekord TDateBaseRec.  

Teraz pozostaje już tylko oprogramowanie jednej funkcji, która odpowiadać będzie za odczytanie konkretnego rekordu:

procedure TMainForm.cbRecClick(Sender: TObject);
var
  StrucRec : TDateBaseRec;
begin
{ Procedura jest wykonywana po wybraniu któregoś z rekordów z listy. Wtedy dane
  rekordu są odczytywane z pliku }
  AssignFile(DateBase, FileName);
  Reset(DateBase);
  Seek(DateBase, cbRec.ItemIndex); // Przesunięcie kursora na wybraną pozycję
  Read(DateBase, StrucRec); // Odczytanie rekordu
  { Przypisanie danych z rekordu do komponentów }
  edtName.Text := StrucRec.Name;
  meDate.Text := DateToStr(StrucRec.Date);
  CloseFile(DateBase);  
end;


W procedurze OnCreate następuje przypisanie do komponentu cbRec każdej pozycji z pliku. Tak więc ilość pozycji w tym komponencie odpowiada za ilość rekordów w pliku, czyż nie tak? W tej procedurze po otwarciu pliku następuje przesunięcie za pomocą Seek do rekordu, który został wybrany w komponencie cbRec. Komponent ten bowiem posiada właściwość ItemIndex, która określa, która pozycja z tego komponentu została wybrana. Tak więc na tej podstawie przesuwamy pozycje kursora w pliku i odczytujemy kolejny rekord. W tym momencie zmienna StrucRec zawiera rekord odczytany z pliku, który później pozostaje już tylko wyświetlenie w komponentach. Cały listing programu przedstawia się następująco:

unit MainFrm;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Buttons, Mask;
 
type
  TMainForm = class(TForm)
    gbHome: TGroupBox;
    Label1: TLabel;
    Label2: TLabel;
    edtName: TEdit;
    meDate: TMaskEdit;
    btnSave: TBitBtn;
    cbRec: TComboBox;
    procedure btnSaveClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure cbRecClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
 
var
  MainForm: TMainForm;
 
implementation
 
{$R *.dfm}
 
type
{ deklarujemy rekord danych - w takiej postacii będzie zapisywany do pliku }
  TDatebaseRec = packed record
    Name : String[125];
    Date : TDate;
  end;
  TDateBase = file of TDatebaseRec; // deklaracja pliku typowanego
 
  var DateBase : TDateBase; // zmienna na ten plik
  const FileName = 'dane.dat'; // nazwa pliku do zapisania
 
 
procedure TMainForm.btnSaveClick(Sender: TObject);
var
  StrucRec : TDatebaseRec;
begin
  AssignFile(DateBase, FileName);
  Reset(DateBase);
  Seek(DateBase, FileSize(DateBase)); // przesunięcie na koniec pliku
 
  { pobranie do rekordu danych z komponentów }
  StrucRec.Name := edtName.Text;
  StrucRec.Date := StrToDate(meDate.Text);
 
  Write(DateBase, StrucRec); // zapisanie rekordu do pliku
 
  CloseFile(DateBase);  // zamknięcie pliku
  cbRec.Clear; // wyczyszczenie komponentu
  FormCreate(Sender); // wywołanie procedury OnCreate
end;
 
procedure TMainForm.FormCreate(Sender: TObject);
var
  StrucRec : TDateBaseRec;
  i : Integer;
begin
  AssignFile(DateBase, FileName);
  Reset(DateBase);
 
  { pętelka jest wykonywana tyle razy ile jest rekordów }
  for I := 0 to FileSize(DateBase)-1 do
  begin
    Read(DateBase, StrucRec); // po kolei odczytanie kolejnego z rekordów
    cbRec.Items.Add(StrucRec.Name); // dodanie do komponentu jedynie Imienia i Nazwiska
  end;
 
  CloseFile(DateBase);  // zamknięcie pliku
end;
 
procedure TMainForm.cbRecClick(Sender: TObject);
var
  StrucRec : TDateBaseRec;
begin
{ procedura jest wykonywana po wybraniu któregoś z rekordów z listy. Wtedy dane
  rekordu są odczytywane z pliku }
  AssignFile(DateBase, FileName);
  Reset(DateBase);
 
  Seek(DateBase, cbRec.ItemIndex); // przesunięcie kursora na wybraną pozycję
  Read(DateBase, StrucRec); // odczytanie rekordu
  { przypisanie danych z rekordu do komponentów }
  edtName.Text := StrucRec.Name;
  meDate.Text := DateToStr(StrucRec.Date);
 
  CloseFile(DateBase);  
end;
 
end.


Oczywiście jest to tylko drobny przykład realizujący jedynie dodawanie nowych rekordów. Modyfikowanie i usuwanie danych musisz sobie oprogramować sam. Osobiście jednak jeżeli bym miał wybór - albo stosowanie plików typowanych, albo strumieniowych - wybrałbym strumienie.

Pliki strumieniowe


Pliki typowane nie mogą być wykorzystywane bez udziału VCL'a, czyli tylko za pomocą modułu Windows.pas. Strumienie, a konkretnie klasa TStream to jak już mówiłem klasa, ale abstrakcyjna. Co to oznacza? Że jest to jedynie klasa bazowa zawierająca określone metody, z których nie można ot tak sobie korzystać. To od niej wywodzą się pozostałe klasy:

[[Delphi/TFileStream]] - służy do operowania na plikach
[[Delphi/TStringStream]] - do manipulowania łańcuchami tekstowymi
[[Delphi/TMemoryStream]] - do operowania danymi w pamięci komputera
[[Delphi/TResourceStream]] - służy do operowania strumieniami w zasobach


Tę ostatnią klasę miałeś okazję poznać w poprzednim rozdziale kiedy to zajmowaliśmy się zasobami. Użyłem jej wtedy do wyciągnięcia z zasobów różnych danych. Mogłem wtedy użyć funkcji API - np:

       {  odnajdz w zasobach zasob i przypisz go do zmiennej Fres }
          Fres := FindResource(hInstance, Tablica[i].FName, RT_RCDATA);
    {  do pliku zapisz dane z wyciagnietych zasobow }
          BlockWrite(Ouff, LockResource(LoadResource(hInstance, Fres))^,
          SizeofResource(hinstance, Fres));


Ale wydawało mi się, że skoro programujemy w Delphi to lepiej było użyć jednak klasy VCL. A więc strumienie to specjalny typ danych składających się z bajtów. Takie dane mogą być transportowane miedzy pamięcią, plikami, zasobami itp. Jak już powiedziałem klasa TStream nie może być sama użyta. Jeżeli spróbujesz wywołać jej konstruktora i napisać tak:

Var
  S : TStream;
begin
  S := TStream.Create;
  S.Free;
end;


Delphi wyświetli komunikat: [Warning] Unit1.pas(30): Constructing instance of 'TStream containing abstract method 'TStream.Read'. Żeby pozbyć się tego błędu konstruktor musisz wywołać np. w taki sposób:

var
  sComponent : TFileStream;
begin
  sComponent := TFileStream.Create('C:\ustawienia', fmOpenRead);


Czyli klasę abstrakcyjną zastępujemy klasą TFileStream do manipulowania na plikach. Pierwszy parametr to ścieżka do pliku. Drugi parametr mówi, czy plik ma zostać otwarty do odczytu, zapisu, czy może ma zostać stworzony. Istnieje kilka możliwości:

fmCreate - plik zostanie od nowa stworzony
fmOpenRead - otwarcie tylko do odczytu
fmOpenWrite - otwarcie tylko do zapisu nowych danych
fmOpenReadWrite - otwarcie zarówno do zapisu jak i do odczytu
fmShareExclusive - w przypadku, gdy nasza aplikacja korzysta z danego pliku inna nie może go otworzyć
fmShareDenyWrite - inna aplikacja może otworzyć plik, ale tylko do odczytu - nie do zapisu
fmShareDenyRead - inna aplikacja może otworzyć plik, ale tylko do zapisu -  nie do odczytu


Napiszmy teraz jakiś program korzystający ze strumieni. Może taki, który będzie zapamiętywał ustawienia komponentu na formie? Posłużymy się tutaj klasą TFileStream. Klasa ta posiada bardzo wygodną procedurę WriteComponentRes. Otóż zapisuje ona ustawienia wybranego komponentu w pliku RES (zasobie). Wygeneruj procedurę OnDestroy, formy, która będzie wykonywana przed zamknięciem programu:

procedure TMainForm.FormDestroy(Sender: TObject);
var
  sComponent : TFileStream;
begin
{ stwórz plik z ustawieniami i zapisz je }
 
  sComponent := TFileStream.Create('ustawienia.res', fmCreate);
  sComponent.WriteComponentRes('button', btnClick);
  sComponent.Free;
end;


W tej procedurze tworzymy strumień. Następnie wywołujemy metodę, o której przed chwilą mówiłem. Pierwszym jej parametrem jest nazwa zasobu. Drugim natomiast komponent, który ma być zapisany do zasobu. Nie zapomnij wywołać metody Free po zakończeniu korzystania z zasobów. Jest to bardzo częste przeoczenie programistów - nie zwalniają zasobów po zakończeniu pracy z nimi. Tak więc za pomocą tego kodu właściwości komponentu btnClick, który jest umieszczony na formie zostały zapisane do pliku.
Teraz wygeneruj procedurę OnCreate, której kod będzie wykonywany zaraz po uruchomieniu programu:

procedure TMainForm.FormCreate(Sender: TObject);
var
  sComponent : TFileStream;
begin
  if FileExists('ustawienia.res') then // sprawdź, czy plik istnieje
  begin
    sComponent := TFileStream.Create('ustawienia.res', fmOpenRead); // odczytaj plik
    sComponent.ReadComponentRes(btnClick); // odczytaj ustawienia
    sComponent.Free; // ZWOLNIJ ZASOBY!
  end;
 
end;


Najpierw instrukcja if sprawdza, czy dany plik istnieje. Jeżeli by nie istniał, a my byśmy próbowali odczytać go to Delphi wyświetliłby niepotrzebny błąd. Tak więc później następuje wywołanie konstruktora klasy i odczytany zostaje plik ustawienia.res. Nastepnie procedura ReadComponentRes odczytuje ustawienia komponentu z pliku. To właściwie wszystko, ale aby lepiej zaprezentować działanie tego programu napiszemy jeszcze prostą procedurę w której osoba korzystająca z programu będzie mogła sama zmienić położenie przycisku - komponentu btnClick. W tym celu będziesz musiał wygenerować zdarzenia OnMouseDown, OnMouseMove oraz OnMouseUp dla komponentu btnClick. Pierwsze zdarzenie występuje w momencie, gdy użytkownik nad komponentem wciśnie klawisz myszki. Drugie zdarzenie występuje w momencie, gdy użytkownik porusza kursorem nad komponentem, a ostatnie zdarzenie występuje w momencie, gdy użytkownik puszcza klawisz myszy. Te trzy procedury powinna wyglądać tak:

var MD : Boolean;
    P : TPoint;
 
procedure TMainForm.btnClickMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  MD := True;  // zmień właściwość na TRUE
  P := Point(X, Y); // pobierz dotychczasową pozycję kursora
end;
 
procedure TMainForm.btnClickMouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
begin
  if MD then  // sprawdź, czy właściwość = TRUE
  begin
  { zmieniaj pozycje komponentu wg. pozycji kursora }
    btnClick.Left := btnClick.Left + (X - P.X);
    btnClick.Top := btnClick.Top + (Y - P.Y);
  end;
end;
 
procedure TMainForm.btnClickMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  MD := False;
end;


Na początek zadeklarowałem dwie zmienne. Pierwsza z nich MB jest typu Boolean i "mówi", czy klawisz myszki jest wciśnięty, czy też nie. Zauważ, że w zdarzeniu OnMouseDown, w przypadku, gdy klawisz myszki zostaje wciśnięty zmienna MB przybiera wartość TRUE.

Zadeklarowałem także zmienną globalną typu TPoint. Ten typ zmiennej to w rzeczywistości rekord, który zawiera w sobie właściwości X i Y. Ten typ zmiennej jest doskonały do określania pozycji kursora. Zauważ, że każde zdarzenie posiada parametry X i Y, które określają aktualne położenie kursora myszy. W zdarzeniu OnMouseDown do zmiennej P zostają przypisane współrzędne aktualnego położenia myszy. W kolejnym zdarzeniu OnMouseMove następuje sprawdzenie, czy kursor jest wciśnięty (jeżeli tak to zmienna MB ma wartość TRUE). Tak więc jeżeli klawisz jest wciśnięty można przesunąć komponent. W rezultacie przesuwanie komponentu będzie bardzo płynne. Trzeba tylko zmienić właściwości Left oraz Top, które określają położenie komponentu (w poziomie - właściwość Left oraz w pionie - właściwość Top). W zdarzeniu OnMouseUp następuje "odciśnięcie" klawisza myszy i zmiana wartości zmiennej MB na FALSE.

A skoro już jesteśmy przy tej partii materiału to pozwolę sobie omówić pozostałe parametry tych zdarzeń. Np. parametr Shift który jest typu TShiftState. Określa on, czy jakieś dodatkowe klawisze są w tej chwili wciśnięte.

ssShift - czy klawisz Shift jest wciśnięty?
ssAlt - klawisz Alt jest wciśnięty?
ssCtrl - klawisz Ctrl jest wciśnięty?
ssLeft - lewy klawisz myszy jest wciśnięty?
ssRight - prawy klawisz myszy jest wciśnięty?
ssMiddle - środkowy klawisz jest wciśnięty?
ssDouble - oba klawisze myszy są wciśnięte?


Jeżeli chcesz się dowiedzieć jaki klawisz jest dodatkowo wciśnięty napisz to w ten sposób:

if (Shift = [ssAlt]) then ...


W takim wypadku jeżeli instrukcja ta zwróci TRUE to będzie oznaczać, że klawisz Alt jest wciśnięty. Taki zapis jest konieczny, gdyż TShiftState to zbiór! (o zbiorach mówiłem w drugim rozdziale).

Typ TMouseButton natomiast informuje jaki klawisz myszki jest wciśnięty. Ten typ może zawierać trzy parametry: mbLeft, mbRight, mbMiddle.

  if (Button = mbLeft) then
  begin
    MD := True;  // zmień właściwość na TRUE
    P := Point(X, Y); // pobierz dotychczasową pozycję kursora
  end;


W takim wypadku ten kod zadziała tylko wtedy, gdy naciskany jest klawisz lewy - nie prawy.

Przykład zastosowania strumieni: wyciąganie tagu z pliku mp3


Osoby lubiące słuchać muzykę w mp3 zapewne wiedzą co to jest ID3v1.1 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 strumieniowym możemy tę informację "wyłuskać" z pliku mp3. Starsza wersja (ID3v1), nie pozwalała na odczyt numeru utworu. W nowszej wersji odcięto 2 znaki z Comment (było 30, jest 28) i wykorzystano je właśnie w tym celu. Pierwszy bajt jest pusty (zawiera NULL), a drugi to numer ścieżki audio. 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. Teraz dzięki plikom strumieniowym (dałoby się to zrobić także przy pomocy plików amorficznych - o tym za chwilę) możemy tę informacje odczytać.

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[28]; // komentarz
    Track : Byte; // numer utworu
    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 : TFileStream;
  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
    mpFile := TFileStream.Create(OpenDialog.FileName, fmOpenRead);
    mpFile.Seek(mpFile.Size - 128, soFromBeginning);
    mpFile.Read(Buffer, 128);
    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, 28);
      Track :=Ord(Buffer[127]);
      Genre := Ord(Buffer[128]);
    end;
    if TAG.ID = 'TAG' then // czy w ogóle tag został odczytany?
    begin
      edtTitle.Text := Tag.Title;
      edtArtist.Text := Tag.Artist;
      edtAlbum.Text := Tag.Album;
      edtYear.Text := Tag.Year;
      edtComment.Text := Tag.Comment;
      edtTrack.Text :=IntToStr(Tag.Track);
      edtGenre.Text := Genre[Tag.Genre];
    end else Application.MessageBox('Tag w tym pliku mp3 NIE ISTNIEJE!', 'Nie istnieje...', MB_OK + MB_ICONINFORMATION);
      mpFile.Free;
  end;
end;


Ona załatwi całą sprawę. Wbrew pozorom nie jest ona taka skompilowana. Na samym początku stworzony został strumień i do niego załadowały został plik mp3. Następnie użyłem procedury Seek, która powoduje przesunięcie w określone miejsce pliku. Po przesunięciu w określone miejsce od tego miejsca rozpoczynać się będzie odczytywanie tekstu. Tak więc w tym przypadku cofamy się na ostatnie 128 bajtów pliku. Funkcja Size klasy strumienia podaje ilość bajtów jaką zajmuje dany plik. Czyli od tej ilości odejmujemy 128 i już jesteśmy na początku IDv1 Tag. Drugi parametr procedury Seek oznacza, czy czytanie rozpoczynać się będzie od końca, czy też od początku. Ja napisałem, że od początku. Następnie wywołałem funkcję Read, która odczytuje informacje ze strumienia. Pierwszy parametr tej funkcji to zmienna do której te te informacje zostaną przypisane. W naszym wypadku jest to tablica 128 znaków (Char). Później informacje z tej tablicy muszą zostać rozdzielone i przypisane do poszczególnych elementów rekordu. Użyłem tutaj funkcji Copy. Wbrew pozorom nie służy ona do kopiowania plików. Służy ona do operacji na łańcuchach tekstowych, a konkretniej do kopiowania części łańcucha do innej zmiennej. Otóż pierwszy parametr tej funkcji określa zmienną, której dotyczyć będzie operacja. Drugi parametr to pozycja w zmiennej od której operacja się rozpocznie, czyli od którego miejsca rozpocznie się kopiowanie łańcucha. Ostatni parametr natomiast to pozycja zakończenia kopiowania. W pierwszym np. przypadku kopiujemy znaki od 1 do 3 i przypisujemy je do rekordu - do jego elementu ID. I tak za każdym elementem.

Jak już mówiłem wcześniej do napisania tego programu potrzebna jest wiedza o budowie plików mp3. Stąd wiem ile znaków potrzebne jest do skopiowania, aby uzyskać rok produkcji utworu z pliku mp3.

Program przypisuje dane z rekordów do poszczególnych elementów komponentów TEdit. Cały listing programu przedstawia się następująco:

unit MainFrm;
 
interface
 
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
 
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[28]; // komentarz
    Track : Byte; // numer utworu
    Genre : Byte; // typ - np. POP, Techno, Jazz itp.
  end;
 
var
  MainForm: TMainForm;
 
implementation
 
{$R *.DFM}
 
{
 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[28]; // komentarz
    Track : Byte; // numer utworu
    Genre : Byte; // typ - np. POP, Techno, Jazz itp.
  end;
 
 
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')
  );
 
 
 
procedure TMainForm.btnOpenClick(Sender: TObject);
var
  mpFile : TFileStream;
  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
    mpFile := TFileStream.Create(OpenDialog.FileName, fmOpenRead);
    mpFile.Seek(mpFile.Size - 128, soFromBeginning);
    mpFile.Read(Buffer, 128);
    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, 28);
      Track :=Ord(Buffer[127]);
      Genre := Ord(Buffer[128]);
    end;
    if TAG.ID = 'TAG' then // czy w ogóle tag został odczytany?
    begin
      edtTitle.Text := Tag.Title;
      edtArtist.Text := Tag.Artist;
      edtAlbum.Text := Tag.Album;
      edtYear.Text := Tag.Year;
      edtComment.Text := Tag.Comment;
      edtTrack.Text := IntToStr(Tag.Track);
      edtGenre.Text := Genre[Tag.Genre];
    end else Application.MessageBox('Tag w tym pliku mp3 NIE ISTNIEJE!', 'Nie istnieje...', MB_OK + MB_ICONINFORMATION);
  end;
     mpFile.Free;
end;
 
end.


Pliki amorficzne


Dotąd podczas omawiania plików mówiliśmy albo o plikach tekstowych (których linie są oddzielone znakiem ASCII - #13), albo o plikach typowanych, których budowa jest rekordowa. Pliki amorficzne (ang. untyped file) to pliki, które nie mają jednolitej budowy, a odczytywać z nich możemy poszczególne bajty lub inną określoną porcję danych. Pliki amorficzne to inaczej pliki binarne. Aby operować na tych typach danych musisz zadeklarować taką zmienną:

var
  fFile : File;


Typ danych musi być File. Nazwy funkcji operujących na plikach tekstowych są bardzo podobne jak w przypadku plików tekstowych - ba, są nawet takie same. Zapis i odczyt danych z/do pliku odbywa się jednak za pomocą procedur BlockWrite i BlockRead. Jest różnica w otwieraniu plików za pomocą Reset/ReWrite. Otóż teraz potrzebny jest jeszcze jeden - drugi parametr tych procedur. Jedyną słuszną wartością tego parametru jest cyfra 1. Otóż określa on ilość porcji odczytywanych jednocześnie. Tutaj na programistę czyha jednak pułapka. Nie wpisując tutaj żadnej wartości kompilator potraktuje to jako przyjęcie domyślnej wartości - 128.
Jak już mówiłem odczytywanie danych następuje przy pomocy procedury BlockRead. Pierwszy jej parametr to plik, czyli zmienna wskazująca na typ File. Drugi parametr to bufor danych, czyli zmienna, w której trzymane będą odczytywane dane. Trzeci parametr określa ilość danych do wyciągnięcia. Istnieje jeszcze ostatni parametr, lecz jest on opcjonalny i określa ilość faktycznie odczytanych bajtów. W to miejsce wstawić należy zmienną, która wtedy będzie właśnie zawierać faktyczną ilość odczytanych danych. Lecz jak mówiłem nie jest to parametr obowiązkowy więc można go ominąć. Spójrz na poniższy kod:
 
procedure TForm1.Button1Click(Sender: TObject);
var
  F : File;
  Buff : array[0..125] of char;
begin
  AssignFile(F, 'C:\flash.rar');
  Reset(F);
  BlockRead(F, Buff, 128);
  CloseFile(F);
end;


Następuje tutaj odczytanie pierwszych 128 bajtów pliku. Dane zostaną odczytane do tablicy 128-elementowej tablicy typu Char. Każdy element tablicy w takim wypadku oznacza jeden bajt. Zwróć jednak uwagę, że nie podałem w poleceniu Reset tego ostatniego parametru. Takie wywołanie kodu spowoduje błąd I/O Error 87. Uważaj na to gdyż jest to częsta przyczyna pojawiania się tego błędu. Czyli prawidłowy zapis polecenia Reset powinien wyglądać tak:

 
Reset(F, 1);


Za pomocą tych funkcji można np. zrealizować kopiowanie plików. W rzeczywistości polegać to będzie na odczytywaniu w pętli określonej liczby bajtów z jednego pliku i dopisywanie tych odczytanych bajtów do drugiego pliku. Cała operacja przebiegać będzie w pętli aż odczyt danych się zakończy.

procedure TForm1.Button1Click(Sender: TObject);
var
  Src, Dst : File; // zmienne plikowe
  Buffer : array[0..128] of char; // bufor przechowywać będzie same bajty
  BytesRead : Integer; // zmienna przechowywać będzie liczbę odczytanych bajtów
begin
  AssignFile(Src, 'C:\flash.rar'); 
  Reset(Src, 1);  // otwarcie pliku
    AssignFile(Dst, 'C:\flash-kopia.rar');
    Rewrite(Dst, 1); // stworzenie nowego...
 
    while BytesRead > 0 do // pętla wykonywana dopóki zmienna BytesRead będzie większa od 0
    begin
      BlockRead(Src, Buffer, SizeOf(Buffer), BytesRead); // odczytaj porcje danych
      BlockWrite(Dst, Buffer, BytesRead); // zapisz tę porcję do oddzielnego pliku
    end;
 
 
    CloseFile(Dst);
  CloseFile(Src);
end;


W tym wypadku zastosowałem tutaj ostatni opcjonalny parametr. Zacznijmy od początku. Po otwarciu pliku następuje rozpoczęcie pętli. i na samym początku sprawdza wartość zmiennej BytesRead. Jeżeli jest ona większa od 0 to kontynuuje wykonywanie pętli. A w samej pętli następuje odczytanie kolejnych bajtów i przypisanie do tablicy Buffer. Zwróć uwagę na konstrukcję SizeOf(). Otóż podaje ona liczbę elementów danego typu podanego w nawiasie. W tym wypadku w jakim tę funkcję zastosowano zwraca ona liczbę bajtów zajmowanych przez tablicę. I ostatni element procedury BlockRead, czyli nazwa zmiennej, która przechowywać będzie ilość odczytanych danych. W procedurze BlockWrite należy podać także bufor z danymi do zapisania. W naszym przypadku jest to tablica Buffer, która zawiera elementy. Ostatni parametr procedury BlockRead to liczba bajtów, które będą dodane do pliku.

Kody źródłowe przedstawione w tym artykule znajdują się w dziale kody źródłowe w serwise 4programmers.net.

Obsługa plików w .NET


Uwaga! Część z przedstawionego tu materiału może nie działać w Delphi 8 .NET. Po prostu pliki typowane są przestarzałą formą przechowywania informacji. To samo tyczy się plików tekstowych oraz amorficznych.

W Delphi 8 oraz Delphi 2005 dla .NET, funkcje BlockRead oraz BlockWrite w ogóle wyszły z użycia. W miejsce starych technologi powstają nowe - w tym wypadku: XML, bazy danych BDE.

Zobacz też:


  1. Kafelki
  2. Lista

TextFile

Text

Assign

File of

File

15 komentarzy

TomRiddle 2012-01-29 15:55

Czemu jest tylko 80 nazw gatunków, jak dobrze liczę bajt ma 0..255, więc czemu 80. Na pewno windows i inne programy odczytają tą liczbę tak jak zapisał mój program?  Żeby nie było że zapiszę jako "Rock" a potem mi pokaże jakieś "Gospel".

Loloki 2009-02-28 10:58

W plikach tekstowych brakuje jeszcze opisu dla Append.

Adam.Pilorz 2007-01-25 12:47

maly186: A miałeś do czynienia z plikami (tekstowymi) rozmiarów powyżej kilku MB? A może z plikami rozmiarów sięgających GB? Takie pliki chcesz otwierać TStringList? Powodzenia życzę...

maly186 2006-12-06 13:12

Ja jestem zwolennikiem obuiektowego podjeścia do tematu. Do plików tekstowych korzystam z TStringList a binarnych TFileStream. Uważam, że korzystanie z procedur/funkcji typu AssignFile,Rewrite,itd. Wygodniej jest korzystać z metod klasy TStringList LoadFromFile,SaveToFile,Insert,Add i tak dalej.

Adam Boduch 2006-04-07 15:01

Nie mogles wiec poprawic skoro widzisz blad?? Zakladka "Edycja" :|

Morgoth_ 2006-04-07 14:58

W tekście:
Var
  TF : TextFile;
  S : String;
begin
  AssignFile(TF, 'C:\plik.txt');
  Reset(TF);
  while not eof(TF) do
  begin
    Readln(TF, S);
    ListBox1.Items.Add(S)           <-brakuje średnika
  end;
  CloseFile(TF);



a ma być:

Var
  TF : TextFile;
  S : String;
begin
  AssignFile(TF, 'C:\plik.txt');
  Reset(TF);
  while not eof(TF) do
  begin
    Readln(TF, S);
    ListBox1.Items.Add(S);
  end;
  CloseFile(TF);

Adam Boduch 2006-01-09 17:29

Przydaloby sie jeszcze podlinkowac slowa kluczowe jezyka Delphi pod odpowiednie artykuly.

mswdkg 2006-01-07 23:38

Jak zrobić zeby otwierajac plik textowy (muj program jest przypisany do tego typu plikó) automatycznie po otwarciu programu zawartość byla wczytywana do jakiejs zmiennej w programi??

mswdkg 2006-01-07 23:31

Jak zrobić zeby otwierajac plik textowy (muj program jest przypisany do tego typu plikó) automatycznie po otwarciu programu zawartość byla wczytywana do jakiejs zmiennej w programi??

jakubkrol 2006-01-04 22:25

Bardzo dobre bardzo dobre. Szkody tylke, ze nie dodales o PLIKACH INI albo chociaz nie zrobiles linku do tego Twojego arta o Plikach INI (wedlug mnie jest wrecz genialny)

Dziadek 2005-05-21 17:03

Całkiem dobry artykuł

AklimX 2005-01-02 19:13

"Pliki strumienioniowe" :D:D:D - popraw

warlock 2004-07-17 12:13

Uwagi co do kodu z przykładem odczytywania Tagu ID3v1
Po co napisałeś mpFile.Seek(mpFile.Size - 128, soFromBeginning); ???
Po krótkim zastanowieniu można powiedzieć ,że tu jest 2x razy to samo!
Blisko twojej myśli by było już:
mpFile.Position := mpFile.Size - 128;
A już idealnie by było
mpFile.Seek(-128, soFromEnd);

Deti 2004-02-07 09:49

procedure TForm1.Button1Click(Sender: TObject);
var
  F : file;
  Buff : array[0..125] of char;
begin
  AssignFile(F, 'C:\flash.rar');
  Reset(F);
  BlockRead(F, Buff, 128);
  CloseFile(F);
end;


a nie powinno byc array[0..127] ? :)

Neddy 2003-06-03 14:20

Proste i skuteczne...