Živě.cz o počítačích a internetu

Umíme to s Delphi: 158. díl – komponenty a streamy

Václav Kadlec 9.8.2005

Dnešní článek se opět týká datových proudů a zaměřuje se na to, jakou „streamovou“ podporu obsahují standardní vizuální komponenty knihovny VCL. Jinak řečeno, dnes se dozvíte, jak velmi jednoduše využívat streamy při běžné práci s komponentou ListBox, Memo, RichEdit a dalšími. Kromě toho si ukážeme velmi elegantní způsob, jak kopírovat jeden stream do druhého a k čemu takové kopírování můžeme využít.

Po kratší odmlce se opět hlásíme s dalším pokračováním seriálu o programování v Delphi. V několika předchozích pokračováních jsme se zabývali problematikou streamů nebo datových proudů. Nejprve si pojďme stručně zopakovat, co už jsme si o streamech pověděli, potom se vrhněme na náplň dnešního článku.

Prozatím jsme se dozvěděli, že stream funguje jako jakési vodovodní potrubí: na jedné straně do něho lijeme vodu, na straně druhé jej kamsi připojíme. V důsledku toho voda jednoduše protéká z nádrže do jejího cílového umístění. Podobně funguje stream: na jedné straně jej kamsi připojíme (např. souborový stream do diskového souboru), na druhé straně do něho lijeme data. Po úvodním nakonfigurování streamu („připojení trubky“) data jednoduše protékají tak, jak chceme.

Jednou z hlavních výhod streamu, a zároveň jedním z hlavních důvodů, proč streamy vlastně používat, je skutečnost, že je lze velmi jednoduše zaměňovat jeden za druhý. Pokud se například jednoho krásného dne rozhodneme, že data budeme chtít lít namísto do diskového souboru do síťového socketu, bude změna při použití streamů velmi, velmi jednoduchá. Právě v tomhle spočívá největší výhoda streamů – není obtížné rozhodnout se pro změnu používaného streamu a namísto diskového použít třeba paměťový a nebo síťový.

V předchozích částech seriálu jsme se podrobněji zabývali několika konkrétními druhy streamů a ukázali jsem si jejich praktické využití na demonstračních aplikacích. Jednalo se o následující streamy:

V dílu 154 jsme se pak zabývali úplnými streamovými fundamenty a podali jsme si základní informace o tom, co streamy jsou a jak se používají.

Tolik ke stručnému shrnutí předchozího obsahu seriálu. V dnešním článku si řekneme několik dalších užitečných informací o streamech, takříkajíc „z několika soudků“. Podíváme se na to, jak jsou streamy podporovány některými vizuálními komponentami knihovny VCL, podíváme se na to, jak kopírovat data z jednoho streamu do jiného a v neposlední řadě se podíváme na zjišťování a nastavování pozice ve streamu.

Pojďme rovnou na věc, první téma se týká podpory streamů vizuálními komponentami VCL.

Podpora streamů vizuálními komponentami VCL

Protože streamy jsou obecně velmi užitečná věc, tvůrci vizuálních komponent z knihovny VCL samozřejmě neodolali a zařadili podporu tohoto konceptu přímo ve formě metod u některých komponent. Podíváte-li se například do nápovědy Delphi na komponentu Memo nebo RichEdit, seznáte záhy, že v seznamu metod těchto komponent je několik zástupců, kteří se bezpochyby úzce týkají právě streamů. Přesněji řečeno, nepátrejte přímo v metodách příslušných komponent, raději zkuste zabrousit do metod jejich „datových vlastností“, například do metod třídy TStrings, která reprezentuje data uvnitř komponenty Memo.

Pokud jste nyní lehce zmatení, dovolte mi připomenout, že:

Takže: třída TStrings obsahuje kromě jiných také metody LoadFromStream a Save ToStream. Jak už jejich názvy napovídají, tyto metody jsou určeny právě pro načítání, resp. ukládání dat ze, resp. do streamů.

Pojďme společně vytvořit jednoduchou ukázkovou aplikaci, která práci s těmito metodami demonstruje. Vložte na formulář komponenty ListBox ze záložky Standard, RichEdit ze záložky Win32 a Button ze záložky Standard. Funkčnost aplikace bude následující: po klepnutí na tlačítko Button dojde k překopírování dat z komponenty ListBox do komponenty Memo. K realizaci kompírování bude použita právě technologie proudů.

Následně ošetříme událost OnClick tlačítka Button:

procedure TForm1.Button1Click(Sender: TObject);

var
  TempStream : TMemoryStream;

begin
  TempStream := TMemoryStream.Create;

  // dame do listboxu nekolik radek textu
  ListBox1.Items.Add(`Nazdar`);
  ListBox1.Items.Add(`Ahoj`);
  ListBox1.Items.Add(`Cau`);

  // nejprve zapiseme obsah ListBoxu do pametoveho streamu
  ListBox1.Items.SaveToStream(TempStream); 
                                           
  // nyní se nastavime na zacatek streamu
  TempStream.Position := 0;     

  // nacteme obsah streamu do komponenty RichEdit
  RichEdit1.Lines.LoadFromStream( TempStream);

  // uvolnime stream – uz nebude potreba
  TempStream.Free;
end;

Vidíme, že zdrojový kód je hrozně jednoduchý. Kromě nám dobře známého vytvoření a následného uvolnění streamu používá ve své podstatě jen dvě metody, které jsme dosud v předchozích dílech explicitně nezmínili: metodu LoadFromStream a metodu SaveToStream.

Metoda SaveToStream třídy TStrings zapíše obsah vlastnosti Text do streamu specifikovaného parametrem metody. Pokud TStrings obsahovaly více řetězců, jsou při zápisu do streamu odděleny znakem konce řádku. Pokud je specifikovaným streamem souborový stream (TFileStream), dělá metoda SaveToStream v podstatě totéž co metoda SaveToFile s jedinou výjimkou: aplikace sama je zodpovědná za vytvoření a uvolnění souborového streamu.

Metoda LoadFromString funguje analogicky: načte řetězce ze streamu do komponenty TStrings. Stream je specifikován parametrem metody. Text načtený ze streamu je rozdělen do jednodlivých řetězců (jednotlivých součástí TStrings) podle výskytu znaků konce řádku. V podstatě tedy lze říct, že LoadFromStream načte hodnotu vlastnosti Text třídy TStream. Pokud je stream souborovým streamem, dělá LoadFromStream v podstatě totéž co LoadFromFile s jedinou výjimkou: aplikace sama je zodpovědná za vytvoření a uvolnění souborového streamu.

Mimochodem, zde také nacházíme to, co někteří čtenáři po právu vytýkali předchozím dílům seriálu. V nich jsme totiž pracovali se streamy „ručně“, tj. ručně jsme do nich lili data, a mnozí tázavě nadzdvihávali obočí ve smyslu „proč učit lidi složitý postup, když máme metody LoadFromStream a SaveToStream?“ Důvodem bylo právě to, aby se čtenáři dozvěděli i o jiných možnostech práce se streamy, ne jen použití primitivně jednoduchých předdefinovaných metod vizuálních komponent. Je ovšem samozřejmé, že použití těchto primitivně jednoduchých metod je doporučené vždy, když to jde: proč to dělat složitě, když to jde jednoduše.

Podívejme se na kompletní zdrojový kód aplikace:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls;

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    RichEdit1: TRichEdit;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);

var
  TempStream : TMemoryStream;

begin
  TempStream := TMemoryStream.Create;

  // dame do listboxu nekolik radek textu
  ListBox1.Items.Add(`Nazdar`);
  ListBox1.Items.Add(`Ahoj`);
  ListBox1.Items.Add(`Cau`);

  // nejprve zapiseme obsah ListBoxu do pametoveho streamu
  ListBox1.Items.SaveToStream(TempStream);

  // nyní se nastavime na zacatek streamu
  TempStream.Position := 0;

  // nacteme obsah streamu do komponenty RichEdit
  RichEdit1.Lines.LoadFromStream( TempStream);

  // uvolnime stream – uz nebude potreba
  TempStream.Free;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Self.Caption := `Ukazka streamu`;
  Button1.Caption := `Kopiruj`;
end;

end.

Metody LoadFromStream a SaveToStream jsou velmi jednoduché a elegantní při použití. Opět největší výhodou použití streamů je možnost zaměnit jednoduše jeden za druhý, aniž bychom museli přepisovat klíčové části aplikace. Kdybychom se například nyní rozhodli, že chceme obsah ListBoxu při kopírování také uložit do souboru, není nic jednoduššího, než zaměnit paměťový stream za stream souborový (TFileStream). Kromě několika drobných změn při vytváření proudu (například musíme specifikovat jméno souboru apod.) není zapotřebí měnit vůbec nic.

Abyste si nemysleli, že metody LoadFromStream a SaveToStream fungují pouze u ListBoxu a RichEditu, uvedu v následujícím výčtu některé další vizuální i nevizuální komponenty a třídy, které tyto metody obsahují. Nejedená se o kompletní výčet, spíše o ilustraci toho, jak široce jsou streamy komponentami podporovány:

Je tedy patrné, že pomocí LoadFromStream a SaveToStream lze načítat a ukládat například ikony, bitmapy, obrázky, databázové údaje, metasoubory, objekty OLE a vůbec celou širokou škálu nejrůznějších typů dat. Je důležité říct ještě jednu věc: skutečnost, že podpora zmíněných metod je zařazena např. do třídy TStrings, kromě jiného znamená, že ji můžeme využít v minimálně pěti nebo deseti komponentách, které používají k ukládání svých dat třídu TStrings. Tuto informaci zdůrazňuji pouze proto, abychom si uvědomili, jak široce jsou streamy podporovány a v jakém množství reálných situací je můžeme velmi jednoduše aplikovat.

Kopírování jednoho streamu do druhého

Další maličkost, na kterou se v rámci dnešního článku podíváme, se zaměřuje na použití streamů při kopírování dat. Jinak řečeno, ukážeme si, jak kopírovat jeden stream do druhého. Chceme-li zkopírovat data jednoho streamu do jiného, máme v zásadě dvě možnosti:

Protože první způsob byste (snad :-)) měli být schopni dát dohromady na základě informací uvedených v předchozích částech seriálu, zaměříme se zde na to, co v předešlých článcích dosud nezaznělo: na metodu CopyFrom.

Následující příklad způsobí zkopírování jednoho souboru do druhého. Kopírování bude přitom fyzicky realizováno prostřednictvím streamů.

Vložte tedy na formulář dvě komponenty Edit a jedno tlačítko Button, vše ze záložky Standard palety komponent. Následně ošetříme událost OnClick tlačítka Button1. Funkčnost aplikace bude následující: za běhu aplikace zapíšeme do editačního pole Edit1 název zdrojového souboru, tedy souboru, z něhož chceme kopírovat. Do editačního pol Edit2 pak zapíšeme název cílového souboru, tedy souboru, do kterého bychom chtěli zapsat. Po klepnutí na tlačítko dojde ke zkopírování dat.

Zde je slíbená obsluha události OnClick tlačítka Button:

procedure TForm1.Button1Click(Sender: TObject);
var
  Source, Destination: TStream;

begin
  // vytvorime zdrojovy stream
  Source := TFileStream.Create(Edit1.Text, fmOpenRead or fmShareDenyWrite);

  try
    // vytvorime cilovy stream
    Destination := TFileStream.Create(Edit2.Text, fmCreate or fmShareDenyRead);

    try
      // zkopirujeme data ze zdroje do cile
      Destination.CopyFrom(Source,Source.Size);

    finally
      // uvolnime cilovy stream
      Destination.Free;
    end;

  finally
    // uvolnime zdrojovy stream
    Source.Free
  end;
end;

Ani v tomto případě není zdrojový kód příliš komplikovaný. Nejprve vytvoříme zdrojový a cílový stream (svážeme je se soubory specifikovanými uživatelem za běhu prostřednictvím komponent Edit1 a Edit2). Streamy vytváříme uvnitř chráněných bloků prostřednictvím mechanismu výjimek. Po vytvoření obou streamů následuje samotné kopírování realizované metodou CopyFrom. Poté oba streamy uvolníme.

A jak přesně funguje CopyFrom? Tato metoda třídy TStream se zavolá u cílového streamu a znamená cosi jako „zkopíruj do mě data ze zdroje uvedeného v prvním parametru“. Proto ji voláme u Destination, tedy u cílového streamu, tedy u cílového souboru. CopyFrom má dva parametry: zdrojový stream a počet bytů určených ke zkopírování. V našem případě chceme zkopírovat celý stream, na místě druhého parametru tedy použijeme výraz Source.Size. Namísto toho bychom mohli použít hodnotu 0, která také způsobí přenesení celého obsahu zdrojového streamu.

Použití CopyFrom pro nás znamená, že při kopírování dat nemusíme vytvářet žádné pomocné buffery, přenášet do nich data, číst z nich data a následně je zase uvolňovat. Copy From po skončení operace nastaví aktuální pozici ve streamu a vrátí počet skutečně zkopírovaných bajtů.

Je třeba dát pozor pouze na jednu věc: pokud by kopírování nefungovalo tak, jak má, je nutné nastavit zkontrolovat a popřípadě nastavit aktuální pozici ve zdrojovém streamu na jeho začátek.

Podívejme se na kompletní zdrojový kód ukázky:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
    Button1: TButton;
    Label1: TLabel;
    Label2: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  Source, Destination: TStream;

begin
  // vytvorime zdrojovy stream
  Source := TFileStream.Create(Edit1.Text, fmOpenRead or fmShareDenyWrite);

  try
    // vytvorime cilov7 stream
    Destination := TFileStream.Create(Edit2.Text, fmCreate or fmShareDenyRead);

    try
      // zkopirujeme data ze zdroje do cile
      Destination.CopyFrom(Source,Source.Size);

    finally
      // uvolnime cilovy stream
      Destination.Free;
    end;

  finally
    // uvolnime zdrojovy stream
    Source.Free
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Text := ``;
  Edit2.Text := ``;
  Button1.Caption := `Zkopiruj`;
  Self.Caption := `Kopirovani souboru pres streamy`;
end;

end.

Závěrem

Původně jsem si myslel, že se dnes podrobněji podíváme ještě na kontrolu a nastavování aktuální pozice ve streamu a na problémy s tím spojené, ale protože koukám, že délka článku začíná být už zase přílišná, bude lepší si tuto otázku ponechat na příště.

Dnes jsme si ukázali zase několik dalších věcí týkajících se streamů. Konkrétně, zaměřili jsme se na podporu streamů uvnitř některých vizuálních komponent prostřednictvím metod SaveToStream a LoadFromStream a také na kopírování jednoho streamu do druhého prostřednictvím metody CopyFrom. Věřím, že dnešní informace zase trochu rozšířily vaše streamové obzory a že si za týden nenecháte ujít ani další pokračování.