Václav Kadlec 19.7.2005
Dnešní článek vám podrobně a od základu vysvětlí, co jsou to řetězcové datové proudy reprezentované třídou TStringStream. Řekneme si nejen, co takové proudy mohou uchovávat, ale také k čemu mohou být dobré a jak je v praxi používat. Ukázková aplikace, která demonstruje praktické použití řetězových proud, nebude samozřejmě chybět.
Pojďme se podívat na další ze série článků věnovaných jednomu z poměrně elegantních způsobů nakládání s daty – s datovými proudy (streams). Dnešní článek je věnován dalšímu druhu proudů, avšak ještě předtím, než se pustíme do jeho samotné náplně, zopakujeme stručně, o čem jsme se bavili dosud.
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.
V diskusi pod jedním z předchozích článků zazněla zajímavá otázka: „proč mám třeba složitě používat streamy pro ukládání dat do souboru, když to, čeho pomocí streamů dokážu, mohu stejně dobře a mnohem elegantněji dosáhnout třeba použitím standardních metod komponent VCL, například SaveToFile. Jiný diskutér podal stručnou, ale velmi výstižnou odpověď: streamy použijeme proto, že s jejich pomocí je velmi jednoduché přejít z jednoho „cílového umístění“ na jiné. 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ředminulém dílu jsme společně vytvořili první jednoduchou aplikaci, na jejíž tvorbě jsme si demonstrovali, jakým způsobem zapisovat do streamu textové řetězce (což je v zásadě jedna z obtížnějších variant). Před týdnem jsme pokračovali: tuto aplikaci jsme přepracovali tak, že namísto do diskového souboru zapisovala údaje pouze do operační paměti. Jinak řečeno, namísto diskového streamu (TFileStream) jsme použili paměťový stream (TMemoryStream).
Pod minulým článkem se objevil příspěvek, jemuž je nutné dát za pravdu. Příspěvek říkal cosi ve smyslu „proč používáte takové neelegantní konstrukce, proč čtete z paměti tímhle a tímhle způsobem, když by to lo udělat mnohem jednodušeji.“ Ano, nehádám se: důvod, proč bylo v článku uvedeno právě tohle řešení, spočívá právě v tom, že jsem chtěl demonstrovat jednoduchost přechodu ze souborového streamu (který byl použit o jeden díl dříve) na paměťový stream. Aniž bychom aplikaci jakkoliv výrazně modifikovali, přešli jsme ze čtení souboru na čtení operační paměti. Právě to bylo cílem článku; bohužel to s sebou neslo jistou neeleganci, která vznikla převodem z předchozí verze aplikace.
Pojďme se však zaměřit na samotnou náplň dnešního článku. V něm si totiž ukážeme další druh streamu, který máme k dispozici. Nejedná se zrovna o stream, který bychom používali dnes a denně, ale o jeho existenci se minimálně vyplatí vědět, protože v některých případech se nám rozhodně může hodit. Hovoříme o tzv. řetězcovém streamu reprezentovaném třídou TStringStream.
TStringStream slouží (jak už jeho název napovídá) k uchovávání textových řetězců. Jinak řečeno, pokud by se nám z nějakého důvodu hodilo reprezentovat kterýkoliv řetězec, který v aplikaci používáme, jako stream, pak je třída TStringStream dobrou volbou. Pokud se vám ve vašich zvídavých programátorských myslích objevuje otázka typu „a kdypak by se nám tak mohlo hodit reprezentovat řetězec jako stream?“, potom se přímo nabízí odpověď typu „no přece kdykoliv, když budete programovat aplikaci používající streamy a budete si chtít ušetřit práci.“
Možná to teď není úplně jasné, ale pořád se bavíme o jedné a téže výhodě, kterou streamy přinášejí: o unifikované práci se všemi jejich druhy. Pokud budeme mít aplikaci, která odkudsi načítá, zobrazuje či zpracovává a dále předává stream, pak se může v určitých situacích hodit reprezentovat nějakou specifickou řetězcovou proměnnou jako proud: tím způsobem si zajistíme, že s ní můžeme pracovat stejně jako se všemi dalšími druhy streamů, které v aplikaci máme.
Sama firma Borland v nápovědě k Delphi uvádí: „Třídu TStringStream použijte v případech, kdy potřebujete uložit data jakožto long string a zároveň nad nimi chcete provádět nějaké vstupně-výstupní operace. TStringStream je užitečná jako meziobjekt, který dokáže udržovat data a kromě toho je číst nebo zapisovat z jiných záznamových médií.“
Třída TStringStream nemá zrovna moc metod a vlastností, podívejme se na následující seznam:
Toť v podstatě vše. Třída disponuje řadou dalších metod, ty jsou však odděděny od předků třídy a nevztahují se tedy specificky přímo k TStringStream.
Po pravdě řečeno, po dlouhé úvaze jsem stejně nepřišel na žádný smysluplný příklad použití třídy TStringStream, který by splňoval základní požadavky na příklady v tomto seriálu (jednoduchost a krátkost, názornost, využití co nejvíce metod/vlastností apod.). Proto omluvte smysl následujícího příkladu – po pravdě řečeno, následující příklad skoro žádný smysl ani nemá. Nicméně má jednu pozitivní vlastnost: demonstruje použití třídy TStringStream.
Aplikace, kterou společně vytvoříme, bude provádět následující činnost:
Neříkám, že takhle koncipovaná aplikace může někomu prakticky v čemkoliv pomoci :-) Jen chci, abyste viděli, jak jednoduché může být reálné (i když nepříliš využitelné) použití řetězového streamu.
Pojďme tedy vytvořit aplikaci. Vložme na formulář komponentu Memo ze záložky Standard palety komponent, dále komponentu RichEdit ze záložky Win32 palety komponent a v neposlední řadě dvě komponenty Button ze záložky Standard.
První, co uděláme, bude nadefinování privátního atributu Ret hlavního formuláře. Atribut Ret bude typu TStringStream a jeho definici vložíme přímo do definice třídy TForm1 v úvodní části hlavního modulu:
type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
RichEdit1: TRichEdit;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
{ Private declarations }
Ret: TStringStream;public
{ Public declarations }
end;
Nyní ošetříme událost OnCreate hlavního formuláře. V její obsluze provedeme kromě triviálního nastavení titulků komponent už jen jednu věc – vytvoříme samotný objekt Ret, tedy samotný proud. Parametrem konstruktoru Create je řetězec, který má být do proudu při jeho vytvoření ihned vložen. V našem případě chceme vytvořit prázdný proud:
procedure TForm1.FormCreate(Sender: TObject);
begin
Ret := TStringStream.Create(``);Button1.Caption := `Uloz`;
Button2.Caption := `Nacti`;
Self.Caption := `Ukazka TStringStream`;
end;
Nyní musíme pouze ošetřit klepnutí na obě tlačítka. Nejprve tlačítko Button1 sloužící k uložení Mema do proudu:
procedure TForm1.Button1Click(Sender: TObject);
begin
Ret.Position := 0;
Ret.WriteString(Memo1.Lines.Text);
Ret.Position := 0;
end;
Obsluha je velmi primitivní: nejprve se pro jistotu nastavíme na začátek proudu (Position := 0), abychom vždy zapisovali od začátku. Poté zapíšeme do proudu požadovaný řetězec (obsah komponenty Memo) za použití metody WriteString. Nakonec se opět nastavíme na začátek proudu, více méně pro jistotu, abychom při případném budoucím čtení z proudu četli zaručeně od prvního znaku.
Toť skoro vše. Posledním úkolem je ošetření klepnutí na Button2, které způsobí naplnění komponenty RichEdit aktuálním obsahem našeho oblíbeného řetězcového proudu:
procedure TForm1.Button2Click(Sender: TObject);
begin
RichEdit1.Lines.LoadFromStream(Ret);
Ret.Position := 0;
end;
Jak jsme si řekli výše, k nalití dat do RichEditu použijeme metodu LoadFroStream. Brzy si ukážeme, že řada vizuálních komponent disponuje obdobnými metodami pro načtení nějakého obsahu ze streamu nebo naopak pro uložení nějakého obsahu do proudu. Použití těchto metod je nesmírně jednoduché; krásně přitom demonstrují hlavní sílu streamů.
Podívejte se například na onen primitivní příklad, který jsme s velkou slávou vyrobili dnes. Předpokládejme, že poslední naprogramovaná metoda, tedy metoda pro načtení textu do RichEditu, není tak primitivní, jak je. Dejme tomu, že namísto triviálního naplnění RichEditu provádí s daty nějaké šílené a náročné transformace, ať už jde o nějaké prohledávání řetězce, nebo o jakékoliv analytické operace či cokoliv jiného. Všimněte si jedné věci: metoda by mohla zůstat implementována stejným způsobem, ať už by byl zdroj našich dat jakýkoliv.
Jinak řečeno, bez ohledu na to, jestli načítáme data z disku, z paměti, z řetězce, ze socketu, z databáze či odkudkoliv jinud, pokud použijeme pro práci s daty proudy, můžeme se v jednou okamžiku rozhodnout pro změnu zdroje dat; přitom však můžeme ponechat všechny „datazpracující“ metody kompletně beze změn.
Podívejte se prosím znovu na krátkou procedurku uvedenou výše. Kdybychom se najednou rozhodli, že do svého drahocenného RichEditu nebudeme načítat řetězec, ale třeba obsah diskového souboru, nemusíme do zdrojového kódu té metody ani sáhnout. Stačí někde „výše“ změnit deklaraci proměnné Ret (nebo spíš použít místo ní nějakého předka, například TStream) a někde jinde naplnit proměnnou Ret jiným druhem streamu, například souborovým nebo síťovým. To je všechno, všechny další metody, které data ze streamu čtou a dále zpracovávají, mohou zůstat naprosto beze změny. Právě tohle je v praxi jedna z největších výhod streamů. Jeden za všechny, všichni za jednoho.

Závěrem se pojďme jako vždy podívat na kompletní zdrojový kód celého příkladu:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls;type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
RichEdit1: TRichEdit;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
Ret: TStringStream;public
{ Public declarations }
end;var
Form1: TForm1;implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
Ret.Position := 0;
Ret.WriteString(Memo1.Lines.Text);
Ret.Position := 0;
end;procedure TForm1.FormCreate(Sender: TObject);
begin
Ret := TStringStream.Create(``);Button1.Caption := `Uloz`;
Button2.Caption := `Nacti`;
Self.Caption := `Ukazka TStringStream`;
end;procedure TForm1.Button2Click(Sender: TObject);
begin
RichEdit1.Lines.LoadFromStream(Ret);
Ret.Position := 0;
end;end.
Dnes jsme se podívali na další druh streamů – na řetězcové streamy reprezentované třídou TStringStream. Ukázali jsme si primitivní, avšak doufejme názorný příklad toho, jak mohou řetězcové streamy být použity v jednoduché reálné aplikaci. Zároveň jsme si zdůraznili hlavní výhodu streamů: mohou být jednoduše nahrazeny jeden druhým, aniž bychom museli předělávat klíčové, datazpracující části aplikace.