-
Mnoho různých aplikací C++ Builderu potřebuje pracovat se seznamem znakových
řetězců. Tyto seznamy jsou prvky v okně seznamu nebo kombinovaného okna,
řádky textu v komponentě Memo, seznam podporovaných písem, apod. Přestože
aplikace používají tyto seznamy různým způsobem, C++ Builder poskytuje
společné rozhraní prostřednictvím objektu nazvaného seznam řetězců. Seznam
řetězců používáme i prostřednictvím Inspektora objektů. Vlastnost seznamu
řetězců se zde zobrazuje hodnotou TStrings ve sloupci hodnot. Když
zde dvojitě klikneme je zobrazen Editor seznamu řetězců, ve kterém můžeme
editovat, přidávat nebo rušit řádky.
K zjištění počtu řetězců v seznamu používáme vlastnost Count.
Je to vlastnost určená pouze pro čtení. Jelikož indexy používané v seznamu
řetězců začínají od nuly, je hodnota Count o jedničku větší než
index posledního řetězce v seznamu. Např. aplikace může zjistit, kolik
různých písem je aktuálně podporováno obrazovkou, přečtením vlastnosti
Count
objektu seznamu písem (seznam jmen všech podporovaných písem), tj. Screen->Fonts->Count.
Seznam řetězců má indexovanou vlastnost nazvanou Strings, kterou
si můžeme představit jako seznam řetězců. Např. první řetězec v seznamu
je Strings[0]. Řetězce do seznamu lze přidávat dvěma způsoby. Jedná
se o přidání na konec seznamu a vložení do seznamu na určený index. K přidání
řetězce na konec seznamu voláme metodu seznamu Add přidávající parametr
jako nový řetězec. K vložení řetězce do seznamu voláme metodu
Insert.
Má dva parametry: index na který chceme řetězec vložit a vkládaný řetězec.
Vytvořte aplikaci obsahující komponentu Memo a tlačítko. Při
stisku tlačítka zapište do komponenty Memo jména všech obrazovkou
podporovaných písem.
-
Se seznamem řetězců můžeme provádět ještě řadu dalších operací. Často potřebujeme
zjistit pozici (index) řetězce v seznamu (nebo zjistit existenci řetězce).
K lokalizaci řetězce v seznamu používáme metodu seznamu IndexOf.
Metoda přebírá řetězec jako svůj parametr a vrací index nalezeného řetězce
nebo -1 není-li řetězec nalezen. Tato metoda pracuje pouze s kompletními
řetězci (parametr se musí celý přesně shodovat s celým řetězcem v seznamu).
Např. následující příkaz zjišťuje zda soubor WIN.INI je v seznamu
souborů v komponentě FileListBox:
if (FileListBox1->Items->IndexOf("WIN.INI")
> -1) ...
Řetězec v seznamu můžeme přesunout na jinou pozici. K přesunu řetězce
v seznamu voláme metodu seznamu Move, která má dva parametry: současný
index prvku a index, na který prvek chceme přesunout. Pro zrušení řetězce
v seznamu voláme metodu seznamu Delete a předáme ji index rušeného
řetězce. Jestliže index rušeného řetězce neznáme, použijeme metodu IndexOf
k jeho zjištění. Např. příkazy, které přidáme do naší předchozí aplikace
můžeme ze seznamu písem odstranit písmo Curier New:
if (Memo1->Lines->IndexOf("Courier New")
> -1)
Memo1->Lines->Delete(Memo1->Lines->IndexOf("Courier
New"));
Kopírování kompletního seznamu řetězců z jednoho seznamu do jiného,
provedeme přiřazením zdrojového seznamu cílovému seznamu. Např.
Memo1->Lines = ListBox1->Items;
Tím přepíšeme řetězce cílového seznamu. Jestliže chceme přidat seznam
řetězců na konec jiného seznamu, voláme metodu
AddStrings seznamu,
ke kterému přidáváme a jako parametr předáme seznam řetězců, který chceme
přidat. Např.
Memo1->AddStrings(ListBox1->Items);
Když kopírujeme řetězce mezi lokálními proměnnými, používáme k zabránění
neočekávaným výsledkům metodu Assign třídy TString. Jestliže
jednoduše přiřadíme jeden řetězec jinému pomocí lokální proměnné, pak proměnná
není kopie řetězce a původní objekt řetězce je ztracen. Následující příklad
ukazuje chybný a správný způsob kopírování řetězců pomocí lokálních proměnných:
TFont *f = new TFont();
f = Form1->Font;
// chybné
f->Assign(Form1->Font);
// správné
Doplňte předchozí aplikaci o komponentu ListBox, do které při
stisku dalšího tlačítka překopírujete všechny řetězce z komponenty Memo,
které obsahují ve svém textu CE.
-
Pomocí cyklu lze procházet jednotlivými řetězci seznamu. Do předchozí aplikace
přidejte další tlačítko, jehož stiskem provedeme převod řetězců v komponentě
Memo
na velká písmena. Pro převod řetězce na velká písmena lze použít metodu
UpperCase
třídy AnsiString.
-
Procvičování používání seznamů řetězců budeme provádět v aplikaci, která
bude vytvářet anglické věty. Nejprve si ukážeme aplikaci bez použití seznamů.
Začneme novou aplikaci. K hornímu okraji formuláře umístíme komponentu
Label,
ve které budeme vypisovat vytvářené věty. Na zbývající plochu formuláře
umístíme vedle sebe tři komponenty GroupBox a změníme jejich Caption
na ?První objekt, ?,Umístění a ?Druhý objekt. Na
lev?. GroupBox (s titulkem První objekt) vložíme nad sebe
4 voliče s texty: ?The book, ?,The pen, ?,The pencil a ?The chair.
Prostředn?. GroupBox bude obsahovat 3 voliče
s texty: ?on, ?,under a ?near. Prav?. GroupBox
bude obsahovat voliče s texty: ?the table, ?,the big box, ?,the carpet
a ?the computer. Všechny tři horní volič?. Všechny tři horní Label zvětšíme písmo a jeho Caption změníme
na ?The book is on the table.. Tento text budeme ovládat voliči. Všechny
voliče budou mít stejnou obsluhu udá?. OnClick. Obsluha
bude tvořena příkazy:
AnsiString Veta;
int I;
for (I = 0; I < GroupBox1->ControlCount;
I++)
if (((TRadioButton*)GroupBox1->Controls[I])->Checked)
Veta=Veta+((TRadioButton*)GroupBox1->Controls[I])->Caption;
Veta = Veta + " is ";
for (I = 0; I < GroupBox2->ControlCount;
I++)
if (((TRadioButton*)GroupBox2->Controls[I])->Checked)
Veta=Veta+((TRadioButton*)GroupBox2->Controls[I])->Caption;
Veta = Veta + " ";
for (I = 0; I < GroupBox3->ControlCount;
I++)
if (((TRadioButton*)GroupBox3->Controls[I])->Checked)
Veta=Veta+((TRadioButton*)GroupBox3->Controls[I])->Caption;
Label1->Caption = Veta + ".";
Aplikace je hotova, můžeme ji vyzkoušet.
-
Změňte předchozí aplikaci tak, že krajní komponenty GroupBox nahradíte
komponentami ListBox. Umožní nám to použití více slov. Do obou komponent
vložíme tato slova: big box, book, carpet, chair,
computer,
desk,
floor,
pen,
pencil,
small
box,
sofa a table (můžeme je vložit pouze do jedné komponenty
a v obsluze
OnCreate formuláře překopírovat tento seznam do druhého
seznamu). Aby se zlepšila použitelnost nastavíme u obou komponent ListBox
vlastnost Sorted na true. Slova jsou uvedena bez členů. Doplňujte
je při vytváření věty. Vybraný prvek v seznamu je určen hodnotou vlastnosti
ItemIndex.
Proveďte tuto změnu aplikace sami.
-
V aplikaci provedeme další změnu. Vybrané slovo v prvním seznamu odstraníme
z druhého seznamu a slovo vybrané v druhém seznamu odstraníme z prvního
seznamu. Po změně výběru v některém seznamu opět odstraněné slovo do seznamu
vrátíme. Je nutno si tedy odstraněná slova zapamatovat. Do soukromé části
deklarace formuláře vložíme položky (pro zapamatování odstraněných slov):
AnsiString String1, String2;
Další problém, který musíme vyřešit je ten, že na začátku není v žádné
komponentě ListBox vybrána položka. V obsluze OnCreate formuláře
tedy vložíme do položky String1, resp. String2 řetězec book,
resp. table (odpovídá to původně zobrazené větě), tato slova zrušíme
v příslušných seznamech (String1 v ListBox2 a String2
v Listbox1) a v seznamech je vybereme. V aplikaci dále umožníme
přidávání dalších slov. Na formulář umístíme editační ovladač a tlačítko
s textem Přidej. Po stisku tlačítka zjistíme, zda editační ovladač
není prázdný nebo zda neobsahuje slovo, které je již obsaženo v některém
seznamu a pokud tyto podmínky jsou splněny, pak obsah editačního ovladače
vložíme do obou seznamů (po vložení musíme opět vybrat použitá slova ve
větě; tzn. nalézt v každém seznamu použité slovo a vybrat je). Při nevhodném
obsahu editačního ovladače zobrazte okno zpráv s oznámením chyby. Vytvořte
tuto aplikaci sami.
-
Seznam řetězců lze snadno uložit do textového souboru (SaveToFile)
a opět jej zavést nebo jej zavést do jiného seznamu (LoadFromFile).
Jako parametr u těchto metod se používá jméno souboru. Např. následující
kód zavádí soubor C:\WinNT\Win.Ini do komponenty Memo a vytváří
záložní kopii souboru Win.BAK:
AnsiString FileName = "C:\\WinNT\\Win.Ini";
Memo1->Lines->LoadFromFile(FileName);
Memo1->Lines->SaveToFile(ChangeFileExt(FileName,
".BAK"));
Upravte předchozí aplikaci tak, aby slova použitá v seznamu byla při
spuštění aplikace načtena z textového souboru slova.txt, vytvořena
kopie tohoto souboru (slova.old) a při uzavření aplikace (např.
v obsluze události OnDestroy formuláře) opět zapsána do slova.txt
(do seznamu jsme mohli nějaké slovo přidat). Vytvořte také potřebný textový
soubor. Aplikaci vyzkoušejte.
-
Naši aplikaci nyní změníme takto: V prvním seznamu umožníme vícenásobný
výběr (nebudeme také již odstraňovat použitá slova ze seznamů). Vícenásobný
výběr umožníme nastavením vlastnosti MultiSelect na true.
Vybrané prvky nyní zjistíme testováním pole Selected. Např. zjištění
počtu vybraných položek provedeme příkazy:
int PocetVybr = 0;
for (int I = 0; I < ListBox1->Items->Count;
I++)
if (ListBox1->Selected[I]) PocetVybr++;
Tuto změnu proveďte sami. Snažte se, aby vytvářené věty vypadaly takto:
The
book is ..., The book, and the computer are .... The book,
the computer, and the pen are .... Uvědomte si, že v seznamu nemusí
být vybrán žádný prvek (v tomto případě vytvořte větu začínající
Nothing
is ...).
-
V další aplikaci se podrobněji seznámíme s komponentou ComboBox
(tato komponenta zabírá méně místa než ListBox). Vlastností Style
můžeme měnit chování kombinovaného ovladače. Jsou zde tyto možnosti: csDropDown
(umožňuje přímou editaci a na požádání zobrazuje seznam), csDropDownList
(neumožňuje editaci, pouze výběr ze seznamu), csSimple (umožňuje
přímou editaci a seznam zobrazen není), csOwnerDrawFixed a csOwnerDrawVariable
(seznamy s uživatelsky definovanými zobrazeními).
V následující aplikaci si ukážeme použití prvních tří z těchto stylů.
Začneme s vývojem nové aplikace. Na formulář umístíme tři komponenty ComboBox
a tlačítko s textem Přidej. Do seznamu řetězců každé komponenty
ComboBox
vložíme asi 20 jmen a nastavíme jejich vlastnosti Sorted na true.
U prvního kombinovaného ovladače nastavíme vlastnost Style na csDropDown.
Vytvoříme ještě obsluhu události OnClick tlačítka
Přidej:
if ((ComboBox1->Text!="")&&(ComboBox1->Items->IndexOf(ComboBox1->Text)<0))
ComboBox1->Items->Add(ComboBox1->Text);
Jestliže uživatel stiskne tlačítko, pak text zadaný do kombinovaného
ovladače (je-li nějaký) je přidán do seznamu, samozřejmě za předpokladu,
že tam již není.
U druhého kombinovaného ovladače nastavíme vlastnost Style na
csDropDownList.
Nepřiřadíme mu žádnou obsluhu události. Použijeme jej pro experimentování
s automatickými vyhledávacími technikami. Stiskneme-li klávesu s nějakým
písmenem, bude vybrán první řetězec v seznamu, začínající tímto písmenem.
Stiskem kláves se šipkou nahoru nebo dolů se můžeme v seznamu pohybovat
bez nutnosti jej otevřít. Tyto postupy lze použít i u ostatních kombinovaných
ovladačů.
Třetí kombinovaný ovladač (vlastnost Style nastavíme na csSimple)
přidá do seznamu nový prvek při stisku klávesy Enter. Vytvoříme pro něj
tuto obsluhu události OnKeyPress (stisknutí klávesy):
if (Key == VK_RETURN)
if((ComboBox3->Text!="")&&(ComboBox3->Items->IndexOf(ComboBox3->Text)<0))
ComboBox3->Items->Add(ComboBox3->Text);
Tím je vývoj této aplikace dokončen. Vyzkoušíme chování jednotlivých
kombinovaných ovladačů.
-
Často seznam řetězců používáme jako součást nějaké komponenty a nemusíme
tedy vytvářet seznam sami. Můžeme ale také vytvořit seznam řetězců, který
není přiřazen žádné komponentě. Pokud vytvoříme svůj vlastní seznam, nesmíme
zapomenout po ukončení práce s ním, jej opět uvolnit. Existují dva různé
způsoby používání seznamu: seznam, který aplikace v jedné metodě vytvoří,
použije a zruší a seznam, který aplikace vytváří, používá při běhu a zruší
jej před svým ukončením.
Pokud seznam řetězců potřebujeme pouze v jedné metodě, pak jej můžeme
vytvořit, použít a zrušit na jednom místě. Je to bezpečný způsob používání
objektu seznamu řetězců. Protože objekt seznamu řetězců alokuje paměť pro
samotný seznam a jeho řetězce, je důležité chránění alokace pomocí bloku
try..catch
k zajištění, že objekt uvolní svoji paměť i když vznikne výjimka. Implementace
krátkodobého seznamu tedy vypadá takto: Vytvoříme objekt seznamu řetězců,
v části try bloku try..catch jej používáme a v části catch
a po celém bloku jej uvolníme. Následující obsluha události reaguje na
stisknutí tlačítka vytvořením seznamu řetězců, jeho použitím a zrušením:
void __fastcall TForm1::Button1Click(TObject
*Sender)
{
TStrings *TempList = new TStringList();
// vytvoření objektu seznamu
try
{
// použití seznamu řetězců
}
catch (...)
{
delete TempList; // zrušení
objektu seznamu
throw;
// obnovení výjimky
}
delete TempList; // zrušení objektu
seznamu, když výjimka nenastala
}
-
Jestliže seznam řetězců potřebujeme kdykoliv při běhu aplikace, je nutno
jej vytvořit ihned po spuštění aplikace a uvolnit jej před ukončením aplikace.
To vyřešíme takto: Do třídy hlavního formuláře aplikace přidáme položku
typu TStringList, v konstruktoru hlavního formuláře vytvoříme objekt
seznamu řetězců, k deklaraci třídy hlavního formuláře přidáme destruktor
a v tomto destruktoru seznam řetězců uvolníme (před zastavením aplikace).
V následující aplikaci se pokusíme vytvořit seznam se souřadnicemi
kliknutí myši a na závěr aplikace tento seznam zapíšeme do souboru. Začneme
vývojem nové aplikace. Formulář zůstane prázdný. Jako veřejnou položku
formuláře přidáme:
TStringList *SeznamKliknuti;
Konstruktor formuláře bude tvořena příkazem:
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
SeznamKliknuti = new TStringList();
}
Obsluha OnMouseDown bude přidávat informace o kliknutí do seznamu:
SeznamKliknuti->Add("Kliknutí na " + IntToStr(X)
+ ", " + IntToStr(Y));
Zbývá ještě vytvořit destruktor formuláře. Deklaraci destruktoru musíme
přidat do veřejné části deklarace třídy formuláře a tento destruktor bude
tvořen příkazy:
__fastcall TForm1::~TForm1()
{
SeznamKliknuti->SaveToFile(ChangeFileExt(Application->ExeName,".LOG"));
delete SeznamKliknuti;
}
Zde zapíšeme seznam do souboru. Jméno tohoto souboru bude vytvořeno
ze jména aplikace, ve kterém příponu změníme na LOG (pokud nepřejmenujeme
aplikaci, pak to bude soubor PROJECT1.LOG). V destruktoru také seznam uvolníme.
Tím je aplikace hotova. Vyzkoušejte ji a pokuste se zjistit jak pracuje.
-
Mimo seznamu řetězců uložených ve vlastnosti Strings může seznam
obsahovat také seznam objektů (přesněji řečeno seznam ukazatelů na objekty),
které jsou uloženy ve vlastnosti Objects. Vlastnost Objects
je také indexovaná, je to indexovaný seznam objektů. Jestliže používáme
řetězce v seznamu, nemusíme mít také objekty; seznam nedělá nic s objekty,
pokud k nim specificky nepřistupujeme. C++ Builder pouze drží informaci
o objektu a my s ní pracujeme. Některé seznamy řetězců přidané objekty
ignorují (protože pro ně nemají význam). Např. seznam řetězců v komponentě
Memo neukládá přidané objekty. Přestože vlastnosti Objects můžeme
přiřadit libovolný typ objektu, často používáme bitové mapy. Důležité je
zapamatovat si, že řetězec a objekt tvoří pár. Pro každý řetězec je přiřazen
objekt; implicitně objekt je NULL. Seznam řetězců nevlastní přiřazené objekty,
tzn. uvolnění objektu seznamu neuvolňuje objekty přiřazené řetězcům.
Práci s objekty provádíme obdobně, jako práci s řetězci. Např. k jednotlivým
objektům přistupujeme indexací vlastnosti Objects a to stejně jako
u vlastnosti Strings. Pro přidání, vložení a lokalizaci objektu
používáme metody AddObject, InsertObject a IndexOfObject.
Metody Delete, Clear a Move pracují s prvkem seznamu
jako s celkem, tzn. zrušení prvku ruší jak řetězec, tak i odpovídající
odkaz na objekt. Metody LoadFromFile a SaveToFile operují
pouze s řetězci, neboť pracují s textovými soubory. Pro přiřazení objektu
k existujícímu řetězci přiřadíme objekt vlastnosti Objects se stejným
indexem. Např. jestliže seznam řetězců jména Ovoce obsahuje řetězec
jablko
a chceme mu přiřadit objekt bitové mapy nazvaný JablkoBitmap použijeme
následující příkaz:
Ovoce->Objects[Ovoce->IndexOf("jablko")]
= JablkoBitmap;
Objekt můžeme také přidat současně s řetězcem. Např.
Ovoce->AddObject("jablko", JablkoBitmap);
Nelze ale přidat objekt bez odpovídajícího řetězce.
-
Pokuste se vytvořit aplikaci, ve které budete zobrazovat obrázky bitových
map. Zadáte jméno souboru bitové mapy, zobrazíte ji a budeme moci zadat
zobrazení další bitové mapy. Současně budete vytvářet seznam jmen souborů
zobrazených bitových map a samotných objektů bitových map (můžeme jej považovat
za seznam historie) a volbou v tomto seznamu opět zobrazte příslušnou bitovou
mapu (bez opětovného načítání ze souboru). Pozor, každou bitovou mapu musíme
načíst na jiné místo v paměti (vytvořit pro ní nový objekt Image).