2. Práce se seznamy
  1. 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.

  2. 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.
  3. 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:

  4. 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.
  5. 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.
  6. 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:

  7. 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.
  8. 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.
  9. 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):

  10. 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.
  11. 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:

  12. 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.
  13. 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:

  14. 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 ...).
  15. 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).

  16. 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čů.
  17. Č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.

  18. 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
    }
  19. 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).

  20. 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.
  21. 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.

  22. 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.
  23. 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).
2. Práce se seznamy