Umíme to s Delphi, 11. díl – práce se soubory

Načítání dat ze souborů a ukládání do souborů je velmi důležitou součástí většiny programů. Soubory (v jakékoliv formě) zůstávají klíčovou formou ukládání dat.

Podporu souborů v Delphi lze rozdělit do tří bodů:
  • souborová podpora vycházející z Object Pascalu. Základním klíčovým slovem je File.
  • souborová podpora knihovny vizuálních komponent, která je poskytována třídami TStream, TComponent, TIniFile a dále metodami pro načítání a ukládání dat různých komponent (např. metoda LoadFromFile, apod.).
  • souborová podpora pro databázové formáty. Touto částí se nebudeme v této kapitole zabývat a necháme si ji až do kapitoly o databázích.

Souborová podpora v Object Pascalu

Práci se soubory v Object Pascalu si pouze připomeneme, a to na příkladu. Vytvořte aplikaci, na formulář umístěte jedno tlačítko a jednu komponentu Memo. Stiskem tlačítka se do Memo načte obsah textového souboru DATA.TXT z aktuálního adresáře (ošetříme tedy událost OnClick zmíněného tlačítka):

type
  TTextSoub = TextFile;


procedure TwndHlavni.btnOPClick(Sender: TObject);
var
  Soub: TTextSoub;
  Radka: String;

begin
  AssignFile(Soub, `data.txt`);
  Reset(Soub);

  while not Eof(Soub) do begin
    ReadLn(Soub, Radka);
    memoSoubor.Lines.Add(Radka);
  end;

  CloseFile(Soub);
end;

Poznámky:

Možná jste z Pascalu zvyklí na výrazy Text (pro označení textového souboru), Assign (pro asociování fyzického souboru s proměnnou) či Close (pro uzavření souboru). Ty jsou v Delphi nahrazeny výrazy TextFile, AssignFile, resp. CloseFile. Důvodem je, že původní znění je v Delphi použito k jinému účelu (např. Text je vlastnost některých komponent, např. Memo či Edit). „Staré“ názvy v Delphi sice přesto zůstaly zachovány i s původním významem, je ale nutné uvozovat je kvalifikátorem modulu System. Například místo Assign(F) použijte System.Assign(F).

Využijete-li poznatků předchozí kapitoly, jistě sami snadno přijdete na to, jak provést ošetření chyb. V Delphi je nejefektivnějším způsobem použití mechanismu výjimek (podrobněji viz níže). V „původním“ Pascalu bylo ošetření chyb samozřejmě řešeno trochu jinak (všichni „pascaláci“ si jistě pamatují nezapomenutelné IOResult apod…

Chcete-li pracovat s jiným než textovým souborem, použijte konstrukci file of <typ>, např. file of Integer (soubor s celými čísly).

Souborová podpora v Delphi

Někdy se ovšem nechceme zdržovat vymýšlením konstrukcí vycházejících ze standardního Pascalu. Pak máme k dispozici množství výkonných podpůrných nástrojů pro práci se soubory přímo v Delphi. Asi nejvýznamnějšími prvky jsou metody LoadFromFile, resp. SaveToFile, které dokáží načíst (a zobrazit) obsah souboru zápisem jediné řádky kódu. Nejde přitom zdaleka jen o textové soubory (jak jsme poznali např. v rámci popisu komponenty Memo v kapitole 7).

Metody LoadFromFile, resp. SaveToFile jsou dostupné např. pro třídy TStrings, TPicture, TBitmap, pro některé datové komponenty (TBlobField, TMemoField, TGraphicField), pro grafické formáty (TGraphic, TIcon, TMetaFile) nebo pro OLE zásobníky. Podobné metody jsou k dispozici také u objektů TMediaPlayer.

Poznámka pro pokročilé uživatele:

Jinou třídou týkající se souborů je TIniFile. Ta popisuje speciálně inicializační soubory Windows 3.1. Ve vyšších Windows aplikace zpravidla nahrazují INI soubory systémovými registry. Delphi obsahují i třídy určené pro takovou situaci – TRegistry, resp. TRegistryIniFile. Třídy TIniFile a TRegistryIniFile mají společného předka (TCustoIniFile), proto umožňují kromě jiného změnu přístupu (registry vs. INI soubory) s minimálními změnami kódu.

Modifikujeme nyní příklad uvedený výše, jen použijeme zmíněnou metodu LoadFromFile.

procedure TwndHlavni.btnOPClick(Sender: TObject);
begin
  memoSoubor.Lines.LoadFromFile(`data.txt`);
end;

Myslím, že úspora je více než zřejmá:-). Nemusíme deklarovat žádné typy, definovat žádné proměnné, otvírat žádné soubory, asociovat žádné názvy.

Poznámka pro pokročilé uživatele:

Další zajímavou formou v Delphi je podpora souborových streamů, která vychází z abstraktní třídy TStream. Tato třída má tři následníky: TFileStream, THandleStream a TMemoryStream. Většina jejich metod se týká komponent a používají je zpravidla jejich tvůrci, ale některé jsou použitelné pro kohokoliv (např. ReadBuffer).

Chyby při práci se soubory

Než se vrhneme na standardní dialogy, podíváme se, jak ošetřovat chyby při práci se soubory. Delphi při jakékoliv I/O chybě (tedy chybě vstupu a výstupu) generují výjimku EInOutError. Přesnější specifikace chyby se objevuje (je vracena) v lokální proměnné ErrorCode, která může nabývat následujících hodnot:

Error Code Význam
2 File not found
3 Invalid file name
4 Too many open files
5 Access denied
100 Disk read error – hlášeno, chcete-li číst za koncem souboru - EOF (End of File)
101 Disk write error – hlášeno, chcete-li zapisovat na zaplněný disk
102 File not assigned – hlášeno, chcete-li použít proměnnou typu Text nebo TextFile bez předchozího volání Assign()
103 File not open – hlášeno, chcete-li pracovat se souborem, jenž nebyl otevřen některou z funkcí Reset(), Rewrite(), Append()
104 File not open for input – hlášeno, chcete-li číst ze souboru otevřeného pro zápis
105 File not open for output – hlášeno, chcete-li zapisovat do souboru otevřeného pro čtení
106 Invalid numeric format – hlášeno, chcete-li číst nečíselnou hodnotu z textového souboru do číselné proměnné

Standardní obsluha výjimek (implicitní handler Delphi) dokáže samozřejmě výjimku ošetřit, i když ji sami neodchytnete (viz díl 10), a vypíše MessageBox s odpovídající hláškou podle konkrétní hodnoty ErrorCode. „Nezná“ ovšem všechny hodnoty ErrorCode, a tak se může stát, že uživateli vypíše hlášku typu „I/O Error 103“. Pokud byste tedy chtěli například hlášku počeštit (což u českých programů výrazně doporučuji!), nebo ošetřit opravdu všechny chyby, odchytněte a ošetřete výjimku sami. Podívejte se na následující příklad, který demonstruje ošetření chybových stavů v souvislosti s načítáním obsahu souboru do Memo.

procedure TwndHlavni.btnOPClick(Sender: TObject);
var
  Soub: TTextSoub;
  Radka: String;

begin

  AssignFile(Soub, `data.txt`);

  try
    Reset(Soub);

    try
      while not Eof(Soub) do begin
        ReadLn(Soub, Radka);
        memoSoubor.Lines.Add(Radka);
      end; // while

    finally
      CloseFile(Soub);
    end; // finally

except
    on E:EInOutError do
      case E.ErrorCode of
        2: ShowMessage(`Soubor nenalezen!`);
      103: ShowMessage(`Soubor neotevřen!`);
      else
        ShowMessage(`Chyba při práci se souborem: ` + E.Message);
      end; // case
    end; // except
  end; // procedure

Asi byste sami dokázali vysvětlit, že ve vnějším bloku try se pokusíme soubor otevřít, a nepodaří-li se to, ošetříme výjimku (oznámíme chybu) a končíme. Ve druhém vnořeném bloku try (vnitřnějším) se pokusíme soubor načíst do komponenty Memo. Je jisté, že v tuto chvíli je soubor správně otevřen, takže v každém případě soubor posléze zavřeme (sekce finally).

V tomto příkladu tedy máme ošetřeny chyby při otvírání souboru i při vlastním čtení. Pokud by ovšem např. ReadLn generovala při chybě výjimku EReadError, chyby čtení by ošetřeny nebyly (neboť odchytáváme jen EInOutError). To ona ovšem negeneruje:-).

Poznámky pro pokročilé uživatele:

S problematikou I/O chyb souvisí také výjimky EReadError, resp. EWriteError. První je generována, pokusí-li se aplikace načíst ze streamu větší počet bytů, než je možno. Může být také generována, nedokáže-li Delphi načíst vlastnost při vytváření formuláře (komponenta načítá zdroje – resource – nekorektně, příp. zdroje jsou chybné). Analogicky funguje EWriteError.

Představte si, že byste měli v aplikaci mnoho procedur a funkcí, které budou pracovat se souborem. Opisovat vždy tentýž kód spojený s obsluhou výjimek je jistě nepříjemné a nudné. Naštěstí je to také zbytečné, neboť lze použít událost OnException objektu TApplication, ve které napíšeme ošetřující kód „jednou provždy“. Jednoduchá ukázka, ve které se aplikace po vzniku každé neošetřené výjimky ukončí:

procedure TwndHlavni.FormCreate(Sender: TObject);
begin
  Application.OnException := AppException;
end;

procedure TwndHlavni.AppException(Sender: TObject; E: Exception);
begin
  Application.ShowException(E);  // ukaž chybovou zprávu
  Application.Terminate;        // ukonči aplikaci
end;

Další příkazy pro práci se soubory

Pouze telegraficky si popíšeme několik dalších metod Delphi, které se soubory (a adresáři) úzce souvisí.

Funkce Význam
FileExist(jmeno) Vrací True, existuje-li zadaný soubor
DeleteFile(jmeno) Smaže zadaný soubor a vrací True, podařilo-li se
RenameFile(stare, nove) Přejmenuje soubor a vrací True, podařilo-li se
ChangeFileExt(jmeno, pripona) Změní příponu souboru na zadanou a vrací nový název souboru
ExtractFileName(uplne_jmeno) Extrahuje název souboru (tj. odstraní případnou cestu). Vrací získaný název.
ExtractFileExt(jmeno) Vrací příponu daného souboru

Poznámka: nekamenujte mě prosím za nesprávně (stručně) uváděnou syntaxi. Použil jsem ji pro úsporu místa, jistě si sami dokážete odvodit (příp. najít v helpu), že např. „úplný funkční prototyp“ funkce FileExists() vypadá takto:

function FileExists(const FileName: string): Boolean;

Poznámka pro pokročilé uživatele:

Dále existuje celá řada funkcí pracujících s adresáři a se jmény souborů v jednotce SysUtils.

K souborům lze v Delphi přistupovat též pomocí FileCreate(), FileOpen(), FileSeek(), FileWrite(), FileClose(). Tyto funkce tvoří "samostatnou" množinu funkcí pro práci se soubory za pomoci handlerů.

Určitě si přečtěte

Články odjinud